essay on programming languages, computer science, information techonlogies and all.

Saturday, February 1, 2014

Embedding Lua in C++

Lua is a script language. It is light weight and easily embedded in C. Below is a code snippet that embeds Lua and retrieves result back from the script.
// copy lua5.1.dll to output path
// link lua5.1.lib
extern "C"
{
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}
typedef boost::shared_ptr<lua_State> LuaStatePtr;
{
LuaStatePtr L( luaL_newstate(), lua_close ); // lua_close is called automatically when the lua_State is not reference in any place.
int width = 0, height = 0;
Foo( L.get(), "dimension.lua", &width, &height );
}
void Foo( lua_State* L, const char *filename, int *w, int *h )
{
if( luaL_loadfile( L, filename ) ||
lua_pcall( L, 0, 0, 0 ) )
{
throw Exception( StringFormat::As(
_T("cannot run config file : %s"), CA2CT( lua_tostring(L,-1) )));
}
lua_getglobal(L, "width"); // the 'width' value is pushed to the stack
lua_getglobal(L, "height"); // then 'height' is pused
if( !lua_isnumber(L,-2))
{
throw Exception( _T("'width' should be a number"));
}
if( !lua_isnumber(L,-1))
{
throw Exception( _T("'height' should be a number"));
}
*w = lua_tointeger( L, -2 ); // stack -2 is where the 'width' was pused
*h = lua_tointeger( L, -1 ); // stack -1 is where the 'height' was pushed
}
view raw LuaSample.cpp hosted with ❤ by GitHub
And the script used from above C++ code can be like below.
-- dimension.lua
width = 200
height = 300
view raw dimension.lua hosted with ❤ by GitHub


To further assist the embedding and interacting with C/C++, there is Luabind. You can make method and class binded to Lua script. And it also provides utility class that hide the details of stack.

Refer below code snippet to include Luabind in the C++ code.
// luabind 0.9.1
// copy luabind.dll ( or luabindd.dll ) to output path
// link luabind.lib ( or luabindd.lib )
#pragma warning( push )
// need preprocessor : LUABIND_DYNAMIC_LINK
#pragma warning(disable:4251)
#include "luabind\luabind.hpp"
#include "luabind\object.hpp"
#pragma warning( pop )
void foo()
{
LuaStatePtr LPtr( luaL_newstate(), lua_close );
lua_State* L = LPtr.get();
luabind::open(L);
...
}
Lua syntax is quite simple and intuitive. It can be applied to a configuration file and can be a good alternative to the INI. Refer below. Each name value pair can be string, real number, boolean or table. A name represents a table if it contains another sets of name-value pair like variable 'b' at below.

config =
{
a = 123.4, -- real number
b = { r = 0.3, g = 0.1, b = 0 }, -- table
c = true, -- boolean
d = 'Hello World!' -- string
}
It is not necessary to enclose configuration items with 'config={ }'. Any variable can be defined at the highest level - global level. But there are other global variables used in Lua - e.g. string, preload, package, os, setmetable and so on - which cause problem when we want to iterate through config items. If we choose to use a fixed name, then it will be straight forward on iteration. Just find the 'config' variable and iterate through all variables inside of it. It will be shown in code later.

void foo()
{
using namespace luabind;
try
{
LuaStatePtr LPtr( luaL_newstate(), lua_close );
lua_State* L = LPtr.get();
luabind::open(L);
if( luaL_loadfile( L, "test.lua" ) ||
lua_pcall( L, 0, 0, 0 ) )
{
throw Exception( StringFormat::As(
_T("cannot run config file : %s"), CA2CT( lua_tostring(L,-1) )));
}
object G( globals(L) );
object config( G[ "config" ] );
double a = object_cast<double>( config["a"] ); // a <- 123.4
object b( config[ "b" ] );
double br = object_cast<double>( b["r"] ); // b <- 0.3
bool c = object_cast<bool>( config["c"] ); // c <- true
std::string d = object_cast<std::string>( config["d"] ); // d <- "Hello World!"
}
catch( ... )
{
}
}
With above code, you can simply access each variable presumed that you know the variable names and hierachy.

But what if you want to access configuration like SAX does ? There should be a way to iterate through configuration items. Lua can iterate through items in the table, and we can make the script calls you with predefined interface. Here is code snippet that can do it.
using namespace std;
using namespace luabind;
class Visitor // this is the interface class that will be binded to Lua using LuaBind.
{
public:
virtual ~Visitor() {}
virtual void Begin( const string& arg ) = 0; // when an iteration starts
virtual bool BeginTable( const string& name ) = 0; // when a table starts
virtual void VisitItem( const string& key, const string& value ) = 0; // string
virtual void VisitItem( const string& key, double value ) = 0; // real number
virtual void VisitItem( const string& key, bool value ) = 0; // boolean
virtual void EndTable() = 0; // when the table ends
virtual void End() = 0; // when the iteration ends
};
class ConfigWriter : public Visitor
{
public:
ConfigWriter() { ... }
~ConfigWriter() { ... }
void Begin( const string& arg ) { ... }
...
};
typedef boost::shared_ptr<Visitor> VisitorPtr; // Lua will hold the created instance using shared_ptr which can be destroyed when it goes out of scope.
VisitorPtr CreateVisitor( const string& arg )
{
if( arg == "ConfigWriter" )
return VisitorPtr( new ConfigWriter() );
else
...
}
void Config::Open( cont _TCHAR* filename )
{
m_LuaState = LuaStatePtr( luaL_newstate(), lua_close );
lua_State* L = m_LuaState.get();
luaopen_base(L);
luaL_openlibs(L);
luabind::open( L );
// need typedef to help luabind to figure out right overloaded method.
typedef void(Visitor::*VisitItem1)( const string&, const string& );
typedef void(Visitor::*VisitItem2)( const string&, double );
typedef void(Visitor::*VisitItem3)( const string&, bool );
// Here goes a beautiful code snippet that binds Lua with C++ thanks to LuaBind
module(L) [
class_<Visitor>("Visitor")
.def("Begin", &Visitor::Begin)
.def("BeginTable", &Visitor::BeginTable)
.def("VisitItem", (VisitItem1)&Visitor::VisitItem)
.def("VisitItem", (VisitItem2)&Visitor::VisitItem)
.def("VisitItem", (VisitItem3)&Visitor::VisitItem)
.def("EndTable", &Visitor::EndTable)
.def("End", &Visitor::End)
,
def("CreateVisitor", CreateVisitor)
];
}
int pcall_handler(lua_State* L)
{
return 1;
}
void dostring(lua_State* state, char const* str)
{
lua_pushcclosure(state, &pcall_handler, 0);
if (luaL_loadbuffer(state, str, std::strlen(str), str))
{
string err(lua_tostring(state, -1));
lua_pop(state, 2);
throw err;
}
if (lua_pcall(state, 0, 0, -2))
{
std::string err(lua_tostring(state, -1));
lua_pop(state, 2);
throw err;
}
lua_pop(state, 1);
}
void Config::Save( const _TCHAR* filename )
{
stringstream script;
script <<
"function IterateTable( visitor, table, tablename ) \n"
" if visitor:BeginTable( tablename ) == false then \n " // call ConfigWriter::BeginTable( tablename )
" return \n"
" end \n"
" for k,v in pairs(table) do \n"
" local typev = type(v) \n"
" if typev == 'table' then \n"
" IterateTable( visitor, v, k ) \n" // recursive call to itself
" elseif typev == 'string' or typev == 'boolean' or typev == 'number' then \n"
" visitor:VisitItem( k, v ) \n" // call ConfigWriter::VisitItem( k, v )
" end \n"
" end \n"
" visitor:EndTable() \n" // call ConfigWriter::EndTable()
"end \n"
"visitor = CreateVisitor( 'ConfigWriter' ) \n" // this create a C++ class ConfigWriter
"visitor:Begin('" << string(filename) << "') \n" // call ConfigWriter::Begin( filename )
" IterateTable( visitor, Config, 'Config') \n" // iterate through 'Config'
"visitor:End() \n" // call ConfigWriter::End()
"visitor = nil \n"
"collectgarbage() \n" // ensure ConfigWriter's desturctor call.
;
dostring( m_LuaState.get(), script.str().c_str() );
}