Lua-API++  2015-02-12-3
Lua-API++ library
Motivational example

To get a feeling of how the library is used, let's write a simple example program. It will be line interpreter working with standard input stream, with support of fixed-size numeric arrays. We assume that Lua and Lua-API++ are already set up in our project.

First, we start with the interpreter part. The State object will create Lua state and interpret strings.

#include <iostream>
#include <luapp/lua.hpp>
using namespace std;
using namespace lua;
void interpretLine(State& state, const string& line)
{
try {
state.runString(line); // Attempt to execute string
} catch(exception& e) {
cerr << e.what() << endl; // Show error message in case of execution failure
}
}
void interpretStream(State& state, istream& in)
{
string currentLine;
while(!in.eof()) { // Read and interpret lines from given stream
getline(in, currentLine);
interpretLine(state, currentLine);
}
}
int main(int argc, const char* argv[])
{
State state;
interpretStream(state, cin);
}

With this part done, we can add support of arrays. We will use vector of double as underlying native type. The simple Lua interface of our arrays will be as follows:

  • global Lua function createArray will create array objects of given size;
  • array objects will be accessed with integer indices for reading and writing elements;
  • wrong index will produce an error;
  • length operator will return amount of elements in the array.

We can salvage some of the functions vector already has, so we need only to register userdata and write a couple of functions.

using namespace lua;
#include <vector>
using dvec = vector<double>; // Nice comfy name for array type
LUAPP_USERDATA(dvec, "numeric_array") // Register dvec as user data
dvec aCreate(unsigned int size) // Function to create array
{
return dvec(size); // Created object gets moved into Lua-allocated storage, no expensive reallocations occur.
}
void aDestroy(dvec& self) // Destruction function is required because taking destructor address is forbidden
{
self.~dvec(); // Explicitly destroy object placed in memory allocated by Lua
}
void aWrite(dvec& self, unsigned int index, double val) // Write a number into array
{
self.at(index) = val; // Use .at() for checked access
}

Now we are ready for the final step: setting up the environment. It will require two things: associate metatable with dvec and define array-creating function. The function that does it needs access to Lua state, so it has to be LFunction.

using namespace lua; // For automatic link generation
Retval setup(Context& c)
{
c.mt<dvec>() = // Context's mt() function gives access to userdata metatable
Table::records(ctx, // Assign to MT newly created table filled with key-value pairs
"__index", static_cast<double& (dvec::*)(size_t)>(&dvec::at), // Explicitly select non-const overload
"__newindex", aWrite, // Checked write
"__len", dvec::size, // Salvage vector::size for length operator
"__gc", aDestroy // Salvage vector's destructor for garbage collection
);
c.global["createArray"] = aCreate; // Array creation function is a global value
}

This setup function will have to be called from main() by State object, so it will need to be transformed into Lua compatible C function with mkcf template. Here is the final result:

#include <iostream>
#include <vector>
#include <luapp/lua.hpp>
using namespace lua;
using namespace std;
using dvec = vector<double>;
LUAPP_USERDATA(dvec, "Number array")
dvec aCreate(unsigned int size) { return dvec(size); }
void aDestroy(dvec& self) { self.~dvec(); }
void aWrite(dvec& self, unsigned int index, double val) { self.at(index) = val; }
Retval setup(Context& c)
{
c.mt<dvec>() = Table::records(c,
"__index", static_cast<double& (dvec::*)(size_t)>(&dvec::at),
"__newindex", aWrite,
"__len", dvec::size,
"__gc", aDestroy
);
c.global["createArray"] = aCreate;
return c.ret();
}
void interpretLine(State& state, const string& line)
{
try {
state.runString(line);
} catch(exception& e) {
cerr << e.what() << endl;
}
}
void interpretStream(State& state, istream& in)
{
string currentLine;
while(!in.eof()){
getline(in, currentLine);
interpretLine(state, currentLine);
}
}
int main()
{
State state;
state.call(mkcf<setup>);
interpretStream(state, cin);
}