Question

Consider this small Lua test script.

g1 = "Global 1"
g2 = "Global 2"

function test ()
  local l1
  print(g1,g2,l1)
end

test()

Assume you pause the execution at print(g1,g2,l1) and from C++ get all the global variables with this C code:

lua_pushglobaltable(L);
lua_pushnil(L);
while (lua_next(L,-2) != 0) {
  const char* name = lua_tostring(L,-2);

  // How do I tell a user defined
  // global variable (now in name)
  // from all other environment variables?

  lua_pop(L,1);
}
lua_pop(L,1); // global table

When I get the name of a global entry, how can I tell if this is a global variable defined by the user in the script, like g1 and g2?

Since the user can freely write the script, I can't search for a specific global, I need to tell them apart somehow.

Was it helpful?

Solution 2

My solution was to build a hash table of the global environment before I loaded the main script. When I need to get the user defined globals I only display globals not present in the hash table. In this way the script can run at full speed without keeping track of globals in in runtime.

Example of my solution (this is the short version of my implementation):

// The hash table storing global names
std::set<unsigned int> Blacklist;

// Create hash table "Blacklist"
void BlacklistSnapshot(lua_State *L) {

  lua_pushglobaltable(L);
  lua_pushnil(L);
  while (lua_next(L,-2) != 0) {                     // pop NIL, push name,value
    Blacklist.insert(HashName(lua_tostring(L,-2))); // insert to hash table
    lua_pop(L,1);                                   // remove value
  }
  lua_pop(L,1); // Remove global table
}


// Display user defined globals only
void PrintGlobals(lua_State *L) {

  lua_pushglobaltable(L);
  lua_pushnil(L);
  while (lua_next(L,-2) != 0) { // pop NIL, push name,value

    // Check if the global is present in our blacklist
    if (Blacklist.find(HashName(lua_tostring(L,-2))) == Blacklist.end()) {
      // Not present, print it...
      PrintFormattedVariable(lua_type(L,-1),lua_tostring(L,-2));
    }
    lua_pop(L,1); // remove value
  }
  lua_pop(L,1);   // remove global table
}


void RunScript(void) {

  // Create new Lua state
  L = luaL_newstate();

  // Load all Lua libraries
  luaL_openlibs(L);

  // Create co-routine
  CO = lua_newthread(L);

  BlacklistSnapshot(CO);

  // Load and compile script
  AnsiString script(Frame->Script_Edit->Text);
  if (luaL_loadbuffer(CO,script.c_str(),script.Length(),"Test") == LUA_OK) {
    lua_resume(CO,NULL,0);
  } else {
    cs_error(CO, "Compiler error: ");    // Print compiler error
  }
}

The function HashName takes a string and returns the hash key for it as an unsigned int, use whatever Hash algorithm you like here...

When you need to display the globals, call PrintGlobals() (I do it from a hook routine)

OTHER TIPS

I see two ways. In the first, you record the names of all global variables before loading user scripts:

local S={}
_G["system variables"]=S
for k in pairs(_G) do S[k]=true end

Then in your C code, you traverse globals variables and filter only those whose name is in the table "system variables". Use lua_getglobal(L,"system variables") to get this table.

In the second way, you track the definition of global variables after the system ones have been loaded. You set this up by running this script before loading user scripts:

local U={}
_G["user variables"]=U
local function trace(t,k,v)
    U[k]=true
    rawset(t,k,v)
end   
setmetatable(_G,{ __newindex = trace })

Then in your C code, you traverse globals variables and filter only those whose name is not in the table "user variables". Use lua_getglobal(L,"user variables") to get this table.

In both cases, do not convert keys in _G to strings: indexed the special tables directly with the original keys.

Note that you can call lua_getglobal(L,"system variables") or lua_getglobal(L,"user variables") just once, before the traversal, and index it repeatedly inside the loop.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top