How to filter out user defined globals in Lua from C++?
Asked Answered
T

2

2

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.

Tamboura answered 11/12, 2013 at 18:57 Comment(1)
I'ved edited some of your questions to add the lua tag so that they are easier to find.Infare
T
4

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)

Tamboura answered 12/12, 2013 at 1:29 Comment(0)
I
5

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.

Infare answered 11/12, 2013 at 19:7 Comment(1)
I think I see how this work... So you suggest that I manually keep track of all global variables along as they got declared? I do have a similar idea with hash keys, I will post it later.Tamboura
T
4

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)

Tamboura answered 12/12, 2013 at 1:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.