In Lua 4, many tables have an "n" property which tracks the number of items inside the table.
Do all tables have this property? Can it be overridden?
I ask, because I'm trying to develop a routine that prints all of a table's elements recursively in valid Lua syntax, and want to know if it's safe to filter all "n" items out of the result?
Thanks.
[edit]
Here's the script:
-- ThoughtDump v1.4.0
-- Updated: 2017/07/25
-- *****************
-- Created by Thought (http://hw2.tproc.org)
-- Updated by Mikali
-- DESCRIPTION
-- ***********
-- Parses the globals table and __TDPrints its contents to "HW2.log".
-- Can also be used to parse (i.e., pretty-print) generic tables in some cases.
-- Note: functions & variables must actually be declared in order to be parsed.
-- Otherwise, they are ignored.
-- Note: if parsing a table other than the globals table, the __TDPrinted table
-- values may be in a different order than was originally written. Values with
-- numerical indices are moved to the "top" of the table, followed by values
-- with string indices, followed by tables. Functions appear in different
-- locations, depending on whether they are indexed using a number or a string.
-- Note: despite the fact that nil values cannot be stored in tables, they are
-- still handled.
-- Note: even though functions may be referenced within tables, a function will
-- only be parsed correctly if it is indexed using a string that is the same as
-- the name of the function.
__TDOutputString = ""
function __TDParse(name, value, level, verbose, numbers, collapse)
if ((name == "__TDParse") or (name == "__TDSortHash") or (name == "__TDPrint") or (name == "__TDPrintGlobals()") or (name == "__TDOutputString")) then
return
end
local Element = nil
local ValType = type(value)
local NamType = type(name)
local PreLevel = ""
if (collapse == 0) then
for i = 1, level do
PreLevel = PreLevel .. "\t"
end
end
local ComLevel = ""
if (level ~= 0) then
ComLevel = ","
end
if ((ValType == "function") or (ValType == "userdata")) then
if (NamType == "string") then
Element = PreLevel .. name .. " = " .. name .. ComLevel
elseif (numbers == 1) then
Element = PreLevel .. "[" .. name .. "] = " .. name .. ComLevel
else
Element = PreLevel .. name .. ComLevel
end
elseif (ValType == "string") then
if (NamType == "string") then
Element = PreLevel .. name .. " = \"" .. value .. "\"" .. ComLevel
elseif (numbers == 1) then
Element = PreLevel .. "[" .. name .. "] = \"" .. value .. "\"" .. ComLevel
else
Element = PreLevel .. "\"" .. value .. "\"" .. ComLevel
end
elseif (ValType == "number") then
if (NamType == "string") then
Element = PreLevel .. name .. " = " .. value .. ComLevel
elseif (numbers == 1) then
Element = PreLevel .. "[" .. name .. "] = " .. value .. ComLevel
else
Element = PreLevel .. value .. ComLevel
end
elseif (ValType == "table") then
if (NamType == "string") then
Element = PreLevel .. name .. " ="
elseif (numbers == 1) then
Element = PreLevel .. "[" .. name .. "] ="
else
Element = ""
end
elseif (ValType == "nil") then
if (NamType == "string") then
Element = PreLevel .. name .. " = nil" .. ComLevel
elseif (numbers == 1) then
Element = PreLevel .. "[" .. name .. "] = nil" .. ComLevel
else
Element = PreLevel .. "nil" .. ComLevel
end
else
Element = PreLevel .. "-- unknown object type " .. ValType .. " for object " .. name
end
if (verbose == 1) then
Element = Element .. " -- " .. ValType .. ", tag: " .. tag(value)
end
if (((ValType == "table") and (NamType == "number") and (numbers == 0)) or (collapse == 1)) then
__TDPrint(Element, 0)
else
__TDPrint(Element, 1)
end
if (ValType == "table") then
__TDPrint(PreLevel .. "{", collapse == 0)
__TDSortHash(__TDParse, value, level + 1, verbose, numbers, collapse)
__TDPrint(PreLevel .. "}" .. ComLevel, 1)
end
end
function __TDSortHash(func, tabl, level, verbose, numbers, collapse)
local typesarray = {}
local typescount = {}
local keycount = 1
local keyarray = {}
for i, iCount in tabl do
local thistype = type(iCount)
if not (typesarray[thistype]) then
typescount[thistype] = 0
typesarray[thistype] = {}
end
typescount[thistype] = typescount[thistype] + 1
typesarray[thistype][typescount[thistype]] = i
end
sort(typesarray)
for i, iCount in typesarray do
sort(iCount)
for j, jCount in iCount do
keyarray[keycount] = tostring(jCount)
keycount = keycount + 1
end
end
for i, iCount in keyarray do
local tempcount = tonumber(iCount)
if (tempcount) then
iCount = tempcount
end
func(iCount, tabl[iCount], level, verbose, numbers, collapse)
end
end
function __TDPrint(instring, newline)
__TDOutputString = __TDOutputString .. instring
if (newline == 1) then
__TDOutputString = __TDOutputString .. "\n"
end
end
function __TDPrintGlobals()
__TDOutputString = ""
__TDPrint("globals =", 1)
__TDPrint("{", 1)
__TDSortHash(__TDParse, globals(), 1, 0, 0, 0)
__TDPrint("}\n", 1)
local WriteFile = "$test_globals_write.lua"
writeto(WriteFile)
write(__TDOutputString)
writeto()
end
__TDPrintGlobals()
In Lua 4.x in tables, n is just an element of the table like any other elements a table could contain, but it's not part of the table mechanism itself. So, it can be overwritten or removed.
Some functions use it, like tinsert() ,and other table functions:
local tbl = { n=0 }
tinsert(tbl, 123)
print(tbl.n) --> 1
This is very useful as the getn() function gives only the highest number index of the table. But if there is only named elements in the table or a mix or number indexes and named indexes, then getn() doesn't reflect the real number of elements in the table. If elements are always inserted (or removed) using table functions like tinsert() then n is the accurate number of elements in the table.
Lua 4.x --> Lua 5.x equivalent:
getn(tbl) #tbl
tinsert(tbl,e) table.insert(tbl,e) or tbl:insert(e)
Of course you can still add elements in a table using the simple table access. But as n can be very useful, try to keep it updated as well.
tbl["Bla"] = 234
tbl.Bli = 345
tbl.n = tbl.n + 2
If n doesn't exist in a table but is needed by the code somewhere, it can be added using the for loop:
local tbl = {1,2,3,4,5,6}; tbl.a=11; tbl.b=22; tbl.c=33
local n = 0
for ie, e in tbl do
n = n + 1
end
tbl.n = n
or the foreach loop:
local tbl = {1,2,3,4,5,6}; tbl.a=11; tbl.b=22; tbl.c=33
tbl.n = 0
foreach(tbl, function() %tbl.n = %tbl.n + 1 end )
Note 1: The initialization of tbl.n to 0 will give the number of elements in the table, including n. Here the result of tbl.n is 10. As eventually we don't want n to be counted as a real element of the table (which is is) but only counting other elements, we should init n to -1 then.
Note 2: The Lua 4.x upvalue operator % is used here because the tbl variable is not reachable in the function (not in the scope) for the foreach loop. It can be reached using %tbl. However, a upvalue is always read only, so the tbl variable can't be changed. The following will generate an error in the function:
%tbl = { } -- change the reference to another table
%tbl = 135 -- change the ref to the table for a number (or a string, ...)
As tbl variable contains in fact a reference to a table, the referenced table can be modifiable and therefore the element n can be changed without problem (as well as other elements of the table).
%tbl.n = %tbl.n + 1 -- increment the element n of the referenced table
Note 3: A global variable tbl could have been used, but it's good practice to always use local variable. Access to local variables is also faster than global.