Gavin
Gavin

Reputation: 499

How to use table.concat in Lua tables containing functions

I'm using Lua tables to store data to create web pages. The body content is stored in a single table, there is some static text and some generated by Lua functions.

Web.HTML={
"<h1>Hello World</h1><br>",
"<br><h2>Current Directory</h2><br>",
io.popen("cd"):read('*l'),
"<br><h2>Current Lua Interpreter</h2><br>",
arg[-1] or arg[0],
"<br><h2>Current Package Path</h2><br>",
package.path:gsub(";",";<br>\n"),
"<br><h2>Current Package CPath</h2><br>",
package.cpath:gsub(";",";<br>\n"),
"<br><h2>Current Environment Table:</h2><br>",
io.popen("set"):read('*a'):gsub("\n","<br>\n").." ",
"<br><h2>Current Date:</h2><br>",
os.date(),
"<br><h2>Math calculation</h2><br>",
math.pi/180
}

This table is then "printed" using table.concat function, adding some newlines to aid readability:

print(table.concat(Web.HTML,"<br>\n"))

The example above works as expected in Lua 5.1 or equivalent and the server successfully passes this as part of my web page.

I would like to to place arbitrary Lua code in my HTML table which returns a string to be concatenated, but I can't find the correct syntax. The concat function complains invalid value (function) at index in table for 'concat'.

I have tried:

Web.HTML = {
"Classic text example:",
function() print "Hello World"; end,
}

and

Web.HTML = {
"Classic text example:",
function() return "Hello World"; end,
}

A more useful example would be to list all the tables in the Lua global environment:

Web.HTML = {
    "<br><h2>Current Lua Libraries</h2><br>",
    function()
        local text = ''
        for i,v in pairs(_G) do
            if type(v)=="table" then
               text = text..i.."<br>\n"
            end
        end
        return text
    end
    ,
    "Success!"
}

I have also tried using loadstring(code ;return text )() as an entry in my table without success. Any pointers welcome.

Thanks in advance.

Gavin

Upvotes: 3

Views: 2897

Answers (4)

Egor Skriptunoff
Egor Skriptunoff

Reputation: 23737

Unfortunately, standard function table.concat() works only with strings and numbers.

You may write your own more versatile table.concat:

do
   local orig_table_concat = table.concat

   -- Define new function "table.concat" which overrides standard one
   function table.concat(list, sep, i, j, ...)
      -- Usual parameters are followed by a list of value converters
      local first_conv_idx, converters, t = 4, {sep, i, j, ...}, {}
      local conv_types = {
         ['function'] = function(cnv, val) return cnv(val)        end,
         table        = function(cnv, val) return cnv[val] or val end
      }
      if conv_types[type(sep)]   then first_conv_idx, sep, i, j = 1
      elseif conv_types[type(i)] then first_conv_idx,      i, j = 2
      elseif conv_types[type(j)] then first_conv_idx,         j = 3
      end
      sep, i, j = sep or '', i or 1, j or #list
      for k = i, j do
         local v, idx = list[k], first_conv_idx
         while conv_types[type(converters[idx])] do
            v = conv_types[type(converters[idx])](converters[idx], v)
            idx = idx + 1
         end
         t[k] = tostring(v) -- 'tostring' is always the final converter
      end
      return orig_table_concat(t, sep, i, j)
   end
end

Examples of usage:

Web = {}
Web.HTML = {
   "Classic text example:",
   function() return "Hello World"; end,
}

-- without converters
print(table.concat(Web.HTML, "<br>\n"))
--> Classic text example:<br>
--> function: 0x9ad1398

-- with a converter
print(table.concat(
   -- usual parameters for table.concat:
   Web.HTML, "<br>\n",
   -- additional parameters (converters):
   function(x)
      if type(x) == 'function' then
         return x()
      else
         return x
      end
   end
))
--> Classic text example:<br>
--> Hello World

Upvotes: 0

Piglet
Piglet

Reputation: 28950

table.concat concatenates the elements of a table to a string. Therefor it is mandatory that every element in the table can be converted to a string.

In all your attempts:

Web.HTML = {
"Classic text example:",
function() print "Hello World"; end,
}

Web.HTML[2] is a function which cannot be converted to a string.

You could replace your functions by strings or their return values in this case by simply defining them outside your list and then calling them in your table constructor or calling them right away with the function definitin in parentheses, or you could overload table.concat to your needs. Although I would rather implement a new concat function instead of overwriting the standard one to avoid confusion.

Upvotes: 0

Oleg V. Volkov
Oleg V. Volkov

Reputation: 22421

function returns, obviously, a function. Just call it immediately with (). Also don't forget to change print to return - your function needs to return value for table, not to print it out!

Web.HTML = {
    "Classic text example:",
    (function() return "Hello World"; end)(),
}

print(table.concat(Web.HTML,"<br>\n"))
-- Classic text example:<br>
-- Hello World

Upvotes: 3

Nicol Bolas
Nicol Bolas

Reputation: 473447

table.concat will not automagically execute code that it encounters. It concatenates a list of strings (or numbers); that's its job. If you pass it something that isn't a list of strings, then you have done something wrong.

If you have a list of strings+functions-that-return-strings, then you need to transform this into a list of strings yourself. This is easily done:

local list = --However you generate it.
for i, val in ipairs(list) do
  if(type(val) == "function") then
    list[i] = val() --call function
  end
end

Then you can concatenate list with table.concat. If you want to create a copy of the table, instead of overwriting the existing one, then that's easily done as well.

local list = --However you generate it.
local copy = {}
for i, val in ipairs(list) do
  if(type(val) == "function") then
    copy[i] = val() --call function
  else
    copy[i] = val
  end
end

Upvotes: 2

Related Questions