Merge remote-tracking branch 'myk002/myk_printall_nontable' into develop

develop
lethosor 2021-08-21 22:51:35 -04:00
commit 10db894d99
No known key found for this signature in database
GPG Key ID: 76A269552F4F58C1
4 changed files with 289 additions and 19 deletions

@ -50,6 +50,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## API
- The ``Items`` module ``moveTo*`` and ``remove`` functions now handle projectiles
## Lua
- new global function: ``safe_pairs(iterable[, iterator_fn])`` will iterate over the ``iterable`` (a table or iterable userdata) with the ``iterator_fn`` (``pairs`` if not otherwise specified) if iteration is possible. If iteration is not possible or would throw an error, for example if ``nil`` is passed as the ``iterable``, the iteration is just silently skipped.
## Documentation
- `quickfort-library-guide`: updated dreamfort documentation and added screenshots
- `dfhack-examples-guide`: documentation for all of `dreamfort`'s supporting files (useful for all forts, not just Dreamfort!)

@ -162,24 +162,49 @@ NEWLINE = "\n"
COMMA = ","
PERIOD = "."
function printall(table)
local ok,f,t,k = pcall(pairs,table)
if ok then
for k,v in f,t,k do
print(string.format("%-23s\t = %s",tostring(k),tostring(v)))
local function _wrap_iterator(next_fn, ...)
local wrapped_iter = function(...)
local ret = {pcall(next_fn, ...)}
local ok = table.remove(ret, 1)
if ok then
return table.unpack(ret)
end
end
return wrapped_iter, ...
end
function printall_ipairs(table)
local ok,f,t,k = pcall(ipairs,table)
if ok then
for k,v in f,t,k do
print(string.format("%-23s\t = %s",tostring(k),tostring(v)))
end
function safe_pairs(t, iterator_fn)
iterator_fn = iterator_fn or pairs
if (pcall(iterator_fn, t)) then
return _wrap_iterator(iterator_fn(t))
else
return function() end
end
end
-- calls elem_cb(k, v) for each element of the table
-- returns true if we iterated successfully, false if not
-- this differs from safe_pairs() above in that it only calls pcall() once per
-- full iteration and it returns whether iteration succeeded or failed.
local function safe_iterate(table, iterator_fn, elem_cb)
local function iterate()
for k,v in iterator_fn(table) do elem_cb(k, v) end
end
return pcall(iterate)
end
local function print_element(k, v)
dfhack.println(string.format("%-23s\t = %s", tostring(k), tostring(v)))
end
function printall(table)
safe_iterate(table, pairs, print_element)
end
function printall_ipairs(table)
safe_iterate(table, ipairs, print_element)
end
local do_print_recurse
local function print_string(printfn, v, seen, indent)
@ -199,15 +224,9 @@ local fill_chars = {
setmetatable(fill_chars, fill_chars)
local function print_fields(value, seen, indent, prefix)
local ok,f,t,k = pcall(pairs,value)
if not ok then
dfhack.print(prefix)
dfhack.println('<Type doesn\'t support iteration with pairs>')
return 0
end
local prev_value = "not a value"
local repeated = 0
for k, v in f,t,k do
local print_field = function(k, v)
-- Only show set values of bitfields
if value._kind ~= "bitfield" or v then
local continue = false
@ -233,7 +252,10 @@ local function print_fields(value, seen, indent, prefix)
end
end
end
if repeated > 0 then
if not safe_iterate(value, pairs, print_field) then
dfhack.print(prefix)
dfhack.println('<Type doesn\'t support iteration with pairs>')
elseif repeated > 0 then
dfhack.println(prefix .. "<Repeated " .. repeated .. " times>")
end
return 0

@ -0,0 +1,40 @@
-- tests misc functions added by dfhack.lua
function test.safe_pairs()
for k,v in safe_pairs(nil) do
expect.fail('nil should not be iterable')
end
for k,v in safe_pairs('a') do
expect.fail('a string should not be iterable')
end
for k,v in safe_pairs({}) do
expect.fail('an empty table should not be iterable')
end
for k,v in safe_pairs(df.item._identity) do
expect.fail('a non-iterable light userdata var should not be iterable')
end
local iterated = 0
local t = {a='hello', b='world', [1]='extra'}
for k,v in safe_pairs(t) do
expect.eq(t[k], v)
iterated = iterated + 1
end
expect.eq(3, iterated)
end
function test.safe_pairs_ipairs()
local t = {1, 2}
setmetatable(t, {
__pairs = function()
expect.fail('pairs() should not be called')
end,
})
local iterated = 0
for k,v in safe_pairs(t, ipairs) do
expect.eq(k, v)
iterated = iterated + 1
end
expect.eq(#t, iterated)
end

@ -0,0 +1,205 @@
-- tests print-related functions added by dfhack.lua
local dfhack = dfhack
local mock_print = mock.func()
local function test_wrapper(test_fn)
mock.patch({{dfhack, 'print', mock_print},
{dfhack, 'println', mock_print}},
test_fn)
mock_print = mock.func()
end
config.wrapper = test_wrapper
function test.printall_table()
printall({a='b'})
expect.eq(1, mock_print.call_count)
expect.true_(mock_print.call_args[1][1]:find('a%s+= b'))
end
function test.printall_string()
printall('a')
expect.eq(0, mock_print.call_count)
end
function test.printall_number()
printall(10)
expect.eq(0, mock_print.call_count)
end
function test.printall_nil()
printall(nil)
expect.eq(0, mock_print.call_count)
end
function test.printall_boolean()
printall(false)
expect.eq(0, mock_print.call_count)
end
function test.printall_function()
printall(function() end)
expect.eq(0, mock_print.call_count)
end
local function new_int_vector()
-- create a vector of integers by cloning one from world. we do it this way
-- because we can't allocate typed vectors from lua directly.
local vector = df.global.world.busy_buildings:new()
vector:resize(0)
return vector
end
function test.printall_userdata()
local utable = new_int_vector()
dfhack.with_temp_object(utable, function()
utable:insert(0, 10)
utable:insert(1, 20)
printall(utable)
expect.eq(2, mock_print.call_count)
expect.true_(mock_print.call_args[1][1]:find('0%s+= 10'))
expect.true_(mock_print.call_args[2][1]:find('1%s+= 20'))
end)
end
function test.printall_noniterable_userdata()
printall(df.item._identity)
expect.eq(0, mock_print.call_count)
end
function test.printall_ipairs_table()
printall_ipairs({'a', 'b'})
expect.eq(2, mock_print.call_count)
expect.true_(mock_print.call_args[1][1]:find('1%s+= a'))
expect.true_(mock_print.call_args[2][1]:find('2%s+= b'))
end
function test.printall_ipairs_string()
printall_ipairs('a')
expect.eq(0, mock_print.call_count)
end
function test.printall_ipairs_number()
printall_ipairs(10)
expect.eq(0, mock_print.call_count)
end
function test.printall_ipairs_nil()
printall_ipairs(nil)
expect.eq(0, mock_print.call_count)
end
function test.printall_ipairs_boolean()
printall_ipairs(false)
expect.eq(0, mock_print.call_count)
end
function test.printall_ipairs_function()
printall_ipairs(function() end)
expect.eq(0, mock_print.call_count)
end
function test.printall_ipairs_userdata()
local utable = new_int_vector()
dfhack.with_temp_object(utable, function()
utable:insert(0, 10)
utable:insert(1, 20)
printall_ipairs(utable)
expect.eq(2, mock_print.call_count)
expect.true_(mock_print.call_args[1][1]:find('0%s+= 10'))
expect.true_(mock_print.call_args[2][1]:find('1%s+= 20'))
end)
end
function test.printall_ipairs_noniterable_userdata()
printall_ipairs(df.item._identity)
expect.eq(0, mock_print.call_count)
end
local function validate_patterns(start_idx, patterns)
for i,pattern in ipairs(patterns) do
expect.true_(mock_print.call_args[start_idx+i-1][1]:find(pattern))
end
return start_idx + #patterns
end
function test.printall_recurse()
local udatatable = new_int_vector()
dfhack.with_temp_object(udatatable, function()
local udataint = df.new('uint32_t')
dfhack.with_temp_object(udataint, function ()
udatatable:insert(0, 10)
udatatable:insert(1, 20)
udatatable:insert(2, 20)
udatatable:insert(3, 20)
udatatable:insert(4, 0)
udatatable:insert(5, 0)
local t2 = {}
local t = {num=5,
bool=false,
fn=function() end,
udatatable=udatatable,
udataint=udataint,
lightudata=df.item._identity,
table=t2}
t2.cyclic = t
printall_recurse(t)
expect.eq(55, mock_print.call_count)
expect.true_(mock_print.call_args[1][1]:find('^table: '))
local idx = 2
local EQ = '^%s+= $'
while idx <= 55 do
expect.eq('', mock_print.call_args[idx][1])
idx = idx + 1
local str = mock_print.call_args[idx][1]
if str:startswith('num') then
idx = validate_patterns(idx, {'^num$', EQ, '^5$'})
elseif str:startswith('bool') then
idx = validate_patterns(idx, {'^bool$', EQ, '^false$'})
elseif str:startswith('fn') then
idx = validate_patterns(idx, {'^fn$', EQ, '^function: '})
elseif str:startswith('udatatable') then
idx = validate_patterns(idx,
{'^udatatable$', EQ, '^<vector<int32_t>%[6%]: ',
'%s+', '^0$', EQ, '^10$',
'%s+', '^1$', EQ, '^20$',
'Repeated 2 times',
'%s+', '^4$', EQ, '^0$',
'Repeated 1 times'}) -- [sic]
elseif str:startswith('udataint') then
idx = validate_patterns(idx,
{'^udataint$', EQ, '^<uint32_t: ',
'%s+', '^value$', EQ, '^0$'})
elseif str:startswith('lightudata') then
idx = validate_patterns(idx,
{'^lightudata$', EQ, '',
'', 'iteration with pairs>$'})
elseif str:startswith('table') then
idx = validate_patterns(idx,
{'^table$', EQ, '^table: ',
'%s+', '^cyclic$', EQ, '^table: ',
'%s+', '^<Cyclic reference'})
else
expect.fail('unhandled print output')
break
end
end
end)
end)
end
function test.printall_recurse_cyclic_userdata()
local t = df.job_list_link:new()
dfhack.with_temp_object(t, function()
t.next = t
printall_recurse(t)
expect.eq(15, mock_print.call_count)
-- conveniently, field order is deterministic
validate_patterns(1,
{'^<job_list_link: ',
'', '^item$', EQ, 'nil',
'', '^prev$', EQ, 'nil',
'', '^next$', EQ, '^<job_list_link: ',
' +', '^<Cyclic reference'})
end)
end