├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── examples └── HelloWorld │ ├── HelloWorld.cpp │ ├── HelloWorld.ffipp │ ├── HelloWorld.lua │ ├── build.bat │ └── build.sh ├── ffipp ├── binding_format.lua ├── binding_generator.lua ├── binding_parser.lua ├── code_generator.lua ├── detector.lua ├── init.lua ├── mangler.lua └── utility.lua └── tools └── ljgenbinding.lua /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # FFI++ Changelog 2 | 3 | ![1.3.0](https://img.shields.io/badge/1.3.0-alpha-orange.svg?style=flat-square) 4 | - FFI++ is now broken in multiple files, this is much cleaner 5 | - This also means that FFI++ requires init files to be enabled in your Lua path 6 | - Added `version_string` field to core ffipp file 7 | - Probably API compatible with 1.2.0, but maybe not 8 | 9 | ![1.2.0](https://img.shields.io/badge/1.2.0-latest-brightgreen.svg?style=flat-square) 10 | - Non-member functions no longer break binding generator 11 | - Reference argument support 12 | - Capable of building simple bindings to relatively complex (non-template) codebases 13 | 14 | ![1.1.0](https://img.shields.io/badge/1.1.0-unsupported-red.svg?style=flat-square) 15 | - Minor runtime fixes 16 | - First version of MSVC+Windows binding generator 17 | 18 | ![1.0.0](https://img.shields.io/badge/1.0.0-unsupported-red.svg?style=flat-square) 19 | - First release of FFI++ -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Lucien Greathouse 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 4 | 5 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 6 | 7 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 8 | 9 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 10 | 11 | 3. This notice may not be removed or altered from any source distribution. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LuaJIT FFI++ 2 | ![shield_license] 3 | ![shield_release_version] 4 | ![shield_prerelease_version] 5 | ![shield_dev_version] 6 | 7 | FFI++ is a C++ FFI for LuaJIT 2.0+. 8 | 9 | ## Supported Platforms/Compilers: 10 | FFI++ supports any compiler that can generate Itanium C++ ABI-compliant symbols, as well as MSVC-style symbols on Windows. 11 | 12 | ### Windows: 13 | - Visual Studio 2005+ (MSVC-style symbols) 14 | - Clang (Itanium and MSVC-style symbols) 15 | - GCC 3.0+ (Itanium-style symbols) 16 | 17 | ### Linux, Mac OS X: 18 | - GCC 3.0+ (Itanium-style symbols) 19 | - Clang (Itanium-style symbols) 20 | 21 | ## Creating a Binding 22 | ***The binding generator presently only works with MSVC-style symbols on Windows.*** 23 | 24 | ### What You Need 25 | There are two platform sets bindings should support to cover most compilers. MSVC-style symbols can be generated by Visual Studio (Windows-only) or Clang (everything). Itanium-style symbols can be generated by pretty much everything else, notably Clang (everything) and GCC 3.0+ (everything). 26 | 27 | This means that if you have a clang installation, you can generate bindings for all possible cases. Otherwise, you'll need Visual Studio and one other compiler. 28 | 29 | On Windows, you'll need `dumpbin.exe`, which you can get from Visual Studio. 30 | 31 | On Linux, you'll need `nm`, which comes with GCC on Linux. 32 | 33 | For MSVC-style symbol binding generation with the included binding generator, you'll also need a copy of `undname.exe`, which you can get from Visual Studio (Express or otherwise) or from WINE, if you're on another platform. 34 | 35 | For Itanium-style symbol binding generation, you'll need `nm`, which comes with GCC on Linux and GNU BinUtils on Windows, which can come from MinGW, Cygwin, or elsewhere. 36 | 37 | ### Actually Creating the Binding 38 | For every symbol style you need to support, you'll need a copy of the library compiled with that symbol style. For this example, I've compiled my example file, `HelloWorld.cpp` into two assemblies, `HelloWorld-msvc.dll` and `HelloWorld-itanium.dll`. I'm planning to rename them to `HelloWorld.dll` when I distribute my application, removing the compiler suffix. 39 | 40 | To produce a binding with these inputs and the desired assembly name, I would execute a command like this: 41 | 42 | ```bash 43 | luajit ljgenbinding.lua --output=HelloWorld.ffipp HelloWorld-msvc.dll 44 | ``` 45 | 46 | This produces a file named `HelloWorld.fipp` that can be loaded by FFI++ at runtime. It will automatically load an assembly named 'HelloWorld' at runtime. 47 | 48 | ## Using a Binding 49 | See the `examples` directory for directions on using a binding. 50 | 51 | ## Notes 52 | - Some compilers will not export symbols of member functions with inline definitions. If you notice a missing constructor after generating a binding, modifying the code or filling in the missing method in Lua might be necessary. 53 | 54 | [shield_license]: https://img.shields.io/badge/license-zlib/libpng-333333.svg?style=flat-square 55 | [shield_release_version]: https://img.shields.io/badge/release-1.2.0-brightgreen.svg?style=flat-square 56 | [shield_prerelease_version]: https://img.shields.io/badge/prerelease-1.3.0--alpha-blue.svg?style=flat-square 57 | [shield_dev_version]: https://img.shields.io/badge/development-1.3.0-orange.svg?style=flat-square -------------------------------------------------------------------------------- /examples/HelloWorld/HelloWorld.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* 6 | This is some really mediocre C++ code to demonstrate building a binding. 7 | 8 | Use build.bat/build.sh with GCC, or make a VS project to compile to a DLL. 9 | I'd make a real build script, but it isn't worth it right now. 10 | */ 11 | 12 | // For Windows, other platforms usually don't care about it 13 | #define DllExport __declspec(dllexport) 14 | 15 | class DllExport HelloClass { 16 | public: 17 | int64_t one; 18 | int32_t two; 19 | double three; 20 | 21 | HelloClass(); 22 | HelloClass(int64_t one, int32_t two); 23 | HelloClass(int64_t one, int32_t two, double three); 24 | 25 | ~HelloClass(); 26 | 27 | virtual void SayHello(); 28 | virtual void Say(const char* value); 29 | 30 | static void StaticHello(); 31 | private: 32 | int32_t thisIsPrivate; 33 | }; 34 | 35 | HelloClass::HelloClass() { 36 | this->one = 1; 37 | this->two = 2; 38 | this->three = 3; 39 | this->thisIsPrivate = 4; 40 | } 41 | HelloClass::~HelloClass(){} 42 | HelloClass::HelloClass(int64_t one, int32_t two) { 43 | this->one = one; 44 | this->two = two; 45 | } 46 | HelloClass::HelloClass(int64_t one, int32_t two, double three) { 47 | this->one = one; 48 | this->two = two; 49 | this->three = three; 50 | } 51 | 52 | void HelloClass::SayHello() { 53 | std::cout << "Hello, from HelloClass with\none: " << one << ", two: " << two << ", and three: " << three << std::endl; 54 | } 55 | void HelloClass::Say(const char* value) { 56 | std::cout << value << std::endl; 57 | } 58 | void HelloClass::StaticHello() { 59 | std::cout << "Hello, from HelloClass::StaticHello!" << std::endl; 60 | } -------------------------------------------------------------------------------- /examples/HelloWorld/HelloWorld.ffipp: -------------------------------------------------------------------------------- 1 | // Example FFI++ binding file 2 | // You can generate a binding like this, minus the itanium symbols and data members with 3 | // luajit ljgenbinding.lua HelloWorld-msvc.dll 4 | 5 | // Load assemblies from production or from potential development files 6 | // Prefixes (lib-) and suffixes (.dll, .so) will be added automatically. 7 | assemblies { 8 | HelloWorld 9 | HelloWorld-msvc 10 | HelloWorld-gcc 11 | } 12 | 13 | // Use these test symbols 14 | test { 15 | msvc ??0HelloClass@@QAE@XZ; 16 | itanium _ZN10HelloClassC1Ev; 17 | } 18 | 19 | class HelloClass { 20 | has_virtuals; 21 | 22 | data { 23 | //public 24 | int64_t one; 25 | int32_t two; 26 | double three; 27 | 28 | //private 29 | int32_t thisIsPrivate; 30 | } 31 | 32 | methods { 33 | !() { 34 | msvc ??0HelloClass@@QAE@XZ; 35 | itanium _ZN10HelloClassC1Ev; 36 | } 37 | 38 | !(int64_t, int32_t) { 39 | msvc ??0HelloClass@@QAE@_JH@Z; 40 | itanium _ZN10HelloClassC1Exi; 41 | } 42 | 43 | !(int64_t, int32_t, double) { 44 | msvc ??0HelloClass@@QAE@_JHN@Z; 45 | itanium _ZN10HelloClassC1Exid; 46 | } 47 | 48 | ~() { 49 | msvc ??1HelloClass@@QAE@XZ; 50 | itanium _ZN10HelloClassD1Ev; 51 | } 52 | 53 | void Say(const char*) { 54 | msvc ?Say@HelloClass@@UAEXPBD@Z; 55 | itanium _ZN10HelloClass8SayHelloEv; 56 | } 57 | 58 | void SayHello() { 59 | msvc ?SayHello@HelloClass@@UAEXXZ; 60 | itanium _ZN10HelloClass8SayHelloEv; 61 | } 62 | 63 | static void StaticHello() { 64 | msvc ?StaticHello@HelloClass@@SAXXZ; 65 | itanium _ZN10HelloClass11StaticHelloEv; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /examples/HelloWorld/HelloWorld.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/luajit 2 | 3 | -- Add FFI++ to the path; used for running this script as-is 4 | package.path = package.path .. ";../../?.lua" 5 | 6 | local ffi = require("ffi") -- C FFI 7 | local ffipp = require("ffipp") -- C++ FFI 8 | 9 | -- Load a binding and retrieve the table of defined symbols 10 | local binding = ffipp.loadfile("HelloWorld.ffipp") 11 | 12 | -- Print off a list of symbols defined by the binding 13 | print("Symbols exposed by binding:") 14 | for name, value in pairs(binding) do 15 | print(name) 16 | end 17 | 18 | -- Call a static function 19 | binding.HelloClass__StaticHello_V() 20 | 21 | -- Create a class instance and do things with it 22 | local myhello = binding.HelloClass() 23 | binding.HelloClass__C_LID(myhello, 1, 2, 3) 24 | 25 | print(myhello[0].one) --> 1LL 26 | print(myhello[0].two) --> 2 27 | print(myhello[0].three) --> 3 28 | 29 | -- Call a member function 30 | binding.HelloClass__SayHello_V(myhello) -------------------------------------------------------------------------------- /examples/HelloWorld/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call mingwvars 3 | g++ -shared -static-libgcc -static-libstdc++ -o HelloWorld-gcc.dll HelloWorld.cpp -------------------------------------------------------------------------------- /examples/HelloWorld/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | g++ -shared -static-libgcc -static-libstdc++ -o HelloWorld-gcc.so HelloWorld.cpp -------------------------------------------------------------------------------- /ffipp/binding_format.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | FFI++ for Lua 3 | Binding Format 4 | 5 | Provides a higher level interface than binding_parser and binding_generator for saving/loading bindings. 6 | ]] 7 | 8 | local parser = require("ffipp.binding_parser") 9 | local generator = require("ffipp.binding_generator") 10 | local format = {} 11 | 12 | function format.parse(body) 13 | return parser.binding(body) 14 | end 15 | 16 | function format.load(filename) 17 | local file = assert(io.open(filename, "rb")) 18 | local body = file:read() 19 | file:close() 20 | 21 | return format.parse(body) 22 | end 23 | 24 | function format.save(binding) 25 | error("not implemented") 26 | end 27 | 28 | return format -------------------------------------------------------------------------------- /ffipp/binding_generator.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | FFI++ for Lua 3 | Binding Generator 4 | 5 | STUB 6 | ]] 7 | 8 | local generator = {} 9 | 10 | return generator -------------------------------------------------------------------------------- /ffipp/binding_parser.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | FFI++ for Lua 3 | Binding Parser 4 | 5 | Parses string-format FFI++ binding files into their full format. 6 | ]] 7 | 8 | local utility = require("ffipp.utility") 9 | local parser = { 10 | matchers = {} 11 | } 12 | 13 | local function matched_parse(matchers, source, result, line) 14 | line = line or 0 15 | local i = 1 16 | 17 | while (i <= #source) do 18 | local char = source:sub(i, i) 19 | local matched = false 20 | 21 | for key, matcher in ipairs(matchers) do 22 | local count, line_count = matcher(source, result, i, line) 23 | 24 | if (count) then 25 | matched = true 26 | i = i + count 27 | 28 | if (line_count) then 29 | line = line + line_count 30 | end 31 | end 32 | end 33 | 34 | if (not matched) then 35 | if (char:match("%w")) then 36 | char = source:match("^%w+", i) or char 37 | end 38 | 39 | return nil, ("Error on line %d: unexpected token %q"):format(line, char) 40 | end 41 | end 42 | 43 | return true, line 44 | end 45 | 46 | local function strip(str) 47 | return (str:gsub("^%s+", ""):gsub("%s+$", "")) 48 | end 49 | 50 | function parser.binding(source) 51 | local result = { 52 | assemblies = {}, 53 | classes = {}, 54 | methods = {} 55 | } 56 | 57 | assert(matched_parse(parser.matchers.binding, source, result)) 58 | 59 | return result 60 | end 61 | 62 | function parser.symbols(source) 63 | local symbols = {} 64 | 65 | for compiler, symbol in source:gmatch("(%S+)%s+(%S+);") do 66 | symbols[compiler] = symbol 67 | end 68 | 69 | return symbols 70 | end 71 | 72 | function parser.assemblies(source) 73 | local assemblies = {} 74 | 75 | for assembly in source:gmatch("[^\r\n\t]+") do 76 | table.insert(assemblies, assembly) 77 | end 78 | 79 | return assemblies 80 | end 81 | 82 | function parser.class(source, line) 83 | local class = { 84 | inherits = {}, 85 | data = {}, 86 | methods = {} 87 | } 88 | 89 | local name, inherits_string, body = source:match("^class%s+(%S+)%s*:?%s*([^{]*)%s*{(.+)}$") 90 | 91 | for item in inherits_string:gmatch("[^,%s]+") do 92 | table.insert(class.inherits, item) 93 | end 94 | 95 | class.name = name 96 | 97 | local ok, line = assert(matched_parse(parser.matchers.class, body, class, line)) 98 | 99 | return class 100 | end 101 | 102 | function parser.class_data(source, line) 103 | local members = {} 104 | 105 | local body = source:match("^data%s*{(.+)}$") 106 | 107 | assert(matched_parse(parser.matchers.class_data, body, members, line)) 108 | 109 | return members 110 | end 111 | 112 | function parser.class_methods(source, line) 113 | local methods = {} 114 | 115 | local body = source:match("^methods%s*{(.+)}$") 116 | 117 | assert(matched_parse(parser.matchers.class_methods, body, methods, line)) 118 | 119 | return methods 120 | end 121 | 122 | -- Top-level symbols 123 | parser.matchers.binding = { 124 | -- spaces 125 | function(source, result, i) 126 | local matched = source:match("^%s+", i) 127 | 128 | if (matched) then 129 | return #matched, select(2, matched:gsub("\n", "")) 130 | end 131 | end, 132 | 133 | -- line comments 134 | function(source, result, i) 135 | local matched = source:match("^//[^\n]*\n?", i) 136 | 137 | if (matched) then 138 | return #matched, select(2, matched:gsub("\n", "")) 139 | end 140 | end, 141 | 142 | -- block comments 143 | function(source, result, i) 144 | local matched = source:match("^/%*.-%*/", i) 145 | 146 | if (matched) then 147 | return #matched, select(2, matched:gsub("\n", "")) 148 | end 149 | end, 150 | 151 | -- assemblies directive 152 | function(source, result, i) 153 | local matched, assemblies = source:match("^(assemblies%s*{([^}]*)})", i) 154 | 155 | if (matched) then 156 | local assemblies = parser.assemblies(assemblies) 157 | 158 | utility.dictionary_shallow_copy(assemblies, result.assemblies) 159 | 160 | return #matched, select(2, matched:gsub("\n", "")) 161 | end 162 | end, 163 | 164 | --assembly directive 165 | function(source, result, i) 166 | local matched, assembly = source:match("^(assembly%s*\"([^\"]*)\")", i) 167 | 168 | if (matched) then 169 | table.insert(result.assemblies, assembly) 170 | 171 | return #matched, select(2, matched:gsub("\n", "")) 172 | end 173 | end, 174 | 175 | -- test symbols directive 176 | function(source, result, i) 177 | local matched, body = source:match("^(test%s*{([^}]*)})", i) 178 | 179 | if (matched) then 180 | local symbols = parser.symbols(body) 181 | 182 | result.test = symbols 183 | 184 | return #matched, select(2, matched:gsub("\n", "")) 185 | end 186 | end, 187 | 188 | -- class directive 189 | function(source, result, i, line) 190 | local matched = source:match("^(class%s+%S+%s*:?[^{]*%b{})", i) 191 | 192 | if (matched) then 193 | local class = parser.class(matched, line) 194 | 195 | table.insert(result.classes, class) 196 | 197 | return #matched, select(2, matched:gsub("\n", "")) 198 | end 199 | end 200 | } 201 | 202 | -- Class symbols 203 | parser.matchers.class = { 204 | -- spaces, line comments, block comments 205 | parser.matchers.binding[1], 206 | parser.matchers.binding[2], 207 | parser.matchers.binding[3], 208 | 209 | -- has_virtuals directive 210 | function(source, result, i) 211 | local matched = source:match("^has_virtuals;", i) 212 | 213 | if (matched) then 214 | result.has_virtuals = true 215 | 216 | return #matched, select(2, matched:gsub("\n", "")) 217 | end 218 | end, 219 | 220 | -- data directive 221 | function(source, result, i) 222 | local matched = source:match("^data%s*%b{}", i) 223 | 224 | if (matched) then 225 | local members = parser.class_data(matched, line) 226 | 227 | utility.array_shallow_copy(members, result.data) 228 | 229 | return #matched, select(2, matched:gsub("\n", "")) 230 | end 231 | end, 232 | 233 | -- methods directive 234 | function(source, result, i) 235 | local matched = source:match("^methods%s*%b{}", i) 236 | 237 | if (matched) then 238 | local methods = parser.class_methods(matched, line) 239 | 240 | utility.array_shallow_copy(methods, result.methods) 241 | 242 | return #matched, select(2, matched:gsub("\n", "")) 243 | end 244 | end 245 | } 246 | 247 | parser.matchers.class_data = { 248 | parser.matchers.binding[1], 249 | parser.matchers.binding[2], 250 | parser.matchers.binding[3], 251 | 252 | -- class data member 253 | function(source, result, i) 254 | local matched, type, name = source:match("^((%S+)%s+(%S+);)", i) 255 | 256 | if (matched) then 257 | local entry = ("%s %s"):format(type, name) 258 | 259 | table.insert(result, entry) 260 | 261 | return #matched, select(2, matched:gsub("\n", "")) 262 | end 263 | end 264 | } 265 | 266 | parser.matchers.class_methods = { 267 | parser.matchers.binding[1], 268 | parser.matchers.binding[2], 269 | parser.matchers.binding[3], 270 | 271 | -- constructor 272 | function(source, result, i) 273 | local matched, arguments, body = source:match("^(!%(([^%)]*)%)%s*{([^}]*)})", i) 274 | 275 | if (matched) then 276 | local entry = { 277 | type = "constructor", 278 | arguments = {}, 279 | } 280 | 281 | for argument in arguments:gmatch("[^,]+") do 282 | table.insert(entry.arguments, strip(argument)) 283 | end 284 | 285 | entry.symbols = parser.symbols(body) 286 | 287 | table.insert(result, entry) 288 | 289 | return #matched, select(2, matched:gsub("\n", "")) 290 | end 291 | end, 292 | 293 | -- destructor 294 | function(source, result, i) 295 | local matched, arguments, body = source:match("^(~%(([^%)]*)%)%s*{([^}]*)})", i) 296 | 297 | if (matched) then 298 | local entry = { 299 | type = "destructor", 300 | arguments = {}, 301 | } 302 | 303 | for argument in arguments:gmatch("[^,]+") do 304 | table.insert(entry.arguments, strip(argument)) 305 | end 306 | 307 | entry.symbols = parser.symbols(body) 308 | 309 | table.insert(result, entry) 310 | 311 | return #matched, select(2, matched:gsub("\n", "")) 312 | end 313 | end, 314 | 315 | -- member function 316 | function(source, result, i) 317 | local matched, returns, name, arguments, body = source:match("^(([%w%s%*]+)%s+([%w%*]+)%(([^%)]*)%)%s*(%b{}))", i) 318 | 319 | if (matched) then 320 | if (returns:match("%s*static%s*")) then 321 | return 322 | end 323 | 324 | body = body:sub(2, -2) 325 | local entry = { 326 | type = "member_function", 327 | name = name, 328 | returns = returns, 329 | arguments = {}, 330 | } 331 | 332 | for argument in arguments:gmatch("[^,]+") do 333 | table.insert(entry.arguments, strip(argument)) 334 | end 335 | 336 | entry.symbols = parser.symbols(body) 337 | 338 | table.insert(result, entry) 339 | 340 | return #matched, select(2, matched:gsub("\n", "")) 341 | end 342 | end, 343 | 344 | -- static member function 345 | function(source, result, i) 346 | local matched, returns, name, arguments, body = source:match("^(static%s+([%w%s%*]+)%s+([%w%*]+)%(([^%)]*)%)%s*(%b{}))", i) 347 | 348 | if (matched) then 349 | body = body:sub(2, -2) 350 | local entry = { 351 | type = "static_member_function", 352 | name = name, 353 | returns = returns, 354 | arguments = {}, 355 | } 356 | 357 | for argument in arguments:gmatch("[^,]+") do 358 | table.insert(entry.arguments, strip(argument)) 359 | end 360 | 361 | entry.symbols = parser.symbols(body) 362 | 363 | table.insert(result, entry) 364 | 365 | return #matched, select(2, matched:gsub("\n", "")) 366 | end 367 | end 368 | } 369 | 370 | return parser -------------------------------------------------------------------------------- /ffipp/code_generator.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | FFI++ for LuaJIT 3 | Code Generator 4 | 5 | Generates C code to be used by the regular C FFI. 6 | ]] 7 | 8 | local ffi = require("ffi") 9 | 10 | local mangler = require("ffipp.mangler") 11 | local generator = { 12 | defines = {}, 13 | names = {}, 14 | templates = {}, 15 | report_code = true 16 | } 17 | 18 | generator.templates.class_body = [[ 19 | {{inherits}} 20 | {{vfptr}} 21 | {{members}} 22 | ]] 23 | 24 | generator.templates.class = [[ 25 | typedef struct { 26 | {{class_body}} 27 | } {{name}}; 28 | ]] 29 | 30 | generator.templates.member_function_name = [[{{classname}}__{{name}}_{{type_suffix}}]] 31 | generator.templates.member_function = [[{{returns}} __thiscall {{name}}({{classname}}*{{arguments}}) asm("{{symbol}}");]] 32 | generator.templates.static_member_function_name = [[{{classname}}__{{name}}_{{type_suffix}}]] 33 | generator.templates.static_member_function = [[{{returns}} __cdecl {{name}}({{arguments}}) asm("{{symbol}}");]] 34 | generator.templates.constructor_name = [[{{classname}}__C_{{type_suffix}}]] 35 | generator.templates.constructor = [[{{classname}}* __thiscall {{name}}({{classname}}*{{arguments}}) asm("{{symbol}}");]] 36 | generator.templates.destructor_name = [[{{classname}}__D_{{type_suffix}}]] 37 | generator.templates.destructor = [[void __thiscall {{name}}({{classname}}*{{arguments}}) asm("{{symbol}}");]] 38 | 39 | --[[ 40 | string get_argument_list(string[] arguments) 41 | arguments: A list of arguments a method has. 42 | 43 | Returns a string to be used in an FFI++ template involving a list of arguments. 44 | ]] 45 | local function get_argument_list(arguments) 46 | if (arguments and #arguments > 0) then 47 | return ", " .. table.concat(arguments, ",") 48 | else 49 | return "" 50 | end 51 | end 52 | 53 | local function cdef(block) 54 | if (generator.report_code) then 55 | print(block) 56 | end 57 | ffi.cdef(block) 58 | end 59 | 60 | function generator.names.class(definition) 61 | local unmangled = definition.unmangled_name 62 | 63 | if (unmangled) then 64 | return unmangled 65 | end 66 | 67 | definition.unmangled_name = definition.name:gsub(":", "_") 68 | 69 | return definition.unmangled_name 70 | end 71 | 72 | function generator.defines.class_body(info, definition, classes) 73 | local members_list = {} 74 | 75 | if (definition.data) then 76 | for i, member in ipairs(definition.data) do 77 | table.insert(members_list, member .. ";") 78 | end 79 | end 80 | 81 | local name = generator.names.class(definition) 82 | 83 | local inherits = {} 84 | if (definition.inherits and #definition.inherits > 0) then 85 | for key, classname in ipairs(definition.inherits) do 86 | if (not classes[classname]) then 87 | error(("Class %q cannot inherit from unknown class %q."):format(name, classname), 4) 88 | return 89 | end 90 | 91 | table.insert(inherits, generator.defines.class_body(info, classes[classname], classes)) 92 | end 93 | end 94 | 95 | local vfptr 96 | if (definition.has_virtuals) then 97 | vfptr = "void* vfptr_" .. name .. ";" 98 | else 99 | vfptr = "" 100 | end 101 | 102 | local body = generator.templates.class_body 103 | :gsub("{{inherits}}", table.concat(inherits, "\n")) 104 | :gsub("{{vfptr}}", vfptr) 105 | :gsub("{{members}}", table.concat(members_list, "\n")) 106 | 107 | return body 108 | end 109 | 110 | function generator.defines.class(info, definition, classes) 111 | local name = generator.names.class(definition) 112 | local body = generator.defines.class_body(info, definition, classes) 113 | 114 | local def = generator.templates.class 115 | :gsub("{{name}}", name) 116 | :gsub("{{class_body}}", body) 117 | 118 | cdef(def) 119 | 120 | classes[name] = definition 121 | 122 | local defines = {} 123 | 124 | if (definition.methods) then 125 | for i, method in pairs(definition.methods) do 126 | if (generator.defines[method.type]) then 127 | local key, value = generator.defines[method.type](info, method, definition) 128 | 129 | if (key and value) then 130 | defines[key] = value 131 | end 132 | end 133 | end 134 | end 135 | 136 | defines[name] = ffi.typeof(name .. "[1]") 137 | 138 | return defines 139 | end 140 | 141 | function generator.names.constructor(definition, class) 142 | return ( 143 | generator.templates.constructor_name 144 | :gsub("{{classname}}", generator.names.class(class)) 145 | :gsub("{{type_suffix}}", mangler.get_type_suffix(definition)) 146 | ) 147 | end 148 | 149 | function generator.defines.constructor(info, definition, class) 150 | local name = generator.names.constructor(definition, class) 151 | 152 | if (not definition.symbols) then 153 | print(("No symbols defined for constructor %q: skipping"):format(name)) 154 | return 155 | end 156 | 157 | local arguments = get_argument_list(definition.arguments) 158 | 159 | local def = generator.templates.constructor 160 | :gsub("{{name}}", name) 161 | :gsub("{{classname}}", generator.names.class(class)) 162 | :gsub("{{arguments}}", arguments) 163 | :gsub("{{symbol}}", definition.symbols[info.compiler]) 164 | 165 | cdef(def) 166 | 167 | return name, info.assembly[name] 168 | end 169 | 170 | function generator.names.destructor(definition, class) 171 | return ( 172 | generator.templates.destructor_name 173 | :gsub("{{classname}}", generator.names.class(class)) 174 | :gsub("{{type_suffix}}", mangler.get_type_suffix(definition)) 175 | ) 176 | end 177 | 178 | function generator.defines.destructor(info, definition, class) 179 | local name = generator.names.destructor(definition, class) 180 | 181 | if (not definition.symbols) then 182 | print(("No symbols defined for destructor %q: skipping"):format(name)) 183 | return 184 | end 185 | 186 | local arguments = get_argument_list(definition.arguments) 187 | 188 | local def = generator.templates.destructor 189 | :gsub("{{name}}", name) 190 | :gsub("{{classname}}", generator.names.class(class)) 191 | :gsub("{{arguments}}", arguments) 192 | :gsub("{{symbol}}", definition.symbols[info.compiler]) 193 | 194 | cdef(def) 195 | 196 | return name, info.assembly[name] 197 | end 198 | 199 | function generator.names.member_function(definition, class) 200 | return ( 201 | generator.templates.member_function_name 202 | :gsub("{{classname}}", generator.names.class(class)) 203 | :gsub("{{name}}", definition.name) 204 | :gsub("{{type_suffix}}", mangler.get_type_suffix(definition)) 205 | ) 206 | end 207 | 208 | function generator.defines.member_function(info, definition, class) 209 | local name = generator.names.member_function(definition, class) 210 | 211 | if (not definition.symbols) then 212 | print(("No symbols defined for member function %q: skipping"):format(name)) 213 | return 214 | end 215 | 216 | if (not definition.symbols[info.compiler]) then 217 | print(("No symbols defined for member function %q for this compiler: skipping"):format(name)) 218 | return 219 | end 220 | 221 | local arguments = get_argument_list(definition.arguments) 222 | 223 | local def = generator.templates.member_function 224 | :gsub("{{returns}}", definition.returns) 225 | :gsub("{{name}}", name) 226 | :gsub("{{classname}}", generator.names.class(class)) 227 | :gsub("{{arguments}}", arguments) 228 | :gsub("{{symbol}}", definition.symbols[info.compiler]) 229 | 230 | cdef(def) 231 | 232 | return name, info.assembly[name] 233 | end 234 | 235 | function generator.names.static_member_function(definition, class) 236 | return ( 237 | generator.templates.static_member_function_name 238 | :gsub("{{classname}}", generator.names.class(class)) 239 | :gsub("{{name}}", definition.name) 240 | :gsub("{{type_suffix}}", mangler.get_type_suffix(definition)) 241 | ) 242 | end 243 | 244 | function generator.defines.static_member_function(info, definition, class) 245 | local name = generator.names.static_member_function(definition, class) 246 | 247 | if (not definition.symbols) then 248 | print(("No symbols defined for static member function %q: skipping"):format(name)) 249 | return 250 | end 251 | 252 | local arguments = table.concat(definition.arguments, ", ") 253 | 254 | local def = generator.templates.static_member_function 255 | :gsub("{{returns}}", definition.returns) 256 | :gsub("{{name}}", name) 257 | :gsub("{{classname}}", generator.names.class(class)) 258 | :gsub("{{arguments}}", arguments) 259 | :gsub("{{symbol}}", definition.symbols[info.compiler]) 260 | 261 | cdef(def) 262 | 263 | return name, info.assembly[name] 264 | end 265 | 266 | return generator -------------------------------------------------------------------------------- /ffipp/detector.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | FFI++ for LuaJIT 3 | Detector 4 | 5 | Locates assemblies and determines the compiler used to compile them. 6 | ]] 7 | 8 | local ffi = require("ffi") 9 | local utility = require("ffipp.utility") 10 | 11 | local detector = {} 12 | 13 | local compiler_test_count = 0 14 | local test_template = [[void %s() asm("%s");]] 15 | 16 | --[[ 17 | string? detect_compiler(Assembly assembly, dictionary test_symbols) 18 | assembly: The assembly (by default ffi.C, obtained by ffi.load) that contains the symbols. 19 | test_symbols: A dictionary of symbols mapping compiler names to test symbols. 20 | 21 | Returns the name of the compiler that probably compiled this assembly, or nil if it could not be determined. 22 | ]] 23 | function detector.compiler(assembly, test_symbols) 24 | assembly = assembly or ffi.C 25 | local template = test_template 26 | 27 | compiler_test_count = compiler_test_count + 1 28 | 29 | for compiler, symbol in pairs(test_symbols) do 30 | local name = ("_TEST_%s_%d"):format(compiler, compiler_test_count) 31 | 32 | ffi.cdef(template:format( 33 | name, 34 | symbol 35 | )) 36 | 37 | if (pcall(utility.index, assembly, name)) then 38 | return compiler 39 | end 40 | end 41 | 42 | return nil 43 | end 44 | 45 | --[[ 46 | Assembly?, string? choose_assembly(dictionary binding, Assembly assembly) 47 | binding: The binding to choose for. 48 | assembly: A runtime-chosen assembly to use instead of a definition-specified one. 49 | 50 | Chooses an assembly to represent the binding based on the assembly names and test symbols it has. 51 | Also returns the compiler that compiled the chosen assembly. 52 | ]] 53 | function detector.choose_assembly(binding, assembly) 54 | local assemblies 55 | 56 | if (type(assembly) == "table") then 57 | assemblies = assembly 58 | else 59 | assemblies = {assembly} 60 | end 61 | 62 | if (binding.assemblies) then 63 | for i, try in ipairs(binding.assemblies) do 64 | local ok, loaded = pcall(ffi.load, try) 65 | 66 | if (ok) then 67 | table.insert(assemblies, loaded) 68 | end 69 | end 70 | 71 | if (#assemblies == 0) then 72 | error(("Binding contained multiple assembly names (%s), but none of them could be located."):format( 73 | table.concat(binding.assemblies, ", ")), 3) 74 | end 75 | else 76 | table.insert(assemblies, ffi.C) 77 | end 78 | 79 | for key, assembly in ipairs(assemblies) do 80 | local compiler = detector.compiler(assembly, binding.test) 81 | 82 | if (compiler) then 83 | return assembly, compiler 84 | end 85 | end 86 | 87 | error("No suitable assembly-compiler combination was found to load the binding.") 88 | end 89 | 90 | return detector -------------------------------------------------------------------------------- /ffipp/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | LuaJIT C++ FFI 3 | 4 | This is the runtime for FFI++ 5 | 6 | Type Remangler: 7 | Members need to be re-mangled to avoid collisions given function with the same name but different arguments. 8 | This is allowed in C++ and is common practice but isn't allowed in C. 9 | Since differing only by return type isn't allowed in C++, we don't encode the return type in our names. 10 | 11 | Reference types: R* where * is the type 12 | Pointer to type: P* where * is the type 13 | Const: Q* where * is the type 14 | 64-bit integer: L for signed, l for unsigned 15 | 32-bit integer: I for signed, i for unsigned 16 | 16-bit integer: S for signed, s for unsigned 17 | 8-bit integer: C for signed, c for unsigned 18 | double: D 19 | float: F 20 | void: V 21 | struct: name of struct surrounded by underscores 22 | 23 | Samples: 24 | MangleMe(const char* str, uint32_t length, void* callback); 25 | becomes 26 | MangleMe_PQCiPV 27 | 28 | MeToo(std::string, lib_callback callback); 29 | becomes 30 | MeToo__std__string__lib_callback_ 31 | ]] 32 | 33 | local ffi = require("ffi") 34 | 35 | local code_generator = require("ffipp.code_generator") 36 | local binding_format = require("ffipp.binding_format") 37 | local detector = require("ffipp.detector") 38 | local utility = require("ffipp.utility") 39 | 40 | local ffipp = { 41 | version = {1, 3, 0, "alpha"}, 42 | version_string = "1.3.0-alpha" 43 | } 44 | 45 | --[[ 46 | Binding loadfile(string filename, Assembly? assembly) 47 | filename: The location of the FFI++ binding on disk. 48 | assembly: An assembly to override the 'assembly' directive of the binding. 49 | 50 | Loads an FFI++ binding from a file and returns the result. 51 | ]] 52 | function ffipp.loadfile(filename, assembly) 53 | local file, err = io.open(filename, "rb") 54 | 55 | if (not file) then 56 | error(err, 2) 57 | end 58 | 59 | local contents = file:read("*a") 60 | file:close() 61 | 62 | return ffipp.load(contents, assembly) 63 | end 64 | 65 | --[[ 66 | Binding load(string contents, Assembly? assembly) 67 | contents: An FFI++ binding in source form. 68 | assembly: An assembly to override the 'assembly' directive of the binding. 69 | 70 | Loads an FFI++ binding from a string and returns the result. 71 | This is closer to the style of LuaJIT's C FFI than ffipp.loadfile. 72 | ]] 73 | function ffipp.load(contents, assembly) 74 | local parsed, err = binding_format.parse(contents) 75 | 76 | if (not parsed) then 77 | return nil, err 78 | end 79 | 80 | return ffipp.define_binding(parsed) 81 | end 82 | 83 | --[[ 84 | dictionary define_binding(Binding binding, Assembly assembly) 85 | binding: A generated and parsed FFI++ binding structure that maps symbols to their meanings. 86 | assembly: The assembly (given by the binding by default, or ffi.C if there are none) that contains the symbols. 87 | 88 | Generates code to handle a given C++ binding and creates the necessary symbols. 89 | A table of symbols is returned. 90 | ]] 91 | function ffipp.define_binding(binding, assembly) 92 | assembly, compiler = detector.choose_assembly(binding, assembly) 93 | 94 | print(("Loading binding for compiler %q..."):format(compiler)) 95 | 96 | local info = { 97 | assembly = assembly, 98 | compiler = compiler 99 | } 100 | 101 | local items = {} 102 | local classes = {} 103 | 104 | if (binding.classes) then 105 | for i, class in pairs(binding.classes) do 106 | local defined = code_generator.defines.class(info, class, classes) 107 | 108 | utility.dictionary_shallow_copy(defined, items) 109 | end 110 | end 111 | 112 | return items 113 | end 114 | 115 | return ffipp -------------------------------------------------------------------------------- /ffipp/mangler.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | FFI++ for LuaJIT 3 | Mangler 4 | ]] 5 | 6 | local mangler = { 7 | type_codes = { 8 | ["long long"] = "L", 9 | ["int64_t"] = "L", 10 | ["unsigned long long"] = "l", 11 | ["uint64_t"] = "l", 12 | 13 | ["int"] = "I", 14 | ["int32_t"] = "I", 15 | ["unsigned int"] = "i", 16 | ["uint32_t"] = "i", 17 | 18 | ["short"] = "S", 19 | ["int16_t"] = "S", 20 | ["unsigned short"] = "s", 21 | ["uint16_t"] = "s", 22 | 23 | ["char"] = "C", 24 | ["signed char"] = "C", 25 | ["int8_t"] = "C", 26 | ["unsigned char"] = "c", 27 | ["uint8_t"] = "c", 28 | 29 | ["double"] = "D", 30 | ["float"] = "F", 31 | 32 | ["void"] = "V" 33 | } 34 | } 35 | 36 | --[[ 37 | string get_type_suffix(Definition definition) 38 | definition: The C++ method definition to generate a suffix for 39 | 40 | Returns the argument-based suffix a function should have to prevent name clashes. 41 | ]] 42 | function mangler.get_type_suffix(definition) 43 | local buffer = {} 44 | 45 | if (#definition.arguments == 0) then 46 | return "V" 47 | end 48 | 49 | for key, argument in ipairs(definition.arguments) do 50 | local is_pointer = not not argument:match("%*") 51 | local is_const = not not argument:match("const%s+") 52 | local is_ref = not not argument:match("&") 53 | local regular_name = argument:gsub("%s*%*%s*", ""):gsub("const%s+", ""):gsub("%s*&%s*", "") 54 | 55 | if (mangler.type_codes[regular_name]) then 56 | regular_name = mangler.type_codes[regular_name] 57 | else 58 | regular_name = "_" .. regular_name .. "_" 59 | end 60 | 61 | if (is_ref) then 62 | regular_name = "R" .. regular_name 63 | end 64 | 65 | if (is_const) then 66 | regular_name = "Q" .. regular_name 67 | end 68 | 69 | if (is_pointer) then 70 | regular_name = "P" .. regular_name 71 | end 72 | 73 | table.insert(buffer, regular_name) 74 | end 75 | 76 | return table.concat(buffer, "") 77 | end 78 | 79 | function mangler.mangle(definition) 80 | local translated = definition.name:gsub(":", "_") 81 | return translated .. mangler.get_type_suffix(definition) 82 | end 83 | 84 | return mangler -------------------------------------------------------------------------------- /ffipp/utility.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | FFI++ for LuaJIT 3 | General Utilities 4 | ]] 5 | 6 | local utility = {} 7 | 8 | --[[ 9 | (dictionary to) dictionary_shallow_copy(dictionary from, dictionary? to) 10 | from: The data source. 11 | to: The data target. 12 | 13 | Shallow-copies all values from a dictionary. 14 | Optionally copies into an existing table. 15 | ]] 16 | function utility.dictionary_shallow_copy(from, to) 17 | to = to or {} 18 | 19 | for key, value in pairs(from) do 20 | to[key] = value 21 | end 22 | end 23 | 24 | --[[ 25 | (array to) array_shallow_copy(array from, array? to) 26 | from: The data source. 27 | to: The data target. 28 | 29 | Shallow copies an array. 30 | Optionally copies into an existing table. 31 | ]] 32 | function utility.array_shallow_copy(from, to) 33 | to = to or {} 34 | 35 | for key, value in ipairs(from) do 36 | table.insert(to, value) 37 | end 38 | 39 | return to 40 | end 41 | 42 | --[[ 43 | mixed index(indexable item, index key) 44 | item: The item to query. 45 | key: The index of the item to search for. 46 | 47 | Functionally equivalent to rawget, with less raw. 48 | Used alongside pcall to check for valid FFI definitions. 49 | ]] 50 | function utility.index(item, key) 51 | return item[key] 52 | end 53 | 54 | return utility -------------------------------------------------------------------------------- /tools/ljgenbinding.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | FFI++ Binding Generator 3 | 4 | Creates a binding file translating C++ symbols to meaningful data. 5 | 6 | This is a prototype and only supports MSVC on Windows right now. 7 | Other compilers and platforms are coming soon. 8 | ]] 9 | 10 | assert(jit, "LuaJIT 2.x is required!") 11 | assert(io.popen, "io.popen is required!") 12 | 13 | local args = {...} 14 | local libver = "1.2.0" 15 | local config = { 16 | lister = (jit.os == "Windows") and "dumpbin" or "nm", 17 | output = nil, -- defaults to [assembly].ffipp 18 | _assemblies = {} 19 | } 20 | 21 | --[[ 22 | Symbol list interfaces list all the exported symbols in an assembly. 23 | Lister interfaces must implement the following methods: 24 | 25 | bool platform:available() 26 | sequence platform:get_symbol_list(string assembly_path) 27 | ]] 28 | local listers = {} 29 | 30 | --[[ 31 | Compiler interfaces demangle symbols and parse them into useful data. 32 | They must implement the following methods: 33 | 34 | bool compiler:available() 35 | symbol_data? compiler:demangle(symbol) 36 | 37 | symbol_data looks like the following: 38 | { 39 | type = "constructor" -- or destructor, member_function, static_member_function 40 | classname = "classname", 41 | arguments = {"int", "std::string"}, 42 | virtual = true/false 43 | } 44 | ]] 45 | local compilers = {} 46 | 47 | --===========-- 48 | -- UTILITIES -- 49 | --===========-- 50 | 51 | local function array_shallow_copy(from, to) 52 | to = to or {} 53 | 54 | for i, value in ipairs(from) do 55 | table.insert(to, value) 56 | end 57 | 58 | return to 59 | end 60 | 61 | local function strip(str) 62 | return (str:gsub("^%s*", ""):gsub("%s*$", "")) 63 | end 64 | 65 | --=========-- 66 | -- LISTERS -- 67 | --=========-- 68 | 69 | do 70 | -- dumpbin /exports [assembly] 71 | -- resulting format is [ordinal] [hint] [RVA] [symbol] [garbage/RTTI]\n 72 | local dumpbin = {} 73 | listers.dumpbin = dumpbin 74 | 75 | function dumpbin:available() 76 | -- check for dumpbin.exe (msvc's symbol lister) 77 | local handle = io.popen("dumpbin") 78 | local body = handle:read("*a") 79 | handle:close() 80 | 81 | return not not body:match("Microsoft") 82 | end 83 | 84 | function dumpbin:get_symbol_list(assembly_path) 85 | local handle = io.popen("dumpbin /exports \"" .. assembly_path .. "\"", "rb") 86 | local body = handle:read("*a") 87 | handle:close() 88 | 89 | local symbols = {} 90 | for symbol in body:gmatch(" +%d+ +%x+ +%x+ +(%S+).-\n") do 91 | table.insert(symbols, symbol) 92 | end 93 | 94 | return symbols 95 | end 96 | end 97 | 98 | do 99 | -- nm -g [assembly] 100 | -- NOT IMPLEMENTED 101 | local nm = {} 102 | listers.nm = nm 103 | 104 | function nm:available() 105 | return false 106 | end 107 | 108 | function nm:get_symbol_list(assembly_path) 109 | end 110 | end 111 | 112 | do 113 | -- readelf -Ws [assembly] | awk '{print $8}' 114 | -- NOT IMPLEMENTED 115 | local readelf = {} 116 | listers.readelf = readelf 117 | 118 | function readelf:available() 119 | return false 120 | end 121 | 122 | function readelf:get_symbol_list(assembly_path) 123 | end 124 | end 125 | 126 | --===========-- 127 | -- COMPILERS -- 128 | --===========-- 129 | 130 | do 131 | -- MSVC ABI compilers (MSVC, Clang for Windows, IC++ for Windows) 132 | local msvc = {} 133 | compilers.msvc = msvc 134 | 135 | local type_map = { 136 | __uint8 = "uint8_t", 137 | __int8 = "int8_t", 138 | __uint16 = "uint16_t", 139 | __int16 = "int16_t", 140 | __uint32 = "uint32_t", 141 | __int32 = "int32_t", 142 | __uint64 = "uint64_t", 143 | __int64 = "int64_t" 144 | } 145 | 146 | function msvc:available() 147 | -- check for undname.exe (msvc's symbol demangler) 148 | local handle = io.popen("undname") 149 | local body = handle:read("*a") 150 | handle:close() 151 | 152 | return not not body:match("Microsoft") 153 | end 154 | 155 | function msvc:demangle(symbol) 156 | local handle = io.popen("undname " .. symbol, "rb") 157 | local body = handle:read("*a") 158 | handle:close() 159 | 160 | local demangled = body:match("is :%- \"([^\"]+)\"") 161 | 162 | -- Probably not an MSVC symbol 163 | if (demangled == symbol) then 164 | return nil 165 | end 166 | 167 | --print(demangled) 168 | 169 | local prefix, convention, name, arguments_string = demangled:match("([%w_ ]*) ([%w_]+) ([%w_:~ ]+)(%b())$") 170 | 171 | -- Probably not a symbol we care about 172 | -- FIXME: operators fall through here 173 | if (not prefix) then 174 | return nil, ("MSVC: Unsupported symbol %q, skipping."):format(symbol) 175 | end 176 | 177 | -- Strip parens off of argument list 178 | arguments_string = arguments_string:sub(2, -2) 179 | local arguments = {} 180 | 181 | -- FIXME: templates with multiple arguments will fail to be parsed correctly 182 | for argument in arguments_string:gmatch("[^,]+") do 183 | table.insert(arguments, argument) 184 | end 185 | 186 | local classname 187 | local is_constructor 188 | local is_standalone 189 | do 190 | local a, b = name:match("([%w_]+)::([%w_~]+)$") 191 | 192 | is_constructor = a and b and (a == b) 193 | 194 | if (is_constructor) then 195 | classname = a 196 | elseif (a and b) then 197 | classname = a 198 | name = b 199 | else 200 | is_standalone = true 201 | print("\tSymbol failure:", demangled) 202 | end 203 | end 204 | 205 | -- Will this break on anything? 206 | local is_destructor = not not name:match("~") 207 | local is_virtual = not not prefix:match("virtual") 208 | local is_static = not not prefix:match("static") 209 | 210 | prefix = prefix:gsub("virtual", ""):gsub("static", "") 211 | 212 | if (arguments[1] and arguments[1]:match("class %w+ const &")) then 213 | return nil, "MSVC: Throwing away unnecessary constructor on class " .. classname 214 | end 215 | 216 | for i, argument in ipairs(arguments) do 217 | if (type_map[argument]) then 218 | arguments[i] = type_map[argument] 219 | end 220 | 221 | local t, rest = argument:match("(%w+) const (.+)") 222 | 223 | if (t) then 224 | arguments[i] = "const " .. t .. " " .. rest 225 | end 226 | 227 | arguments[i] = argument:gsub("class%s+", "") 228 | end 229 | 230 | local out = { 231 | classname = classname, 232 | arguments = arguments, 233 | virtual = is_virtual 234 | } 235 | 236 | if (is_standalone) then 237 | out.type = "function" 238 | out.name = name 239 | out.returns = strip(prefix) 240 | elseif (is_constructor) then 241 | out.type = "constructor" 242 | elseif (is_destructor) then 243 | out.type = "destructor" 244 | elseif (is_static) then 245 | out.name = name 246 | out.type = "static_member_function" 247 | out.returns = strip(prefix) 248 | else 249 | out.name = name 250 | out.type = "member_function" 251 | out.returns = strip(prefix) 252 | end 253 | 254 | return out 255 | end 256 | end 257 | 258 | do 259 | -- Itanium ABI compilers (GCC, Clang for *nix, IC++ for Linux) 260 | local itanium = {} 261 | compilers.itanium = itanium 262 | 263 | function itanium:available() 264 | return false 265 | end 266 | 267 | function itanium:demangle(symbol) 268 | end 269 | end 270 | 271 | --======-- 272 | -- MAIN -- 273 | --======-- 274 | 275 | local function deep_match(a, b, ignore) 276 | return false--[[ 277 | if (not a or not b) then 278 | return false 279 | end 280 | 281 | if (type(a) ~= type(b)) then 282 | return false 283 | end 284 | 285 | ignore = ignore or {} 286 | 287 | for key, value in pairs(a) do 288 | if (not ignore[key]) then 289 | if (type(value) == "table") then 290 | if (not deep_match(value, b[key])) then 291 | return false 292 | end 293 | else 294 | if (not value == b[key]) then 295 | return false 296 | end 297 | end 298 | end 299 | end 300 | 301 | return true]] 302 | end 303 | 304 | local function main() 305 | print(("FFI++ Binding Generator %s for LuaJIT Initialized\n"):format(libver)) 306 | 307 | -- Parse arguments and throw results into config 308 | for key, value in ipairs(args) do 309 | local index, set = value:match("^%-%-([^=]-)=(.*)$") 310 | 311 | if (index and set) then 312 | config[index] = set 313 | else 314 | table.insert(config._assemblies, value) 315 | end 316 | end 317 | 318 | local lister = listers[config.lister] 319 | local have_compilers = {} 320 | local have_compiler = false 321 | 322 | for key, compiler in pairs(compilers) do 323 | if (compiler:available()) then 324 | have_compiler = true 325 | have_compilers[key] = compiler 326 | end 327 | end 328 | 329 | if (not lister) then 330 | print(("Unknown symbol lister %q, terminating..."):format(config.lister)) 331 | return 332 | end 333 | 334 | if (not lister:available()) then 335 | print(("Lister %q could not be found on this system, terminating..."):format(config.lister)) 336 | return 337 | end 338 | 339 | if (not have_compiler) then 340 | print("No compiler toolkits were available (undname or c++filt), terminating...") 341 | return 342 | end 343 | 344 | if (#config._assemblies == 0) then 345 | print("No input files, terminating...") 346 | return 347 | end 348 | 349 | local compiler_names = {} 350 | 351 | for name in pairs(have_compilers) do 352 | table.insert(compiler_names, name) 353 | end 354 | 355 | print("Supported compiler toolkits: " .. table.concat(compiler_names, ", ")) 356 | 357 | print("Demangling symbols...") 358 | 359 | config.output = config.output or config._assemblies[1] .. ".ffipp" 360 | 361 | local symbols = {} 362 | 363 | for i, assembly_name in ipairs(config._assemblies) do 364 | local list = lister:get_symbol_list(assembly_name) 365 | array_shallow_copy(list, symbols) 366 | end 367 | 368 | local test_symbols = {} 369 | local classes = {} 370 | local symbol_count = 0 371 | local class_count = 0 372 | for i, symbol in ipairs(symbols) do 373 | local demangled, compiler_name, why 374 | 375 | for name, compiler in pairs(have_compilers) do 376 | local try, err = compiler:demangle(symbol) 377 | 378 | if (try) then 379 | compiler_name = name 380 | demangled = try 381 | break 382 | elseif (err) then 383 | why = err 384 | end 385 | end 386 | 387 | if (demangled) then 388 | if (not test_symbols[compiler_name]) then 389 | test_symbols[compiler_name] = symbol 390 | end 391 | 392 | symbol_count = symbol_count + 1 393 | 394 | if (demangled.classname) then 395 | local class = classes[demangled.classname] 396 | 397 | if (not class) then 398 | class_count = class_count + 1 399 | classes[demangled.classname] = { 400 | name = demangled.classname, 401 | has_virtuals = false, 402 | data = {}, 403 | methods = {} 404 | } 405 | class = classes[demangled.classname] 406 | end 407 | 408 | if (demangled.virtual) then 409 | class.has_virtuals = true 410 | end 411 | 412 | local existing 413 | for key, method in ipairs(class.methods) do 414 | if (deep_match(demangled, method, {symbols = true})) then 415 | existing = method 416 | break 417 | end 418 | end 419 | 420 | if (existing) then 421 | table.insert(existing.symbols, compiler_name .. " " .. symbol) 422 | else 423 | table.insert(class.methods, demangled) 424 | demangled.symbols = {compiler_name .. " " .. symbol} 425 | end 426 | else 427 | print("symbol is standalone, not supported in 0.1.0") 428 | end 429 | else 430 | if (why) then 431 | print(why) 432 | else 433 | print(("Could not find compiler to demangle symbol %q, skipping..."):format(symbol)) 434 | end 435 | end 436 | end 437 | 438 | print("\nBinding data generated!") 439 | print(("Bound %d symbols in %d classes.\n"):format(symbol_count, class_count)) 440 | 441 | print("Generating ffipp binding code...") 442 | local buffer = {} 443 | 444 | -- assemblies directive 445 | table.insert(buffer, ("assemblies {\n%s\n}"):format(table.concat(config._assemblies, "\n"))) 446 | 447 | -- test symbols directive 448 | local tests = {} 449 | for key, value in pairs(test_symbols) do 450 | table.insert(tests, key .. " " .. value .. ";") 451 | end 452 | table.insert(buffer, ("test {\n%s\n}"):format(table.concat(tests, "\n\t"))) 453 | 454 | -- classes 455 | for name, class in pairs(classes) do 456 | local body_buffer = {} 457 | 458 | if (class.has_virtuals) then 459 | table.insert(body_buffer, "has_virtuals;") 460 | end 461 | 462 | table.insert(body_buffer, "data {\n\t// fill this in\n}\n") 463 | 464 | local method_buffer = {} 465 | for key, method in ipairs(class.methods) do 466 | if (method.type == "constructor") then 467 | table.insert(method_buffer, ("!(%s) {"):format(table.concat(method.arguments, ", "))) 468 | elseif (method.type == "destructor") then 469 | table.insert(method_buffer, ("~(%s) {"):format(table.concat(method.arguments, ", "))) 470 | elseif (method.type == "member_function") then 471 | table.insert(method_buffer, ("%s %s(%s) {"):format(method.returns, method.name, table.concat(method.arguments, ", "))) 472 | elseif (method.type == "static_member_function") then 473 | table.insert(method_buffer, ("static %s %s(%s) {"):format(method.returns, method.name, table.concat(method.arguments, ", "))) 474 | end 475 | 476 | for i, symbol in ipairs(method.symbols) do 477 | method.symbols[i] = symbol .. ";" 478 | end 479 | 480 | table.insert(method_buffer, table.concat(method.symbols, "\n") .. "\n}") 481 | end 482 | 483 | table.insert(body_buffer, ("methods {\n%s\n}"):format(table.concat(method_buffer, "\n"))) 484 | 485 | table.insert(buffer, ("class %s {\n%s\n}"):format(name, table.concat(body_buffer, "\n"))) 486 | end 487 | 488 | local output = table.concat(buffer, "\n") 489 | 490 | print("\nWriting to file...") 491 | local handle, err = io.open(config.output, "wb") 492 | 493 | if (not handle) then 494 | print("IO error: " .. err) 495 | return 496 | end 497 | 498 | handle:write(output) 499 | handle:close() 500 | 501 | print("\nDone!") 502 | print(("Output: %s"):format(config.output)) 503 | end 504 | 505 | main() --------------------------------------------------------------------------------