/integral

Library for binding C++ code with Lua

Primary LanguageC++MIT LicenseMIT

integral

integral is a C++ library for binding C++ code with Lua (Lua 5.1, Lua 5.2, Lua 5.3, lua 5.4 and LuaJIT).

Lua logo

Index

Features

  • no macros;
  • no dependencies; and
  • thread safety (as per Lua state): integral binds to different Lua states independently.

Error safety

  • 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 * (except const 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
  • invalid default arguments definition causes compilation error.

Requirements

  • C++17 compiler; and
  • Lua 5.1 or later.

Tested environments

  • gcc version 8.3.0 (Ubuntu 8.3.0-6ubuntu1) on Ubuntu Linux;
  • Apple clang version 11.0.3 (clang-1103.0.32.62) on MacOS; and
  • Microsoft (R) C/C++ Optimizing Compiler Version 19.28.29336 for x86 (Visual Studio Community 2019 Version 16.8.4) on Windows.

Build

Grab the dependecies/exception/include/exception directory and all the source files (*.hpp and *.cpp) in the lib/integral directory and build (there is no preprocessor configuration for the library).

Alternatively, build and install the library with:

$ make
$ make install

To build the library, the test or the samples with LuaJIT, use:

$ make WITH_LUAJIT=y

Lua and LuaJIT compiler settings can be defined with:

$ make LUA_INCLUDE_DIR=/path/to/lua[jit]/include LUA_LIB_DIR=-L/path/to/lua[jit]/lib LUA_LDLIB=-llua[jit]library [WITH_LUAJIT=y]

Usage

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.

Create Lua state

#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.

Use existing Lua state

    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.

Get and set value

    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.

Reference lua variables

    luaState["x"] = 42;
    luaState["y"] = integral::Global()["x"]; // equivalent to "y = x" in lua
    luaState.doString("print(y)"); // prints "42"

    luaState["global"] = integral::Global(); // equivalent to "global = _G" in lua
    luaState.doString("print(global.y)"); // prints "42"

    luaState["t"] = integral::Table().set("x", integral::Global()["global"]["y"]); // equivalent to "t = {x = global.y}" in lua
    luaState.doString("print(t.x)"); // prints "42"

See example.

Register function

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.

Register function with default arguments

    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.

Register class

class Object {
public:
    const std::string greeting_;
    std::string name_;

    Object(const std::string & greeting, std::string_view 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 &, std::string_view)>("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.

Get object

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.

Register inheritance

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.

Register table

    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.

Use polymorphism

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.

Call function in Lua State

    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.

Register lua function argument

    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.

Table conversion

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

Register function with ignored argument

// 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.

Pusher function value

void pushModule(lua_State *luaState) {
    integral::push<integral::Table>(luaState);
    integral::setFunction(
        luaState,
        "printHello",
        [] {
            std::cout << "Hello!" << std::endl;
        }
    );
}

// ...

        luaState["module"] = integral::Pusher(pushModule);
        luaState.doString("module.printHello()"); // prints "Hello!"

See example.

Optional

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

Register synthetic inheritance

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 and std::shared_ptr automatic inheritance

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

Automatic conversion

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, std::string_view, 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

Automatic inheritance

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 reserved names in Lua

integral uses the following names in Lua registry:

  • integral_LuaFunctionWrapperMetatableName;
  • integral_TypeIndexMetatableName;
  • integral_TypeManagerRegistryKey; and
  • integral_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; and
  • integral_InheritanceSearchTagKey.

Source

integral's Git repository is available on GitHub, which can be browsed at:

http://github.com/aphenriques/integral

and cloned with:

git clone --recurse-submodules git://github.com/aphenriques/integral.git

Author

integral was made by André Pereira Henriques [aphenriques (at) outlook (dot) com].

License

MIT License

Copyright (c) 2013-2023 André Pereira Henriques (aphenriques (at) outlook (dot) com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.