1
0
Fork 0
knot-resolver/daemon/lua/krprint.lua
Daniel Baumann fbc604e215
Adding upstream version 5.7.5.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-21 13:56:17 +02:00

340 lines
9.1 KiB
Lua

-- SPDX-License-Identifier: GPL-3.0-or-later
local base_class = {
cur_indent = 0,
}
-- shared constructor: use as serializer_class:new()
function base_class.new(class, on_unrepresentable)
on_unrepresentable = on_unrepresentable or 'comment'
if on_unrepresentable ~= 'comment'
and on_unrepresentable ~= 'error' then
error('unsupported val2expr on_unrepresentable option '
.. tostring(on_unrepresentable))
end
local inst = {}
inst.on_unrepresentable = on_unrepresentable
inst.done = {}
inst.tab_key_path = {}
setmetatable(inst, class.__inst_mt)
return inst
end
-- format comment with leading/ending whitespace if needed
function base_class.format_note(_, note, ws_prefix, ws_suffix)
if note == nil then
return ''
else
return string.format('%s--[[ %s ]]%s',
ws_prefix or '', note, ws_suffix or '')
end
end
function base_class.indent_head(self)
return string.rep(' ', self.cur_indent)
end
function base_class.indent_inc(self)
self.cur_indent = self.cur_indent + self.indent_step
end
function base_class.indent_dec(self)
self.cur_indent = self.cur_indent - self.indent_step
end
function base_class._fallback(self, val)
if self.on_unrepresentable == 'comment' then
return 'nil', string.format('missing %s', val)
elseif self.on_unrepresentable == 'error' then
local key_path_msg
if #self.tab_key_path > 0 then
local str_key_path = {}
for _, key in ipairs(self.tab_key_path) do
table.insert(str_key_path,
string.format('%s %s', type(key), self:string(tostring(key))))
end
local key_path = '[' .. table.concat(str_key_path, '][') .. ']'
key_path_msg = string.format(' (found at [%s])', key_path)
else
key_path_msg = ''
end
error(string.format('cannot serialize type %s%s', type(val), key_path_msg), 2)
end
end
function base_class.val2expr(self, val)
local val_type = type(val)
local val_repr = self[val_type]
if val_repr then
return val_repr(self, val)
else
return self:_fallback(val)
end
end
-- "nil" is a Lua keyword so assignment below is workaround to create
-- function base_class.nil(self, val)
base_class['nil'] = function(_, val)
assert(type(val) == 'nil')
return 'nil'
end
function base_class.number(_, val)
assert(type(val) == 'number')
if val == math.huge then
return 'math.huge'
elseif val == -math.huge then
return '-math.huge'
elseif tostring(val) == 'nan' then
return 'tonumber(\'nan\')'
else
return string.format("%.60f", val)
end
end
function base_class.char_is_printable(_, c)
-- ASCII (from space to ~) and not ' or \
return (c >= 0x20 and c < 0x7f)
and c ~= 0x27 and c ~= 0x5C
end
function base_class.string(self, val)
assert(type(val) == 'string')
local chars = {'\''}
for i = 1, #val do
local c = string.byte(val, i)
if self:char_is_printable(c) then
table.insert(chars, string.char(c))
else
table.insert(chars, string.format('\\%03d', c))
end
end
table.insert(chars, '\'')
return table.concat(chars)
end
function base_class.boolean(_, val)
assert(type(val) == 'boolean')
return tostring(val)
end
local function ordered_iter(unordered_tt)
local keys = {}
for k in pairs(unordered_tt) do
table.insert(keys, k)
end
table.sort(keys,
function (a, b)
if type(a) ~= type(b) then
return type(a) < type(b)
end
if type(a) == 'number' then
return a < b
else
return tostring(a) < tostring(b)
end
end)
local i = 0
return function()
i = i + 1
if keys[i] ~= nil then
return keys[i], unordered_tt[keys[i]]
end
end
end
function base_class.table(self, tab)
assert(type(tab) == 'table')
if self.done[tab] then
error('cyclic reference', 0)
end
self.done[tab] = true
local items = {'{'}
local previdx = 0
self:indent_inc()
for idx, val in ordered_iter(tab) do
local errors, valok, valexpr, valnote, idxok, idxexpr, idxnote
errors = {}
-- push current index onto key path stack to make it available to sub-printers
table.insert(self.tab_key_path, idx)
valok, valexpr, valnote = pcall(self.val2expr, self, val)
if not valok then
table.insert(errors, string.format('value: %s', valexpr))
end
local addidx
if previdx and type(idx) == 'number' and idx - 1 == previdx then
-- monotonic sequence, do not print key
previdx = idx
addidx = false
else
-- end of monotonic sequence
-- from now on print keys as well
previdx = nil
addidx = true
end
if addidx then
idxok, idxexpr, idxnote = pcall(self.val2expr, self, idx)
if not idxok or idxexpr == 'nil' then
table.insert(errors, string.format('key: not serializable', idxexpr))
end
end
local item = ''
if #errors == 0 then
-- finally serialize one [key=]?value expression
local indent = self:indent_head()
local note
if addidx then
note = self:format_note(idxnote, nil, self.key_val_sep)
item = string.format('%s%s[%s]%s=%s',
indent, note,
idxexpr, self.key_val_sep, self.key_val_sep)
indent = ''
end
note = self:format_note(valnote, nil, self.item_sep)
item = item .. string.format('%s%s%s,', indent, note, valexpr)
else
local errmsg = string.format('cannot print %s = %s (%s)',
self:string(tostring(idx)),
self:string(tostring(val)),
table.concat(errors, ', '))
if self.on_unrepresentable == 'error' then
error(errmsg, 0)
else
errmsg = string.format('--[[ missing %s ]]', errmsg)
item = errmsg
end
end
table.insert(items, item)
table.remove(self.tab_key_path) -- pop current index from key path stack
end -- one key+value
self:indent_dec()
table.insert(items, self:indent_head() .. '}')
return table.concat(items, self.item_sep), string.format('%s follows', tab)
end
-- machine readable variant, cannot represent all types and repeated references to a table
local serializer_class = {
indent_step = 0,
item_sep = ' ',
key_val_sep = ' ',
__inst_mt = {}
}
-- inheritance form base class (for :new())
setmetatable(serializer_class, { __index = base_class })
-- class instances with following metatable inherit all class members
serializer_class.__inst_mt.__index = serializer_class
local function static_serializer(val, on_unrepresentable)
local inst = serializer_class:new(on_unrepresentable)
local expr, note = inst:val2expr(val)
return string.format('%s%s', inst:format_note(note, nil, inst.item_sep), expr)
end
-- human friendly variant, not stable and not intended for machine consumption
local pprinter_class = {
indent_step = 4,
item_sep = '\n',
key_val_sep = ' ',
__inst_mt = {},
}
-- should be always empty because pretty-printer has fallback for all types
function pprinter_class.format_note()
return ''
end
function pprinter_class._fallback(self, val)
if self.on_unrepresentable == 'error' then
base_class._fallback(self, val)
end
return tostring(val)
end
function pprinter_class.char_is_printable(_, c)
-- ASCII (from space to ~) + tab or newline
-- and not ' or \
return ((c >= 0x20 and c < 0x7f)
or c == 0x09 or c == 0x0A)
and c ~= 0x27 and c ~= 0x5C
end
-- "function" is a Lua keyword so assignment below is workaround to create
-- function pprinter_class.function(self, f)
pprinter_class['function'] = function(self, f)
-- thanks to AnandA777 from StackOverflow! Function funcsign is adapted version of
-- https://stackoverflow.com/questions/51095022/inspect-function-signature-in-lua-5-1
assert(type(f) == 'function', "bad argument #1 to 'funcsign' (function expected)")
local debuginfo = debug.getinfo(f)
local func_args = {}
local args_str
if debuginfo.what == 'C' then -- names N/A
args_str = '(?)'
goto add_name
end
pcall(function()
local oldhook
local delay = 2
local function hook()
delay = delay - 1
if delay == 0 then -- call this only for the introspected function
-- stack depth 2 is the introspected function
for i = 1, debuginfo.nparams do
local k = debug.getlocal(2, i)
table.insert(func_args, k)
end
if debuginfo.isvararg then
table.insert(func_args, "...")
end
debug.sethook(oldhook)
error('aborting the call to introspected function')
end
end
oldhook = debug.sethook(hook, "c") -- invoke hook() on function call
f(unpack({})) -- huh?
end)
args_str = "(" .. table.concat(func_args, ", ") .. ")"
::add_name::
local name
if #self.tab_key_path > 0 then
name = string.format('function %s', self.tab_key_path[#self.tab_key_path])
else
name = 'function '
end
return string.format('%s%s: %s', name, args_str, string.sub(tostring(f), 11))
end
-- default tostring method is better suited for human-intended output
function pprinter_class.number(_, number)
return tostring(number)
end
local function deserialize_lua(serial)
assert(type(serial) == 'string')
local deserial_func = loadstring('return ' .. serial)
if type(deserial_func) ~= 'function' then
panic('input is not a valid Lua expression')
end
return deserial_func()
end
setmetatable(pprinter_class, { __index = base_class })
pprinter_class.__inst_mt.__index = pprinter_class
local function static_pprint(val, on_unrepresentable)
local inst = pprinter_class:new(on_unrepresentable)
local expr, note = inst:val2expr(val)
return string.format('%s%s', inst:format_note(note, nil, inst.item_sep), expr)
end
local M = {
serialize_lua = static_serializer,
deserialize_lua = deserialize_lua,
pprint = static_pprint
}
return M