Diluculum::LuaState class, which encapsulates a lua_State*.
DILUCULUM_WRAP_FUNCTION().
DILUCULUM_BEGIN_CLASS() and DILUCULUM_REGISTER_OBJECT().
The next sections discuss each of these three items in more detail. One more note: Diluculum has unit tests for all its features, at the Tests directory in the source distribution. These tests also serve as good examples on how to use the library.
Diluculum::LuaState encapsulates a lua_State*, that is a Lua interpreter. Lua is frequently used as a configuration language, and for this purpose, a Diluculum::LuaState works pretty well. Suppose we have a Lua file named config.lua which will be used as the configuration file for some application:
-- A configuration file
FavoriteColor = "blue"
FavoritePiApproximation = 22/7
UserInfo = {
Name = "Fulano de Tal",
Age = 33,
LikesLargeIcons = true,
}
WindowSize = { 456, 234 }
function EmphasizeFunc(s)
return "<EMPH>"..s.."</EMPH>"
end
In order to access the information in this file from C++, the first step is to create a Diluculum::LuaState, and execute the file, as shown below.
#include <Diluculum/LuaState.hpp> // ... Diluculum::LuaState ls; ls.doFile ("config.lua");
Then, you are ready to used the subscript operator of Diluculum::LuaState to access the variables living in it.
const std::string favColor = ls["FavoriteColor"].value().asString(); const double favPiApprox = ls["FavoritePiApproximation"].value().asNumber(); const std::string userName = ls["UserInfo"]["Name"].value().asString(); const int userAge = static_cast<int>(ls["UserInfo"]["Age"].value().asNumber()); const bool userLikesLargeIcons = ls["UserInfo"]["LikesLargeIcons"].value().asBoolean(); const int winWidth = static_cast<int>(ls["WindowSize"][1].value().asNumber()); const int winHeight = static_cast<int>(ls["WindowSize"][2].value().asNumber());
These calls to value(), asNumber(), asString() and so on are a little cumbersome, but not that bad. Anyway, if you are just testing for equality (or inequality), you can omit them:
if (ls["UserInfo"]["LikesLargeIcons"] == true) { // ... } if (ls["FavoriteColor"] != "blue") { std::cout << "You have bad taste for colors!\n"; }
You may also have noticed that a function was defined in config.lua. It can be called from C++ with relative ease. The only thing to notice is that Lua functions can return an arbitrary number of values, so the return in C++ will be a Diluculum::LuaValueList. The code snippet below shows how to call that function.
Diluculum::LuaValueList ret = ls["EmphasizeFunc"]("String to be emphasized"); std::cout << ret[0].asString() << '\n';
Notice that no call to value() is necessary here, because function calls will aready return values (Diluculum::LuaValues) instead of variables (Diluculum::LuaVariables).
The subscript operator also provides write access to the Lua state, so you can change or add new variables. Perhaps this doesn't make much sense if you are using Lua just as a simple configuration language, as shown up to this point, but the feature is useful nevertheless. The example below shows how to do this.
ls["newNumber"] = -123.456; ls["newString"] = "Ahhhh!"; ls["FavoritePiApproximation"] = 3.14159265;
lua_CFunction (the functions used with the Lua API), you can just assign them to a Diluculum::LuaState, as done in the previous section, and as shown below.
int aLuaCFunction (lua_State* ls) { // ... } // ... // Create a Lua state and register the function there Diluculum::LuaState ls; ls["Func"] = aLuaCFunction; // Call the function from Lua ls.doString ("Func (1, 2, 3, 'four')");
It turns out that writing a lua_CFunction involves manipulating a raw lua_State*, which may not be what you want. So, Diluculum can automatically create a lua_CFunction from a function that takes and returns a Diluculum::LuaValueList. Then, this function can be registered in a Diluculum::LuaState as above. The code snippet below demonstrates this.
#include <Diluculum/LuaWrappers.hpp> // [...] // In Lua, this will take one number parameter and return three numbers, which // are equal the parameter, two times the parameter, and three times the // parameter. Not very useful, uh? Diluculum::LuaValueList MyFunction (const Diluculum::LuaValueList& params) { if (params.size() != 1 || params[0].type() != LUA_TNUMBER) throw Diluculum::LuaError ("Bad parameters!"); Diluculum::LuaValueList ret; ret.push_back (params[0].asNumber()); ret.push_back (params[0].asNumber() * 2); ret.push_back (params[0].asNumber() * 3); return ret; } // Create a 'lua_CFunction' DILUCULUM_WRAP_FUNCTION (MyFunction); // ... // Create a Lua state and register the function. Diluculum::LuaState ls; ls["MyFunction"] = DILUCULUM_WRAPPER_FUNCTION (MyFunction); // Now, 'MyFunction' can be called from Lua ls.doString ("a, b, c = MyFunction (4.5)");
Perhaps the only important thingh to notice in the example above is that throwing a Diluculum::LuaError is the proper way to report an error from the wrapped function. For those that know the Lua C API, this exception will be handled and "translated" to a call to lua_error().
And that's it. Not exactly a proper and "generic" wrapper like toLua, toLua++ or Luabind, but may be useful for someone (it is for me!)
Diluculum cannot handle any class. Just as it expected functions with a specific signature (see the previous section), classes must follow some rules in order to be usable with Diluculum. First, it must have a constructor that takes a Diluculum::LuaValueList as parameter. Second, all methods that will be exported must take a Diluculum::LuaValueList as parameter and also return a Diluculum::LuaValueList. The class below is an example of a "valid" class.
// A class that stores one value. Duh. class ValueBox { public: // The constructor taking a 'Diluculum::LuaValueList' parameter. ValueBox (const Diluculum::LuaValueList& params) { if (params.size() > 0) value_ = params[0]; } // Stores the value passed as parameter in the box, and returns the value // previously stored there. Diluculum::LuaValueList swap (const Diluculum::LuaValueList& params) { if (params.size() != 1) throw Diluculum::LuaError ("Exactly one parameter was expected."); Diluculum::LuaValueList ret; ret.push_back (value_); value_ = params[0]; return ret; } private: Diluculum::LuaValue value_; };
As was the case with free functions, throwing a Diluculum::LuaError is the way to report errors from methods. The class is exported to Lua by issuing a sequence of macro calls that will create some data structures representing the class. (For the Lua-inclined readers: more specifically, it will create a global Diluculum::LuaValueMap representing the class; it will be used as the metatable for the objects in Lua.) These macro calls are shown below.
DILUCULUM_BEGIN_CLASS (ValueBox); DILUCULUM_CLASS_METHOD (ValueBox, swap); DILUCULUM_END_CLASS (ValueBox);
I'm showing just a single call of DILUCULUM_CLASS_METHOD() here because the class being exported has just one method. It is OK to add as many methods as desired. Now we are ready to register this class in a Lua state, so that it can actually be used. This requires one more macro call. After this final macro call, objects of this class can be instantiated from Lua. The code snippet below shows how to do this.
Diluculum::LuaState ls; DILUCULUM_REGISTER_CLASS (ls["ValueBox"], ValueBox); ls.doString ("box = ValueBox.new(3)"); ls.doString ("print (box:swap('foo'))"); // prints '3' ls.doString ("print (box:swap(789.987))"); // prints 'foo'
Diluculum::LuaState ls; DILUCULUM_REGISTER_CLASS (ls["ValueBox"], ValueBox); ls.doString ("box = ValueBox.new()"); // ...use 'box'... ls.doString ("box:delete()");
And this is a good moment to discuss a very important case in which calling the destructor explicitly in Lua is a good idea. The problem is that Lua doesn't know how much memory an object occupies. For every object instantiated using Diluculum's facilities, Lua is only aware of a little of memory (a pointer and some additional flags). If the object itself allocates more memory (as any decent object is supposed to do), Lua will not know this, and the garbage collector will not detect the "right" amount of memory allocated from within Lua. So, if you instantiate a big object, you better free it manually from Lua as soon as you don't need it anymore. Likewise, if you allocate many objects (in a loop, perhaps), you better delete each of them manually.
Diluculum::LuaState ls; Diluculum::LuaValueList params; ValueBox vb (params); DILUCULUM_REGISTER_OBJECT (ls["box"], ValueBox, vb); ls.doString ("box:swap (123)"); std::cout << vb.swap("abc") << '\n'; // prints '123'.
Notice that when box is garbage collected in Lua, the C++ object will not be destroyed. In this case, cleaning the object is responsibility of the programmer on the C++ side.
require "MyModule"
Actually, Diluculum doesn't do much in this regard, and using its facilities is not so much simpler than using the Lua API itself. Anyway, this is a user's guide, so it is supposed to explain how this thing works. So, here we go. Basically, all we can do is to create a module and add functions and classes to it. Here is an example, that shows how to create a module containing a single function and a single class that were defined previously in this guide.
DILUCULUM_BEGIN_MODULE (MyFineModule); DILUCULUM_MODULE_ADD_CLASS (ValueBox, "ValueBox"); DILUCULUM_MODULE_ADD_FUNCTION (DILUCULUM_WRAPPER_FUNCTION(MyFunction), "MyFunction"); DILUCULUM_END_MODULE();
So, if this is compiled to a properly named dynamic library (see Lua's Reference Manual to know what constitutes a proper module name), the Lua programmer can say something like:
require "MyFineModule" local v = MyFineModule.ValueBox.new(4321) v.swap ("Weeeeee!") local x, y, z = MyFineModule.MyFunction (4.567)
1.4.6