integral
is a C++ library for binding C++ code with Lua (Lua 5.1, Lua 5.2, Lua 5.3 or LuaJIT).
- Features
- Error safety
- Requirements
- Tested environments
- Build
- Usage
- Create Lua state
- Use existing Lua state
- Get and set value
- Register function
- Register function with default arguments
- Register class
- Get object
- Register inheritance
- Register table
- Use polymorphism
- Call function in Lua state
- Register lua function argument
- Table conversion
- Register function with ignored argument
- Optional
- Register synthetic inheritance
- std::reference_wrapper and std::shared_ptr automatic inheritance
- Automatic conversion
- Automatic inheritance
- integral reserved names in Lua
- Source
- Author
- License
- no macros;
- no dependencies; and
- thread safety (as per Lua state):
integral
binds to different Lua states independently.
integral
will not crash the Lua state;- stack unwinding is done before the Lua error handling gets in action;
- thrown exceptions in exported functions are converted to Lua errors;
- wrong parameter types in exported functions turn into Lua errors;
- wrong number of parameters in exported functions turn into Lua errors;
- clear error messages;
- it is not possible (it will cause compilation error) to push to Lua:
- pointers
T *
(exceptconst char *
which is considered a string); - non-const references
T &
(const T &
is valid and it is pushed by value); - functions returning pointers (except
const char *
) and non-const references
- pointers
- invalid default arguments definition causes compilation error.
- C++17 compiler; and
- Lua 5.1 or later.
- gcc version 8.3.0 (Ubuntu 8.3.0-6ubuntu1) on Ubuntu Linux; and
- Apple LLVM version 10.0.0 (clang-1000.11.45.5) on MacOS.
Grab all the source files (*.hpp and *.cpp) in src
directory and build (there is no preprocessor configuration for the library).
Alternatively, build and install the library with:
$ make
$ make install
If you want to build the library with LuaJIT use:
$ make USE_LUAJIT=YES
$ make USE_LUAJIT=YES install
Include the library header integral/integral.hpp
(namespace integral
) and link to libintegral.a
or libintegral.so
if the library was built separately.
Check samples/abstraction directory for examples.
#include <integral/integral.hpp>
int main(int argc, char * argv[]) {
integral::State luaState;
luaState.openLibs();
luaState.doFile("path/to/script.lua"); // executes lua script
luaState.doString("print('hello!')"); // prints "hello!"
return 0;
}
See example.
lua_State *luaState = luaL_newstate();
// luaState ownership is NOT transfered to luaStateView
integral::StateView luaStateView(luaState);
luaStateView.openLibs();
luaStateView.doString("print('hello!')"); // prints "hello!"
lua_close(luaState);
See example.
luaState.doString("a = 42");
int a = luaState["a"]; // "a" is set to 42
luaState["b"] = "forty two";
luaState.doString("print(b)"); // prints "forty two"
luaState.doString("t = {'x', {pi = 3.14}}");
std::cout << luaState["t"][2]["pi"].get<double>() << '\n'; // prints "3.14"
luaState["t"]["key"] = "value";
luaState.doString("print(t.key)"); // prints "value"
See example.
double getSum(double x, double y) {
return x + y;
}
double luaGetSum(lua_State *luaState) {
integral::pushCopy(luaState, integral::get<double>(luaState, 1) + integral::get<double>(luaState, 2));
return 1;
}
// ...
luaState["getSum"].setFunction(getSum);
luaState.doString("print(getSum(1, -.2))"); // prints "0.8"
luaState["luaGetSum"].setLuaFunction(luaGetSum);
luaState.doString("print(luaGetSum(1, -.2))"); // prints "0.8"
// lambdas can be bound likewise
luaState["printHello"].setFunction([]{
std::puts("hello!");
});
luaState.doString("printHello()"); // prints "hello!"
See example.
luaState["printArguments"].setFunction([](const std::string &string, int integer) {
std::cout << string << ", " << integer << '\n';
}, integral::DefaultArgument<std::string, 1>("default string"), integral::DefaultArgument<int, 2>(-1));
luaState.doString("printArguments(\"defined string\")\n" // prints "defined string, -1"
"printArguments(nil, 42)\n" // prints "default string, 42"
"printArguments()"); // prints "default string, -1"
See example.
class Object {
public:
const std::string greeting_;
std::string name_;
Object(const std::string greeting, const std::string &name) : greeting_(greeting), name_(name) {}
// const reference values are pushed by value (copied) to the Lua state
const std::string & getGreeting() const {
return greeting_;
}
std::string getHello() const {
return greeting_ + ' ' + name_ + '!';
}
};
// ...
luaState["Object"] = integral::ClassMetatable<Object>()
// invalid constructor register causes compilation error
// .setConstructor<Object()>("invalid")
.setConstructor<Object(const std::string &, const std::string &)>("new")
.setFunction("getGreeting", &Object::getGreeting)
.setGetter("getName", &Object::name_)
.setSetter("setName", &Object::name_)
.setFunction("getHello", &Object::getHello)
.setFunction("getBye", [](const Object &object) {
return std::string("Bye ") + object.name_ + '!';
})
.setLuaFunction("appendName", [](lua_State *lambdaLuaState) {
// objects (except std::vector, std::array, std::unordered_map, std::tuple and std::string) are gotten by reference
integral::get<Object>(lambdaLuaState, 1).name_ += integral::get<const char *>(lambdaLuaState, 2);
return 1;
});
See example.
Objects (except std::vector, std::array, std::unordered_map, std::tuple and std::string) are gotten by reference.
luaState.doString("object = Object.new('foo')\n"
"print(object:getName())"); // prints "foo"
// objects (except std::vector, std::array, std::unordered_map, std::tuple and std::string) are gotten by reference
luaState["object"].get<Object>().name_ = "foobar";
luaState.doString("print(object:getName())"); // prints "foobar"
See example.
class BaseOfBase1 {
public:
void baseOfBase1Method() const {
std::puts("baseOfBase1Method");
}
};
class Base1 : public BaseOfBase1 {
public:
void base1Method() const {
std::puts("base1Method");
}
};
class Base2 {
public:
void base2Method() const {
std::puts("base2Method");
}
};
class Derived : public Base1, public Base2 {
public:
void derivedMethod() const {
std::puts("derivedMethod");
}
};
// ...
integral::State luaState;
luaState["BaseOfBase1"] = integral::ClassMetatable<BaseOfBase1>()
.setConstructor<BaseOfBase1()>("new")
.setFunction("baseOfBase1Method", &BaseOfBase1::baseOfBase1Method);
luaState["Base1"] = integral::ClassMetatable<Base1>()
.setConstructor<Base1()>("new")
.setFunction("base1Method", &Base1::base1Method)
.setBaseClass<BaseOfBase1>();
luaState["Base2"] = integral::ClassMetatable<Base2>()
.setConstructor<Base2()>("new")
.setFunction("base2Method", &Base2::base2Method);
luaState["Derived"] = integral::ClassMetatable<Derived>()
.setConstructor<Derived()>("new")
.setFunction("derivedMethod", &Derived::derivedMethod)
.setBaseClass<Base1>()
.setBaseClass<Base2>();
luaState.doString("derived = Derived.new()\n"
"derived:base1Method()\n" // prints "base1Method"
"derived:base2Method()\n" // prints "base2Method"
"derived:baseOfBase1Method()\n" // prints "baseOfBase1Method"
"derived:derivedMethod()"); // prints "derivedMethod"
See example.
luaState["group"] = integral::Table()
.set("constant", integral::Table()
.set("pi", 3.14))
.setFunction("printHello", []{
std::puts("Hello!");
})
.set("Object", integral::ClassMetatable<Object>()
.setConstructor<Object(const std::string &)>("new")
.setFunction("getHello", &Object::getHello));
luaState.doString("print(group.constant.pi)\n" // prints "3.14"
"group.printHello()\n" // prints "Hello!"
"print(group.Object.new('object'):getHello())"); // prints "Hello object!"
See example.
Objects are automatically converted to base classes types regardless of inheritance definition with integral
.
class Base {};
class Derived : public Base {};
void callBase(const Base &) {
std::puts("Base");
}
// ...
luaState["Derived"] = integral::ClassMetatable<Derived>().setConstructor<Derived()>("new");
luaState["callBase"].setFunction(callBase);
luaState.doString("derived = Derived.new()\n"
"callBase(derived)"); // prints "Base"
See example.
luaState.doString("function getSum(x, y) return x + y end");
int x = luaState["getSum"].call<int>(2, 3); // 'x' is set to 5
See example.
luaState["getResult"].setFunction([](int x, int y, const integral::LuaFunctionArgument &function) {
return function.call<int>(x, y);
});
luaState.doString("print(getResult(-1, 1, math.min))"); // prints "-1"
See example.
Lua tables are automatically converted to/from std::vector, std::array, std::unordered_map and std::tuple.
// std::vector
luaState["intVector"] = std::vector<int>{1, 2, 3};
luaState.doString("print(intVector[1] .. ' ' .. intVector[2] .. ' ' .. intVector[3])"); // prints "1 2 3"
// std::array
luaState.doString("arrayOfVectors = {{'one', 'two'}, {'three'}}");
std::array<std::vector<std::string>, 2> arrayOfVectors = luaState["arrayOfVectors"];
std::cout << arrayOfVectors.at(0).at(0) << ' ' << arrayOfVectors.at(0).at(1) << ' ' << arrayOfVectors.at(1).at(0) << '\n'; // prints "one two three"
// std::unordered_map and std::tuple
luaState["mapOfTuples"] = std::unordered_map<std::string, std::tuple<int, double>>{{"one", {-1, -1.1}}, {"two", {1, 4.2}}};
luaState.doString("print(mapOfTuples.one[1] .. ' ' .. mapOfTuples.one[2] .. ' ' .. mapOfTuples.two[1] .. ' ' .. mapOfTuples.two[2])"); // prints "-1 -1.1 1 4.2"
See example
// std::vector<double> is automatically converted to a lua table. Vector is a regular class for integral and is not converted to a lua table
class Vector : public std::vector<double> {};
// ...
luaState["Vector"] = integral::ClassMetatable<Vector>()
.setConstructor<Vector()>("new")
// because of some legacy lua implementation details, __len receives two arguments, the second argument can be safely ignored
.setFunction("__len", [](const Vector &vector, integral::LuaIgnoredArgument) -> std::size_t {
return vector.size();
})
.setFunction("pushBack", static_cast<void(Vector::*)(const double &)>(&Vector::push_back));
luaState.doString("v = Vector.new()\n"
"print(#v)\n" // prints "0'
"v:pushBack(42)\n"
"print(#v)\n" // prints "1"
"v:pushBack(42)\n"
"print(#v)"); // prints "2"
See example.
std::optional is automatically converted to/from nil/T.
luaState["g"].setFunction([](const std::optional<std::string> &optional) {
if (optional.has_value() == false) {
std::cout << "c++: hi!" << std::endl;
} else {
std::cout << "c++: hi " << optional.value() << '!' << std::endl;
}
});
luaState.doString("g()"); // prints "c++: hi!"
luaState.doString("g('world')"); // prints "c++: hi world!"
std::optional<std::string> match;
match = luaState["string"]["match"].call<std::optional<std::string>>("aei123bcd", "123");
std::cout << match.value() << std::endl; // prints "123"
match = luaState["string"]["match"].call<std::optional<std::string>>("aei123bcd", "xyz");
std::cout << match.has_value() << std::endl; // prints "0"
See example
Synthetic inheritance can be viewed as a transformation from composition in c++ to inheritance in lua.
class BaseOfSyntheticBase {
public:
void baseOfSyntheticBaseMethod() const {
std::cout << "baseOfSyntheticBaseMethod" << std::endl;
}
};
class SyntheticBase : public BaseOfSyntheticBase {
public:
void syntheticBaseMethod() const {
std::cout << "syntheticBaseMethod" << std::endl;
}
};
class SyntheticallyDerived {
public:
SyntheticBase syntheticBase_;
void syntheticallyDerivedMethod() const {
std::cout << "syntheticallyDerivedMethod" << std::endl;
}
SyntheticBase * getSyntheticBasePointer() {
return &syntheticBase_;
}
};
//...
integral::State luaState;
luaState["BaseOfSyntheticBase"] = integral::ClassMetatable<BaseOfSyntheticBase>()
.setFunction("baseOfSyntheticBaseMethod", &BaseOfSyntheticBase::baseOfSyntheticBaseMethod);
luaState["SyntheticBase"] = integral::ClassMetatable<SyntheticBase>()
.setFunction("syntheticBaseMethod", &SyntheticBase::syntheticBaseMethod)
.setBaseClass<BaseOfSyntheticBase>();
luaState["SyntheticallyDerived"] = integral::ClassMetatable<SyntheticallyDerived>()
.setConstructor<SyntheticallyDerived()>("new")
.setFunction("syntheticallyDerivedMethod", &SyntheticallyDerived::syntheticallyDerivedMethod)
.setBaseClass([](SyntheticallyDerived *syntheticallyDerived) -> SyntheticBase * {
return &syntheticallyDerived->syntheticBase_;
});
luaState.doString("syntheticallyDerived = SyntheticallyDerived.new()\n"
"syntheticallyDerived:baseOfSyntheticBaseMethod()\n" // prints "baseOfSyntheticBaseMethod"
"syntheticallyDerived:syntheticBaseMethod()\n" // prints "syntheticBaseMethod"
"syntheticallyDerived:syntheticallyDerivedMethod()"); // prints "syntheticallyDerivedMethod"
See example
std::reference_wrapper<T>
and std::shared_ptr<T>
are registered with automatic synthetic inheritance.
class Object {
public:
void printMessage() const {
std::cout << "Object " << this << " message!" << std::endl;
}
};
//...
luaState["Object"] = integral::ClassMetatable<Object>()
.setFunction("printMessage", &Object::printMessage);
// std::reference_wrapper<T> has automatic synthetic inheritance do T as if it was defined as:
// luaState.defineInheritance([](std::reference_wrapper<T> *referenceWrapperPointer) -> T * {
// return &referenceWrapperPointer->get();
// });
Object object;
object.printMessage(); //prints 'Object 0x7ffee8b68560 message!'
luaState["objectReference"] = std::ref(object);
luaState.doString("objectReference:printMessage()"); //prints the same previous address 'Object 0x7ffee8b68560 message!'
// std::shared_ptr<T> has automatic synthetic inheritance do T as if it was defined as:
// luaState.defineInheritance([](std::shared_ptr<T> *sharedPtrPointer) -> T * {
// return sharedPtrPointer->get();
// });
std::shared_ptr<Object> objectSharedPtr = std::make_shared<Object>();
objectSharedPtr->printMessage(); //prints 'Object 0x7fce594077a8 message!'
luaState["objectSharedPtr"] = objectSharedPtr;
luaState.doString("objectSharedPtr:printMessage()"); //prints the same previous address 'Object 0x7fce594077a8 message!'
luaState["objectSharedPtr"].get<Object>().printMessage(); //prints the same previous address 'Object 0x7fce594077a8 message!'
See example
integral
does the following conversions:
C++ | Lua |
---|---|
integral types (std::is_integral ) |
number [integer subtype in Lua version >= 5.3] |
floating point types (std::is_floating_point ) |
number [float subtype in Lua version >= 5.3] |
bool |
boolean |
std::string , const char * |
string |
std::vector , std::array , std::unordered_map , std::tuple |
table |
std::optional |
nil or converted value |
from: integral::LuaFunctionWrapper , integral::FunctionWrapper |
to: function |
to: integral::LuaFunctionArgument |
from: function |
other class types | userdata |
std::reference_wrapper<T>
and std::shared_ptr<T>
have automatic synthetic inheritance to T
(those types' lua objects inherit T
's lua methods and lua base classes).
integral
uses the following names in Lua registry:
integral_LuaFunctionWrapperMetatableName
;integral_TypeIndexMetatableName
;integral_TypeManagerRegistryKey
; andintegral_InheritanceIndexMetamethodKey
.
The library also uses the following field names in its generated class metatables:
__index
;__gc
;integral_TypeFunctionsKey
;integral_TypeIndexKey
;integral_InheritanceKey
;integral_AutomaticInheritanceKey
;integral_UserDataWrapperBaseTableKey
;integral_UnderlyingTypeFunctionKey
; andintegral_InheritanceSearchTagKey
.
integral
's Git repository is available on GitHub, which can be browsed at:
http://github.com/aphenriques/integral
and cloned with:
git clone git://github.com/aphenriques/integral.git
integral
was made by André Pereira Henriques [aphenriques (at) outlook (dot) com].
Copyright (C) 2013, 2014, 2015, 2016, 2017, 2019 André Pereira Henriques.
integral is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
integral is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
See file COPYING
included with this distribution or check http://www.gnu.org/licenses/ for license information.