Lua uninitialized variable access and nil
March 22nd, 2006
This is something I posted on the Lua mailing list that I felt like reposting here…
Some Lua behaviour that has bothered me since I started using it was:
“It is not an error to access a non-initialized variable; you just get the special value nil as the result”
http://www.lua.org/pil/1.2.html
Sometimes, (in my case, ALL the time) this is not desirable behaviour.
For example, I was trying to figure out a sensible way to expose an
enum in C to Lua. For example,
enum Action
{
Action_Stand
Action_Walk,
Action_Run,
};
So, I figured I’d make a table called Action and add a key for each
(integer) value in the enum. Problem is, if I do this in my script:
some_action = Action.Stand some_other_action = Action.Dance
Then some_action will be set to an integer and some_other_action will
be set to a nil… silently. Obviously, I’ll notice this error when I
attempt to actually use some_other_action, but it would be nice if
Lua would bail out with a warning straight away.
Well here’s the solution.
Create a seperate table with its __index metamethod set to print a “no
such key” error message. I called this table “Object”. Now, set this
table as the metatable of any enum tables. I found this useful
everywhere; “Object” is effectively my top-level base class.
And here it is implemented in C++:
extern "C"
{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#include <string>
#include <iostream>
using namespace std;
//----------------------------------------------------------
void newTable(lua_State* state, const string& table)
{
lua_newtable(state);
lua_setglobal(state, table.c_str());
}
//----------------------------------------------------------
void setTableInt(lua_State* state,
const string& table,
const string& key,
int integer)
{
lua_getglobal(state, table.c_str());
lua_pushstring(state, key.c_str());
lua_pushnumber(state, integer);
lua_settable(state, -3);
lua_pop(state, 1);
}
//----------------------------------------------------------
void setTableCFunction(lua_State* state,
const string& table,
const string& key,
lua_CFunction function)
{
lua_getglobal(state, table.c_str());
lua_pushstring(state, key.c_str());
lua_pushcfunction(state, function);
lua_settable(state, -3);
lua_pop(state, 1);
}
//----------------------------------------------------------
void setMetaTable(lua_State* state,
const string& table,
const string& metatable)
{
lua_getglobal(state, table.c_str());
lua_getglobal(state, metatable.c_str());
lua_setmetatable(state, -2);
lua_pop(state, 1);
}
//----------------------------------------------------------
int index(lua_State* state)
{
string key(luaL_checkstring(state, 2));
string error = "No such key \"" + key + "\"";
luaL_error(state, error.c_str());
}
//----------------------------------------------------------
enum Action
{
Action_Stand,
Action_Walk,
Action_Run
};
//----------------------------------------------------------
int main(int argc, char** argv)
{
const char* script = argv[1];
lua_State* state = lua_open();
luaL_openlibs(state);
newTable(state, "Object");
setTableCFunction(state, "Object", "__index", index);
newTable(state, "Action");
setMetaTable(state, "Action", "Object");
setTableInt(state, "Action", "Stand", Action_Stand);
setTableInt(state, "Action", "Walk", Action_Walk);
setTableInt(state, "Action", "Run", Action_Run);
if (luaL_loadfile(state, script) == 0)
{
int errors = lua_pcall(state, 0, LUA_MULTRET, 0);
if (errors)
{
cerr << lua_tostring(state, -1) << endl;
return -1;
}
}
lua_close(state);
return 0;
}
When I type to execute the line:
a = Action.JumpAroundLikeACrazyPerson
I get the error:
script.lua:1: No such key "JumpAroundLikeACrazyPerson"
Enjoy. In my opinion, this is a lot more useful than simply failing silently.