The Global Table, _G
03/09/21 09:02 Filed in: Lua
Everything in Lua is a table, including variables. Variables that you don't explicitly label as local, are placed into a table known as _G. Today, I learn a bit about how to work with it.
_G
This global table can be used like any other table. I was curious as to what's stored in it, so I wrote a test. The first thing I discovered was that _G is an array, and not a dictionary. That means I can iterate over it using pairs. I wrote a test program to dump the kind of things stored in _G.
_G contains a lot of different things, everything from strings to coroutines. Well look at that! _G also has a table named _G. Let's dump that. I get the same thing. So, _G has a reference to itself. Is that important? It's probably important to the functioning of Lua. I could go through and dump all of the things within _G, but I won't do that now.
Because _G is just a table, albeit a special one, I can store my own data and modify _G. In fact, when I don't declare a variable as local, its stored in _G.
One problem with Lua is the it isn't a typed language. That can cause problems if I try to use variables that are either not declared or mistyped. I can fix this by setting up a way to track which variables are actually declared. I need a table to track my program's variables. I'm going to call it myvars and attach it to _G.
_G.myvars = {} // create an empty table to track my variables.
I need to set the metatable for _G as _G. Hold on… Remember that nested _G in _G? I'm betting that _G is its own metatable! That would explain the table dump. The reason I need set it again is to refresh the reference now that I've modified the base _G.
setmetatable(_G, _G) // set _G as its own metatable.
So what should I store in myvars? Well, I want to store variables I declare. I can do this as either an array or a dictionary. I like dictionaries. I can index into myvars with the variable name: _G.myvars[var]. What do I want to make the value? I'll just make this a boolean to indicate if the variable is defined or not. I need a function to insert the variable into myvars and set the flag.
_G.Create = function (k,v)
_G.myvars[k] = true — I don't see a valid reason for a false here.
end
We should now be able to do this:
In lines 9-10 I use my function to create a variable abc and then assign a value to it. That's what I want. In lines 13-14, I create a variable def and assign a value to it. That shouldn't work because I haven't declared def using Create.
When I run this, I have an obvious problem.
The reason is _G still returns any variable stored in it. I need to adjust _G so that it only returns variables that are declared through my Create function.
I think I need to modify the __index function of _G. The __index metamethod is called to retrieve values if the value isn't nil. __index does the variable lookup. I have to get __index to search the myvars table and return a value only if it exists in myvars.
_G.__index = function(v,k)
if not _G.myvars[k] then
print("ERROR - Undefined variable: " .. k)
return nil
end
— we found the variable return it using the normal Lua routes…
return rawget(v,k)
end
rawget retrieves a variable, but it bypasses __index. I do this because if I just had:
return _G.myvars[k]
I might end up calling __index again in recursive loop.
In addition to __index, I need to modify __newindex which assigns values to a missing key. It's the complement of __index.
_G.__newindex = function (x,k,v)
if not _G.myvars[k] then
print("ERROR - Can't assign to undeclared variable" .. k)
else
rawset(x,k,v)
end
end
Here, I just check to see if the variable exists in myvars. If it does, I use rawset to bypass __newindex the same way I used rawget to bypass __index.
Here is my final code:
And this is what happens when I run it:
Pretty cool! We now have a method of ensuring a variable is declared without using types. One thing I tried was to dump myvars, but I'm either doing something wrong, or my guess is that by modifying _G.__index, I've mangled iteration over myvars, but not the rest of _G for some reason.
This returns:
…and nothing else.
That's enough for today.