Reputation: 396
I have a nice little Lua table parser which prints out pretty looking lua code and I love it...it works beautifully. There is a slight snag...if I go to print a table or array that has any integer keys it loops through it using pairs
(which doesn't mess the code up ironically), but I would rather it use ipairs
if possible. So I want to know is it possible to check a table (without physically looking at it) if it can use ipairs to loop through it first else use pairs. Then is there a way to start looping at 0 instead of Lua's default 1?
Lua Table Parser (Base code found on google, changed it to make it print more array friendly)...
function TableParser(name, object, tabs)
local function serializeKeyForTable(k)
if type(k)=="number" then
return ""
end
if string.find(k,"[^A-z_-]") then
return k
end
return k
end
local function serializeKey(k)
if type(k)=="number" then
if k == 0 then
return "\t[" .. k .."] = "
else
return "\t"
end
end
if string.find(k,"[^A-z_-]") then
return "\t" .. k .. " = "
end
return "\t" .. k .. " = "
end
if not tabs then tabs = "" end
local function serialize(name, object, tabs) -- = {
local output = tabs .. (name ~= "" and name .. " = " or "") .. "{" .. "\n"
for k,v in pairs(object) do
if type(v) == "number" then
output = output .. tabs .. serializeKey(k) .. v
elseif type(v) == "string" then
output = output .. tabs .. serializeKey(k) .. string.format("%q",v)
elseif type(v) == "table" then
output = output .. serialize(serializeKeyForTable(k), v, tabs.."\t")
elseif type(v) == "boolean" then
output = output .. tabs .. serializeKey(k) .. tostring(v)
else
output = output .. tabs .. serializeKey(k) .. "\"" .. tostring(v) .. "\""
end
if next(object,k) then
output = output .. ",\n"
end
end
return output .. "\n" .. tabs .. "}"
end
return serialize(name, object, tabs)
end
Upvotes: 3
Views: 2709
Reputation: 45654
You can always iterate a table both with pairs
and ipairs
, whether it makes sense or not.
ipairs
iterates over the sequence present in the array (which means sequential integer keys starting with 1, up to the first missing value), unless overridden with metamethod __ipairs
(5.2).
pairs
iterates over all key-value pairs with next
(thus in an unspecified order), unless overridden with metamethod __pairs
(5.2).
Which means that ipairs
will generally not enumerate any key-value-pair pairs
won't show.
And there is no way to verify whether ipairs
will enumerate all keys pairs
will enumerate, but enumerating everything and testing manually.
BTW: You can make your own iterator which first iterates over the sequence, and then over everything else:
function my_iter(t)
local k, cap
return function()
local v
if k == nil then k, cap = 0 end
if not cap then
k = k + 1
v = t[k]
if v ~= nil then return k, v end
cap, k = k
end
repeat k, v = next(k)
until type(k) ~= "number" or 0 < k and k < cap and math.ceil(k) == k
return k, v
end
end
Though probably better just sort the keys for pretty-printing:
function sorted_iter(t)
local keys, index = {}, 0
for k in next, t do
keys[#keys + 1] = k
end
table.sort(keys)
return function()
index = index + 1
local k = keys[index]
return k, t[k]
end
end
Upvotes: 6
Reputation: 4271
So I want to know is it possible to check a table (without physically looking at it) if it can use ipairs to loop through it first else use pairs.
Don't check, just do! Use ipairs
first and keep track of the largest key that the ipairs
iterator returned. Then use pairs
to iterate again and ignore all integer keys between 1
and that largest key from ipairs
.
If you really want to check whether ipairs
will do something, then look at index 1
in the table (rawget( object, 1 ) ~= nil
). Checking whether ipairs
will cover all elements in the table is not possible without iterating the table.
Then is there a way to start looping at 0 instead of Lua's default 1?
ipairs(t)
returns three values: an iterator function, the table t
as the state variable, and an initial index value 0
. If you use -1
as initial index value, ipairs
will start the iteration at 0
(the iterator function always increments by one before using the index value):
t = { 1, 2, 3, [ 0 ] = 0 }
for i,v in ipairs( t ), t, -1 do -- only use first value returned by ipairs
print( i, v )
end
However, be aware that Lua 5.2 has added support for a new metamethod __ipairs
which allows you to return a custom iterator triplet to use for ipairs
iteration, and the iterator function returned in this case might need different state and initial index values.
Edit:
To incorporate the suggestions into your code insert before the for k,v in pairs(object) do
-loop:
local largest = 0
for k,v in ipairs(object) do
largest = k
local t = type(v)
if t == "table" then
output = output .. tabs .. "\t" .. serialize( "", v, tabs.."\t" )
elseif t == "string" then
output = output .. tabs .. "\t" .. string.format("%q", v)
else
output = output .. tabs .. "\t" .. tostring(v)
end
output = output .. ",\n"
end
and inside the loop add an additional if
statement to check for array keys:
for k,v in pairs(object) do
if type(k) ~= "number" or k < 1 or k > largest or math.floor(k) ~= k then
-- if type(v) == "number" then
-- ...
end
end
If you apply this modified TableParser
function to the following table:
local t = {
1, 2, 3,
value = "x",
tab = {
"a", "b", field = "y"
}
}
print( TableParser( "", t ) )
the output is:
{
1,
2,
3,
tab = {
"a",
"b",
field = "y"
},
value = "x"
}
But doing table serialization properly is tricky. E.g. your implementation doesn't handle cycles or tables as keys. See the Lua Wiki for some implementations.
Upvotes: 8