0
votes

Intro

Hello Lua programmers.
I don't have much experience in Lua programming language, it is still a new thing for me.
I've run into a problem with coding a recursive function that is supposed to build 'function path' for each function in (any) given Lua table.

Example

I've the following Lua table:

_G["foo_lua_bar_libs"] = {
    ["my cool lib A"] = {
        ["add"] = function(a, b) return a + b end,
        ["sub"] = function(a, b) return a - b end,
        ["mul"] = function(a, b) return a * b end,
        ["div"] = function(a, b) return a / b end
    },
    ["last?"] = function() return -1 end,
    ["my cool lib B"] = {
        ["B 1 hex"] = function() return 0xB1 end,
        ["B 2"] = function() return 0xB2 end,
        ["B utilib"] = {
            ["idk"] = function() return '1234567890' end,
            ["calc"] = function() return math.pi end,
            ["crash"] = function() print(string.rep(' ', math.huge)) end,
            ["first"] = function(x) return x or false end
        },
        ["main"] = {
            ["phys"] = {
                ["calc"] = function(c, s) return math.cos(c) * math.sin(s) end
            },
            ["internal"] = {
                -- no function in here
            },
            [""] = function() local _ = "i am unnamed" end,
            ["foo . bar"] = {
                ["foo"] = "bar" .. 0,
                --["bar"] = _G
            },
            ["math"] = {
                calc = function(x, y) return x * x + y * y end,
                pi = math.pi, -- not a function
                cos = math.cos,
                [math.sin] = math.random
            }
        }
    },
    [""] = {
        [false] = function() return true end,
        [.80] = function(n) return 0.01 * n * n end,
        ["i am a function in nameless lib"] = function() return 1234 end,
        i_am_totally_a_function = 'not a function',
        ["hi. i am a \"quoted!\" func."] = function() return "quoted", '!' end,
        ["\r\n"] = function() return "win" end
    },
    [ [[misc.lib]] ] = {
        ["..."] = function(...) return ... end,
        nothing = function() return nil end,
        ["nil"] = nil, -- not a function
        crash = function() while 0 do end end,
        ["end"] = function() end,
        [string] = " a string; not a function "
    }
}

I need a recursive Lua function (lets call it BuildFunctionPath), which would build a function path for the given table, so that if I call BuildFunctionPath function:

local libs_function_path = BuildFunctionPath(_G.foo_lua_bar_libs, nil, { "_G", "foo_lua_bar_libs" })

BuildFunctionPath function would return the following table to the libs_function_path variable:

{
    ["_G[\"foo_lua_bar_libs\"][\"last?\"]"] = function() return -1 end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib A\"][\"add\"]"] = function(a, b) return a + b end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib A\"][\"div\"]"] = function(a, b) return a / b end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib A\"][\"mul\"]"] = function(a, b) return a * b end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib A\"][\"sub\"]"] = function(a, b) return a - b end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib B\"][\"B 1 hex\"]"] = function() return 0xB1 end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib B\"][\"B 2\"]"] = function() return 0xB2 end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib B\"][\"B utilib\"][\"calc\"]"] = function() return math.pi end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib B\"][\"B utilib\"][\"crash\"]"] = function() print(string.rep(' ', math.huge)) end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib B\"][\"B utilib\"][\"first\"]"] = function(x) return x or false end
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib B\"][\"B utilib\"][\"idk\"]"] = function() return '1234567890' end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib B\"][\"main\"][\"\"]"] = function() local _ = "i am unnamed" end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib B\"][\"main\"][\"math\"][\"calc\"]"] = function(x, y) return x * x + y * y end,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib B\"][\"main\"][\"math\"][\"cos\"]"] = math.cos,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib B\"][\"main\"][\"math\"][math.sin]"] = math.random,
    ["_G[\"foo_lua_bar_libs\"][\"my cool lib B\"][\"main\"][\"phys\"][\"calc\"]"] = function(c, s) return math.cos(c) * math.sin(s) end,
    ["_G[\"foo_lua_bar_libs\"][\"\"][false]"] = function() return true end,
    ["_G[\"foo_lua_bar_libs\"][\"\"][0.80]"] = function(n) return 0.01 * n * n end,
    ["_G[\"foo_lua_bar_libs\"][\"\"][\"\\r\\n\"]"] = function() return "win" end,
    ["_G[\"foo_lua_bar_libs\"][\"\"][\"hi. i am a \\\"quoted!\\\" func.\"]"] = function() return "quoted", '!' end,
    ["_G[\"foo_lua_bar_libs\"][\"\"][\"i am a function in nameless lib\"]"] = function() return 1234 end,
    ["_G[\"foo_lua_bar_libs\"][\"misc.lib\"][\"...\"]"] = function(...) return ... end,
    ["_G[\"foo_lua_bar_libs\"][\"misc.lib\"][\"crash\"]"] = function() while 0 do end,
    ["_G[\"foo_lua_bar_libs\"][\"misc.lib\"][\"end\"]"] = function() end,
    ["_G[\"foo_lua_bar_libs\"][\"misc.lib\"][\"nothing\"]"] = function() return nil end
}

Here is the code I've got stuck at and I don't know how to finish the BuildFunctionPath function:

function BuildFunctionPath(tbl, done, path)
    -- Please help me with this function.
    local functionStore = {} -- Final return table
    if tbl == nil then tbl = _G end
    if done == nil then done = {} end
    if path == nil then path = { "_G" } end
    for key, value in pairs(tbl) do
        if type(value) == "table" and not done[value] then
            done[value] = true
            -- I am stuck...
            local pathIndex = #path + 1
            path[pathIndex] = key
            --print(string.format("%q", path)) -- Quoted/Escaped path string
            --BuildFunctionPath(value, done, path) -- Recursion....
            path[pathIndex] = nil
            done[value] = nil
        elseif type(value) == "function" then
            functionStore[path] = value
        end
    end
end

If you are wondering why I need such function, it is so that I can know exactly in which particular table a given function is stored in.
Take a look at this screenshot: https://i.stack.imgur.com/Rh7Wr.png
So, if user enters "_G[\"print\"]" as path, it would return a _G.print function. Also, by creating inverse table of (_G) function path table, a user can then provide _G.print function and it would return "_G[\"print\"]" - the path string. Genius.

Please I need your help. Thanks for your time! :)
(Pardon my bad English.)

1

1 Answers

0
votes
local function BuildFunctionPath(tbl, done, path, functionStore)
   functionStore = functionStore or {} -- Final return table
   tbl = tbl or _G
   done = done or {} 
   path = path or { "_G" } 
   for key, value in pairs(tbl) do
      if type(value) == "table" and not done[value] or type(value) == "function" then
         -- convert key to string
         if type(key) == "string" then 
            key = string.format("%q", key) -- Quoted/Escaped key
         else
            key = tostring(key)            -- for numbers and booleans
            -- Please note that you can't use a function as a key: [math.sin] = math.random
            -- There is no way in Lua to calculate the name of a function (such as "math.sin")
         end
         local pathIndex = #path + 1
         path[pathIndex] = key
         if type(value) ~= "function" then
            -- if the value is a table
            done[value] = true
            BuildFunctionPath(value, done, path, functionStore) -- Recursion....
            done[value] = nil
         else
            -- if the value is a function
            functionStore[path[1].."["..table.concat(path, "][", 2).."]"] = value 
         end
         path[pathIndex] = nil
      end
   end
   return functionStore
end

local libs_function_path = BuildFunctionPath(_G.foo_lua_bar_libs, nil, { "_G", "foo_lua_bar_libs" }, nil)