Big Bucket Software you like to use
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.