Search code examples
lualua-tablelua-4.0

Lua 4 "n" property of tables


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()

Solution

  • 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.