diff --git a/.gitignore b/.gitignore index 34fb7c2..5868051 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ /lua /*.rock /*.sublime-workspace + +build profile.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0f486f5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/cpp/cmake_git_submodule/lua"] + path = src/cpp/cmake_git_submodule/lua + url = https://github.com/lua/lua.git diff --git a/src/cpp/README.md b/src/cpp/README.md new file mode 100644 index 0000000..0eeddc0 --- /dev/null +++ b/src/cpp/README.md @@ -0,0 +1,30 @@ +# Lua with C++ + +This folder contains a set of examples from my article [C++ and Lua](https://martin-fieber.de/blog/cpp-and-lua) as part of the [Lua series](https://martin-fieber.de/series/lua). + +## Examples + +| Folder | Contents | +| -------------------------- | --------------------------------------------------- | +| `cmake_find_package/` | Include Lua in a project via CMake `find_package` | +| `cmake_external_project/` | Include Lua in a project via CMake external project | +| `cmake_git_submodule/` | Include Lua in a project via Git submodule | +| `example-read-config/` | All about reading data from Lua | +| `example-functions/` | Calling Lua functions from C++ | +| `example-cpp-to-lua/` | Calling a C++ from Lua | +| `example-custom-module` | Define a custom Lua module from C++ | +| `example-override-builtin` | Override built-in Lua functions | + +## Setup + +Every example is set up the same, using CMake to build. Setting any example folder as current working directory, run the following command to create the CMake build configuration. + +```shell +cmake -B build +``` + +The next command will build the executable into the folder `build`. + +```shell +cmake --build build +``` diff --git a/src/cpp/cmake_external_project/CMakeLists.txt b/src/cpp/cmake_external_project/CMakeLists.txt new file mode 100644 index 0000000..22ec2e4 --- /dev/null +++ b/src/cpp/cmake_external_project/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.18) + +# Setup a CMake project for C++ +project( + LuaWithCMakeExternalProject + VERSION 1.0 + LANGUAGES CXX) + +# Add an executable based on one source file +add_executable(ProgramName main.cpp) + +# Include the fetch content module +include(FetchContent) + +# Fetch Lua from git mirror +FetchContent_Declare( + lua + GIT_REPOSITORY "https://github.com/lua/lua.git" + GIT_TAG v5.5.0 +) + +# Make content available +FetchContent_MakeAvailable(lua) + +# Setup Lua library target with include directories +add_library(Lua::Lua INTERFACE IMPORTED) +target_include_directories( + Lua::Lua + INTERFACE "${lua_SOURCE_DIR}") + +# Link Lua library to the executable +target_link_libraries( + ProgramName + PRIVATE Lua::Lua) diff --git a/src/cpp/cmake_external_project/README.md b/src/cpp/cmake_external_project/README.md new file mode 100644 index 0000000..fe0ef4f --- /dev/null +++ b/src/cpp/cmake_external_project/README.md @@ -0,0 +1,17 @@ +# Lua, CMake, FetchContent + +Using Lua with CMake and the `FetchContent` module. Lua will be fetched from source. + +## Setup + +With the folder containing this file as current working directory, run the following command to create the CMake build configuration. + +```shell +cmake -B build +``` + +The next command will build the executable into the folder `build`. + +```shell +cmake --build build +``` diff --git a/src/cpp/cmake_external_project/main.cpp b/src/cpp/cmake_external_project/main.cpp new file mode 100644 index 0000000..7babd29 --- /dev/null +++ b/src/cpp/cmake_external_project/main.cpp @@ -0,0 +1,7 @@ +#include + +int main() { + // More to come ... + + return 0; +} diff --git a/src/cpp/cmake_find_package/CMakeLists.txt b/src/cpp/cmake_find_package/CMakeLists.txt new file mode 100644 index 0000000..fc482be --- /dev/null +++ b/src/cpp/cmake_find_package/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.18) + +# Setup a CMake project for C++ +project( + LuaWithCMakeFindPackage + VERSION 1.0 + LANGUAGES CXX) + +# Add an executable based on one source file +add_executable(ProgramName main.cpp) + +# Try to find the Lua package on the system +find_package(Lua) + +# If Lua was found and target is not set up +if(Lua_FOUND AND NOT TARGET Lua::Lua) + # Setup Lua library target + add_library(Lua::Lua INTERFACE IMPORTED) + + # Set include directories + target_include_directories( + Lua::Lua + INTERFACE "${LUA_INCLUDE_DIR}") +endif() + +# Link Lua library to the executable +target_link_libraries( + ProgramName + PRIVATE Lua::Lua) diff --git a/src/cpp/cmake_find_package/README.md b/src/cpp/cmake_find_package/README.md new file mode 100644 index 0000000..e46079e --- /dev/null +++ b/src/cpp/cmake_find_package/README.md @@ -0,0 +1,17 @@ +# Lua, CMake, find_package + +Using Lua with CMake and find_package. This requires Lua to already be installed/available on the system. + +## Setup + +With the folder containing this file as current working directory, run the following command to create the CMake build configuration. + +```shell +cmake -B build +``` + +The next command will build the executable into the folder `build`. + +```shell +cmake --build build +``` diff --git a/src/cpp/cmake_find_package/main.cpp b/src/cpp/cmake_find_package/main.cpp new file mode 100644 index 0000000..7babd29 --- /dev/null +++ b/src/cpp/cmake_find_package/main.cpp @@ -0,0 +1,7 @@ +#include + +int main() { + // More to come ... + + return 0; +} diff --git a/src/cpp/cmake_git_submodule/CMakeLists.txt b/src/cpp/cmake_git_submodule/CMakeLists.txt new file mode 100644 index 0000000..15e93bf --- /dev/null +++ b/src/cpp/cmake_git_submodule/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.18) + +# Setup a CMake project for C++ +project( + LuaWithGitSubmodules + VERSION 1.0 + LANGUAGES CXX) + +# Add an executable based on one source file +add_executable(ProgramName main.cpp) + +# Setup Lua library target with include directories +add_library(Lua::Lua INTERFACE IMPORTED) +target_include_directories( + Lua::Lua + INTERFACE "lua") + +# Link Lua library to the executable +target_link_libraries( + ProgramName + PRIVATE Lua::Lua) diff --git a/src/cpp/cmake_git_submodule/README.md b/src/cpp/cmake_git_submodule/README.md new file mode 100644 index 0000000..a9823bc --- /dev/null +++ b/src/cpp/cmake_git_submodule/README.md @@ -0,0 +1,17 @@ +# Lua, CMake, Git Submodules + +Using Lua with CMake and the Git submodules. + +## Setup + +With the folder containing this file as current working directory, run the following command to create the CMake build configuration. + +```shell +cmake -B build +``` + +The next command will build the executable into the folder `build`. + +```shell +cmake --build build +``` diff --git a/src/cpp/cmake_git_submodule/lua b/src/cpp/cmake_git_submodule/lua new file mode 160000 index 0000000..c6b4848 --- /dev/null +++ b/src/cpp/cmake_git_submodule/lua @@ -0,0 +1 @@ +Subproject commit c6b484823806e08e1756b1a6066a3ace6f080fae diff --git a/src/cpp/cmake_git_submodule/main.cpp b/src/cpp/cmake_git_submodule/main.cpp new file mode 100644 index 0000000..7babd29 --- /dev/null +++ b/src/cpp/cmake_git_submodule/main.cpp @@ -0,0 +1,7 @@ +#include + +int main() { + // More to come ... + + return 0; +} diff --git a/src/cpp/example-cpp-to-lua/CMakeLists.txt b/src/cpp/example-cpp-to-lua/CMakeLists.txt new file mode 100644 index 0000000..be4cac4 --- /dev/null +++ b/src/cpp/example-cpp-to-lua/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.18) + +# Setup a CMake project for C++ +project( + LuaExampleCppToLua + VERSION 1.0 + LANGUAGES CXX) + +# Add an executable based on one source file +add_executable(ProgramName main.cpp) + +# Let's use C++23 +target_compile_features(ProgramName PRIVATE cxx_std_23) + +# Try to find the Lua package on the system +find_package(Lua REQUIRED) + +# If Lua was found and target is not set up +if(Lua_FOUND AND NOT TARGET Lua::Lua) + add_library(Lua::Lua INTERFACE IMPORTED) + set_target_properties( + Lua::Lua + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}" + ) +endif() + +# Link Lua library to the executable +target_link_libraries( + ProgramName + PRIVATE Lua::Lua) diff --git a/src/cpp/example-cpp-to-lua/README.md b/src/cpp/example-cpp-to-lua/README.md new file mode 100644 index 0000000..6996228 --- /dev/null +++ b/src/cpp/example-cpp-to-lua/README.md @@ -0,0 +1,17 @@ +# Functions example + +Different examples on how to create functions in C++ to be called from Lua. + +## Setup + +With the folder containing this file as current working directory, run the following command to create the CMake build configuration. + +```shell +cmake -B build +``` + +The next command will build the executable into the folder `build`. + +```shell +cmake --build build +``` diff --git a/src/cpp/example-cpp-to-lua/main.cpp b/src/cpp/example-cpp-to-lua/main.cpp new file mode 100644 index 0000000..308ebbb --- /dev/null +++ b/src/cpp/example-cpp-to-lua/main.cpp @@ -0,0 +1,46 @@ +#include + +#include + +int compute(lua_State *L) { + // Function arguments are on the stack in reverse order + lua_Number arg_2 = lua_tonumber(L, -1); + lua_Number arg_1 = lua_tonumber(L, -2); + + // First return value pushed to the stack + lua_pushnumber(L, arg_2 + arg_1); + + // A second return value + lua_pushnumber(L, arg_2 * arg_1); + + // Return number of Lua function return values + return 2; +} + +int main() { + + // Open Lua state + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + + // Setup function before running the script + lua_pushcfunction(L, compute); + lua_setglobal(L, "compute"); + + // Load and run file + if (luaL_dofile(L, "./script.lua") == LUA_OK) { + + // This will be printed after the Lua is done + std::print("Done\n"); + + } else { + // On error reading the file, an error message + // will be on top of the stack. + std::print("Error reading configuration file:\n"); + luaL_error(L, "Error: %s\n", lua_tostring(L, -1)); + } + + lua_close(L); + + return 0; +} diff --git a/src/cpp/example-cpp-to-lua/script.lua b/src/cpp/example-cpp-to-lua/script.lua new file mode 100644 index 0000000..930367a --- /dev/null +++ b/src/cpp/example-cpp-to-lua/script.lua @@ -0,0 +1,5 @@ +-- Example script file + +local first, second = compute(2, 10) +print("Result 1 = " .. first) +print("Result 2 = " .. second) diff --git a/src/cpp/example-custom-module/CMakeLists.txt b/src/cpp/example-custom-module/CMakeLists.txt new file mode 100644 index 0000000..7afeddc --- /dev/null +++ b/src/cpp/example-custom-module/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.18) + +# Setup a CMake project for C++ +project( + LuaExampleCppDataToLua + VERSION 1.0 + LANGUAGES CXX) + +# Add an executable based on one source file +add_executable(ProgramName main.cpp) + +# Let's use C++23 +target_compile_features(ProgramName PRIVATE cxx_std_23) + +# Try to find the Lua package on the system +find_package(Lua REQUIRED) + +# If Lua was found and target is not set up +if(Lua_FOUND AND NOT TARGET Lua::Lua) + add_library(Lua::Lua INTERFACE IMPORTED) + set_target_properties( + Lua::Lua + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}" + ) +endif() + +# Link Lua library to the executable +target_link_libraries( + ProgramName + PRIVATE Lua::Lua) diff --git a/src/cpp/example-custom-module/README.md b/src/cpp/example-custom-module/README.md new file mode 100644 index 0000000..7fc8b2e --- /dev/null +++ b/src/cpp/example-custom-module/README.md @@ -0,0 +1,17 @@ +# Userdata + +Example on how to get data from C++ to Lua. + +## Setup + +With the folder containing this file as current working directory, run the following command to create the CMake build configuration. + +```shell +cmake -B build +``` + +The next command will build the executable into the folder `build`. + +```shell +cmake --build build +``` diff --git a/src/cpp/example-custom-module/main.cpp b/src/cpp/example-custom-module/main.cpp new file mode 100644 index 0000000..54f5750 --- /dev/null +++ b/src/cpp/example-custom-module/main.cpp @@ -0,0 +1,120 @@ +#include + +#include + +struct GameData { + const char *name; + int amount{0}; +}; + +// A new function to be used when constructing +// a new GameData table in Lua. +int GameData_new(lua_State *L) { + // New user data from GameData + auto *game_data = + static_cast(lua_newuserdata(L, sizeof(GameData))); + + // Set up data from `new` function call in Lua. + // On stack index -1 is the new user data located. + game_data->amount = lua_tonumber(L, -2); + game_data->name = lua_tostring(L, -3); + + // Set metatable to GameData + luaL_setmetatable(L, "GameData"); + + return 1; +} + +// Meta method to read fields from Lua data +int GameData_index(lua_State *L) { + // GameData is on top of the stack + auto *game_data = static_cast(lua_touserdata(L, 1)); + + // The key is at index 2. We only want to allow string indexing, so we'll use + // checkstring: + const std::string key = luaL_checkstring(L, 2); + + // Access to "name" field + if (key == "name") { + lua_pushstring(L, game_data->name); + return 1; + } + + // Access to "amount" field + if (key == "amount") { + lua_pushinteger(L, game_data->amount); + return 1; + } + + // Throw if field is not defined. + luaL_error(L, "Unknown property access: %s", key.c_str()); + return 1; +} + +// Meta method to write fields from Lua data +int GameData_newindex(lua_State *L) { + auto *game_data = static_cast(lua_touserdata(L, 1)); + const std::string key = luaL_checkstring(L, 2); + + // Write to "name" field + if (key == "name") { + game_data->name = luaL_checkstring(L, 3); + return 1; + } + + // Write to "amount" field + if (key == "amount") { + game_data->amount = luaL_checkinteger(L, 3); + return 1; + } + + luaL_error(L, "Unknown property access: %s", key.c_str()); + return 1; +} + +// Map out our metatable: +const luaL_Reg game_data_meta[] = { + {"__index", GameData_index}, + {"__newindex", GameData_newindex}, + {nullptr, nullptr}, +}; + +// A "registry" array to hold methods +// defined for the GameData module. +const luaL_Reg game_data_lib[] = { + {"new", GameData_new}, + {nullptr, nullptr}, +}; + +// A function to register the GameData module +int open_GameData(lua_State *L) { + luaL_newmetatable(L, "GameData"); + luaL_setfuncs(L, game_data_meta, 0); + + luaL_newlib(L, game_data_lib); + + return 1; +} + +int main() { + // Open Lua state and standard library + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + + // Add GameData module for `require GameName` + luaL_requiref(L, "GameData", open_GameData, + 0 // 0 = false do not create a global variable + ); + + // Load and run file + if (luaL_dofile(L, "./script.lua") == LUA_OK) { + std::print("Done\n"); + } else { + std::print("Error reading configuration file:\n"); + luaL_error(L, "Error: %s\n", lua_tostring(L, -1)); + } + + lua_close(L); + + return 0; +} diff --git a/src/cpp/example-custom-module/script.lua b/src/cpp/example-custom-module/script.lua new file mode 100644 index 0000000..e27ddf6 --- /dev/null +++ b/src/cpp/example-custom-module/script.lua @@ -0,0 +1,10 @@ +-- Example script file +local GameData = require "GameData" + +local data = GameData.new("Dwarves", 7) +print(type(data)) -- "userdata" + +print(data.amount .. " " .. data.name) + +data.name = "Gnomes" +print(data.amount .. " " .. data.name) diff --git a/src/cpp/example-functions/CMakeLists.txt b/src/cpp/example-functions/CMakeLists.txt new file mode 100644 index 0000000..81e1701 --- /dev/null +++ b/src/cpp/example-functions/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.18) + +# Setup a CMake project for C++ +project( + LuaExampleFunctions + VERSION 1.0 + LANGUAGES CXX) + +# Add an executable based on one source file +add_executable(ProgramName main.cpp) + +# Let's use C++23 +target_compile_features(ProgramName PRIVATE cxx_std_23) + +# Try to find the Lua package on the system +find_package(Lua REQUIRED) + +# If Lua was found and target is not set up +if(Lua_FOUND AND NOT TARGET Lua::Lua) + add_library(Lua::Lua INTERFACE IMPORTED) + set_target_properties( + Lua::Lua + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}" + ) +endif() + +# Link Lua library to the executable +target_link_libraries( + ProgramName + PRIVATE Lua::Lua) diff --git a/src/cpp/example-functions/README.md b/src/cpp/example-functions/README.md new file mode 100644 index 0000000..0077687 --- /dev/null +++ b/src/cpp/example-functions/README.md @@ -0,0 +1,17 @@ +# Functions example + +Different examples on how to call Lua functions from C++. + +## Setup + +With the folder containing this file as current working directory, run the following command to create the CMake build configuration. + +```shell +cmake -B build +``` + +The next command will build the executable into the folder `build`. + +```shell +cmake --build build +``` diff --git a/src/cpp/example-functions/main.cpp b/src/cpp/example-functions/main.cpp new file mode 100644 index 0000000..aa07ab0 --- /dev/null +++ b/src/cpp/example-functions/main.cpp @@ -0,0 +1,105 @@ +#include + +#include + +int on_error(lua_State *L) { + // Get error message from top of the stack and remove it. + const char *err = lua_tostring(L, -1); + lua_remove(L, -1); + + // Push the traceback on top of the stack + luaL_traceback(L, L, err, 1); + + return 1; +} + +int main() { + // Open Lua state + lua_State *L = luaL_newstate(); + + // Load and run file + if (luaL_dofile(L, "./script.lua") == LUA_OK) { + + // === Call Lua function === + + // Get the global some_fn function on top of the stack + lua_getglobal(L, "some_fn"); + + // Check if there is a function on top of the stack + if (lua_isfunction(L, -1)) { + // Push both function arguments on the stack + lua_pushnumber(L, 13); + lua_pushnumber(L, 23); + + constexpr int arguments_count = 2; + constexpr int returnvalues_count = 2; + + // Protected call, returns a status int + int status = lua_pcall(L, arguments_count, returnvalues_count, 0); + + // If everything was good: + if (status == LUA_OK) { + // Read the first return value + lua_Number result1 = lua_tonumber(L, -2); + + // Read the second return value + bool result2 = lua_toboolean(L, -1); + + std::print("result1 = {}\n", result1); + std::print("result2 = {}\n", result2); + } + } + + // === Protected call with message handler === + + // Get top of the stack to restore later + const int top_index = lua_gettop(L); + + // Push message handler on top of the stack + lua_pushcfunction(L, on_error); + + // Get message handler index for `lua_pcall` + const int handler_index = lua_gettop(L); + + // Get the global some_fn function on top of the stack + lua_getglobal(L, "some_fn"); + + // Check if there is a function on top of the stack + if (lua_isfunction(L, -1)) { + // Push wrong arguments that will invoke an error + lua_pushnumber(L, 13); + lua_pushliteral(L, "23"); + + constexpr int arguments_count = 2; + constexpr int returnvalues_count = 2; + + // Protected call, returns a status int + int status = + lua_pcall(L, arguments_count, returnvalues_count, handler_index); + + // An error occured! + if (status == LUA_ERRRUN) { + // Get the error message from the top of the stack + const char *err = lua_tostring(L, -1); + + std::print("Error = {}\n", err); + + // After the error is handled, pop it from the stack. + lua_pop(L, 1); + } + } + + // Restore stack when done + lua_settop(L, top_index); + + } else { + // On error reading the file, an error message + // will be on top of the stack. + std::print("Error reading configuration file:\n"); + luaL_error(L, "Error: %s\n", lua_tostring(L, -1)); + } + + lua_close(L); + + return 0; +} diff --git a/src/cpp/example-functions/script.lua b/src/cpp/example-functions/script.lua new file mode 100644 index 0000000..61080b9 --- /dev/null +++ b/src/cpp/example-functions/script.lua @@ -0,0 +1,7 @@ +-- Example script file + +function some_fn(arg1, arg2) + local result = arg1 + arg2 + -- Multiple return values + return result, true +end diff --git a/src/cpp/example-override-builtin/CMakeLists.txt b/src/cpp/example-override-builtin/CMakeLists.txt new file mode 100644 index 0000000..cb56b1d --- /dev/null +++ b/src/cpp/example-override-builtin/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.18) + +# Setup a CMake project for C++ +project( + LuaExampleOverrideBuiltIn + VERSION 1.0 + LANGUAGES CXX) + +# Add an executable based on one source file +add_executable(ProgramName main.cpp) + +# Let's use C++23 +target_compile_features(ProgramName PRIVATE cxx_std_23) + +# Try to find the Lua package on the system +find_package(Lua REQUIRED) + +# If Lua was found and target is not set up +if(Lua_FOUND AND NOT TARGET Lua::Lua) + add_library(Lua::Lua INTERFACE IMPORTED) + set_target_properties( + Lua::Lua + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}" + ) +endif() + +# Link Lua library to the executable +target_link_libraries( + ProgramName + PRIVATE Lua::Lua) diff --git a/src/cpp/example-override-builtin/README.md b/src/cpp/example-override-builtin/README.md new file mode 100644 index 0000000..5213b31 --- /dev/null +++ b/src/cpp/example-override-builtin/README.md @@ -0,0 +1,17 @@ +# Override Built-in Lua Functions + +Example on how to override or extend built-in Lua functions. + +## Setup + +With the folder containing this file as current working directory, run the following command to create the CMake build configuration. + +```shell +cmake -B build +``` + +The next command will build the executable into the folder `build`. + +```shell +cmake --build build +``` diff --git a/src/cpp/example-override-builtin/main.cpp b/src/cpp/example-override-builtin/main.cpp new file mode 100644 index 0000000..d0bdf73 --- /dev/null +++ b/src/cpp/example-override-builtin/main.cpp @@ -0,0 +1,94 @@ +#include + +#include + +// === Setup built-in function override === + +// Hold reference to built-in `type` function +static int type_ref; + +int lua_type(lua_State *L) { + // When `type` is called the function argument + // is on top of the stack at -1. + if (lua_isuserdata(L, -1)) { + // From the passed argument, get the meta field `__name`. + if (luaL_getmetafield(L, -1, "__name") != LUA_TNIL) { + // Now the fields value is on top of the stack at -1. + const std::string name = lua_tostring(L, -1); + if (!name.empty()) { + // Push the name as return argument to the `type` call. + lua_pushlstring(L, name.c_str(), name.size()); + return 1; + } + } + } + + // If it's not user data, get built-in `type` function. + lua_rawgeti(L, LUA_REGISTRYINDEX, type_ref); + + // Call the built-in `type` function with the + // passed argument pushed. + lua_pushvalue(L, -1); + lua_call(L, -1, -1); + + return 1; +} + +void setup_type_override(lua_State *L) { + // Get global function `type` on top of the stack. + if (lua_getglobal(L, "type") != LUA_TNIL) { + // Get a referece to the type function from the registry. + type_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + // Register custom `type` override + lua_register(L, "type", lua_type); + } +} + +// === Setup Example GameData module === + +struct GameData { + int state{0}; +}; + +int GameData_new(lua_State *L) { + auto *game_data = + static_cast(lua_newuserdata(L, sizeof(GameData))); + luaL_setmetatable(L, "GameData"); + return 1; +} + +const luaL_Reg game_data_lib[] = { + {"new", GameData_new}, + {nullptr, nullptr}, +}; + +int open_GameData(lua_State *L) { + luaL_newmetatable(L, "GameData"); + luaL_newlib(L, game_data_lib); + return 1; +} + +// === Main === + +int main() { + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + + // Set up type override + setup_type_override(L); + + // Example custom module + luaL_requiref(L, "GameData", open_GameData, 0); + + if (luaL_dofile(L, "./script.lua") == LUA_OK) { + std::print("Done\n"); + } else { + std::print("Error reading configuration file:\n"); + luaL_error(L, "Error: %s\n", lua_tostring(L, -1)); + } + + lua_close(L); + + return 0; +} diff --git a/src/cpp/example-override-builtin/script.lua b/src/cpp/example-override-builtin/script.lua new file mode 100644 index 0000000..2b44501 --- /dev/null +++ b/src/cpp/example-override-builtin/script.lua @@ -0,0 +1,4 @@ +-- Example script file +local GameData = require "GameData" +local data = GameData.new() +print(type(data)) diff --git a/src/cpp/example-read-config/CMakeLists.txt b/src/cpp/example-read-config/CMakeLists.txt new file mode 100644 index 0000000..ab6e502 --- /dev/null +++ b/src/cpp/example-read-config/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.18) + +# Setup a CMake project for C++ +project( + LuaExampleReadConfig + VERSION 1.0 + LANGUAGES CXX) + +# Add an executable based on one source file +add_executable(ProgramName main.cpp) + +# Let's use C++23 +target_compile_features(ProgramName PRIVATE cxx_std_23) + +# Try to find the Lua package on the system +find_package(Lua REQUIRED) + +# If Lua was found and target is not set up +if(Lua_FOUND AND NOT TARGET Lua::Lua) + add_library(Lua::Lua INTERFACE IMPORTED) + set_target_properties( + Lua::Lua + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}" + ) +endif() + +# Link Lua library to the executable +target_link_libraries( + ProgramName + PRIVATE Lua::Lua) diff --git a/src/cpp/example-read-config/README.md b/src/cpp/example-read-config/README.md new file mode 100644 index 0000000..04096da --- /dev/null +++ b/src/cpp/example-read-config/README.md @@ -0,0 +1,17 @@ +# Read Lua configuration file + +Using Lua as configuration file format in a C++ application. + +## Setup + +With the folder containing this file as current working directory, run the following command to create the CMake build configuration. + +```shell +cmake -B build +``` + +The next command will build the executable into the folder `build`. + +```shell +cmake --build build +``` diff --git a/src/cpp/example-read-config/config.lua b/src/cpp/example-read-config/config.lua new file mode 100644 index 0000000..cd36db5 --- /dev/null +++ b/src/cpp/example-read-config/config.lua @@ -0,0 +1,14 @@ +-- Example config file +name = "My Configuration" + +width = 42 +height = 23 + +isResizable = true + +labels = { + hello = "Hello", + reader = "Reader" +} + +numbers = {7, 13, 23, 42} diff --git a/src/cpp/example-read-config/main.cpp b/src/cpp/example-read-config/main.cpp new file mode 100644 index 0000000..336108f --- /dev/null +++ b/src/cpp/example-read-config/main.cpp @@ -0,0 +1,105 @@ +#include + +#include + +int main() { + // Open Lua state + lua_State *L = luaL_newstate(); + + // Load and run file + if (luaL_dofile(L, "./config.lua") == LUA_OK) { + + // === Number === + + // Get global variable "width" onto the top + // of the stack. + lua_getglobal(L, "width"); + + // Check if the current value on the top of + // the stack (-1) is a number. + if (lua_isnumber(L, -1)) { + // lua_Number == double + lua_Number width = lua_tonumber(L, -1); + + std::print("width = {}\n", width); + } + + // === Integer === + + lua_getglobal(L, "height"); + if (lua_isinteger(L, -1)) { + // lua_Integer == long long + lua_Integer height = lua_tointeger(L, -1); + std::print("height = {}\n", height); + } + + // === Boolean === + + lua_getglobal(L, "isResizable"); + if (lua_isboolean(L, -1)) { + int isResizable = lua_toboolean(L, -1); + std::print("isResizable = {}\n", static_cast(isResizable)); + } + + // === String === + + lua_getglobal(L, "name"); + if (lua_isstring(L, -1)) { + const char *name = lua_tostring(L, -1); + std::print("name = '{}'\n", name); + } + + // === Table === + + // Get a table from global on the stack + lua_getglobal(L, "labels"); + + // Check if a table is on top of the stack + if (lua_istable(L, -1)) { + // Get one field from that table on top of the stack at `-1` + lua_getfield(L, -1, "hello"); + const char *hello = lua_tostring(L, -1); + + // The previous field is now on top of the stack at `-1` + // Get another field from the table at `-2` onto the stack + lua_getfield(L, -2, "reader"); + const char *reader = lua_tostring(L, -1); + + std::print("From table 'labels' = '{}, {}!'\n", hello, reader); + } + + // === Array === + + // Get an array from global on the stack + lua_getglobal(L, "numbers"); + + // Arrays are also tables in Lua + if (lua_istable(L, -1)) { + // Get the first index from the array + // on top of the stack at `-1` + lua_geti(L, -1, 1); + lua_Number first = lua_tonumber(L, -1); + + // The previous index is now on top of the stack at `-1` + // Get the next index from the array at `-2` onto the stack + lua_geti(L, -2, 2); + lua_Number second = lua_tonumber(L, -1); + + // And so on ... + lua_geti(L, -3, 3); + lua_Number third = lua_tonumber(L, -1); + + std::print("Numbers = [{}, {}, {}]'\n", first, second, third); + } + + } else { + // On error reading the file, an error message + // will be on top of the stack. + std::print("Error reading configuration file:\n"); + luaL_error(L, "Error: %s\n", lua_tostring(L, -1)); + } + + lua_close(L); + + return 0; +}