I've got a scenario where I want to take input to a printf implementation and pre-process the arguments. My goal is to take the input, process the variable arguments (...
) if necessary, and feed that into a string.format. The issue I'm encountering is the data gets "lost" when passing it around. I'm finding nil values in ...
are not passed in. For example, if the normal run passes nil, false, false
, subsequent downline functions in the call path only see false, false
. What am I missing here to get the keep the data preserved when converted to an internal table ({...}
) for processing?
EDIT: I was incorrect in my initial assessment. nil is never preserved (even in printf). If ...
is printed, the table can be empty (for all nil) or only have indexed values in the places that contain "real" data (e.g. TablePrint gives { [2] = false, [3] = false })
This is running inside a game, but I can replicate the behavior without using any of the game data.
local function TablePrint (data, indent)
if not indent then indent = 0 end
local output = string.rep(" ", indent) .. "{\r\n"
indent = indent + 2
for k, v in pairs(data) do
output = output .. string.rep(" ", indent)
if (type(k) == "number") then
output = output .. "[" .. k .. "] = "
elseif (type(k) == "string") then
output = output .. k .. "= "
end
if (v == nil) then
output = output .. 'nil' .. ",\r\n"
elseif (v == NULL) then
output = output .. 'NULL' .. ",\r\n"
elseif (type(v) == "number") then
output = output .. v .. ",\r\n"
elseif (type(v) == "string") then
output = output .. "\"" .. v .. "\",\r\n"
elseif (type(v) == "table") then
output = output .. TablePrint(v, indent + 2) .. ",\r\n"
else
output = output .. "\"" .. tostring(v) .. "\",\r\n"
end
end
output = output .. string.rep(" ", indent-2) .. "}"
return output
end
local function NormalizeValue(v)
local output
if (v == nil) then
output = 'nil'
elseif (v == NULL) then
output = 'NULL'
elseif (type(v) == "number" or type(v) == "string") then
output = v
elseif (type(v) == "table") then
output = TablePrint(v)
else
output = tostring(v)
end
return output
end
local function NormalizeArgs(...)
local args = {...}
--print(TablePrint(args))
local normalizedArgs = {}
for _,v in ipairs(args) do
table.insert(normalizedArgs, NormalizeValue(v))
end
return unpack(normalizedArgs)
end
local function printf(message, ...)
--print(TablePrint({...}))
local output
if (type(message) == "number") then
output = message
elseif (type(message) == "string") then
output = string.format(message, NormalizeArgs(...))
--this call to string.format succeeds if used
--output = string.format(message, ...)
elseif (type(message) == "table") then
output = TablePrint(message)
else
output = tostring(message)
end
print(output)
end
--this line fails since nil isn't preserved across call boundaries
--printf('initial conditions, cursor: %s, confirm window: %s, isfinished: %s', nil, nil, nil)
--this line fails since nil isn't preserved across call boundaries
printf('initial conditions, cursor: %s, confirm window: %s, isfinished: %s', nil, false, false)
--this will succeed
printf('initial conditions, cursor: %s, confirm window: %s, isfinished: %s', false, false, false)
--in the normal impl the final parameter is supplied by a call to a local function
printf('initial conditions, cursor: %s, confirm window: %s, isfinished: %s', gamelib.Cursor.ID(), gamelib.Window('Confirm').Open(), nil)
You can use select to fully examine ...
. It's probably easier to return multiple values recursively than to create a table just to unpack it:
local function NormalizeArgs(...)
if select('#', ...) > 0 then
return NormalizeValue(select(1, ...)), NormalizeArgs(select(2, ...))
end
end