1
0
errors/errors.lua
Jon Michael Aanes 32c88386f3
Some checks failed
Lua Library / Lua-Testing (push) Failing after 7s
Lua Library / Static-Analysis (push) Successful in 4s
Improved error reporting on incorrect arguments
2024-05-05 20:13:53 +02:00

216 lines
7.9 KiB
Lua

-- Check for possible loading errors
local string_dist
do
local thispath = ... and (...):match('.+%.') or ''
local function import (name, ignore_failure)
local was_loaded, lib_or_error = pcall(require, thispath..name)
if not was_loaded then
if ignore_failure then return nil end
error('['..thispath..']: Could not load vital library: '..name..'.lua:\n\t'..lib_or_error)
end
return lib_or_error
end
string_dist = import 'string_distance'
end
if not debug then
error('[errors/loading]: Expected the debug library to be available in _G.')
elseif not debug.getinfo then
error('[errors/loading]: Expected debug.getinfo to be available in the debug library.')
end
--------------------------------------------------------------------------------
-- Constants
local DEFAULT_ERROR_MSG_NORMAL = 'Unknown error occured'
local DEFAULT_ERROR_MSG_INTERNAL = 'Unknown internal error occured'
local DEFAULT_ERROR_MSG_CORRECT = 'Unexpected input "%s", maybe you meant %s?'
--------------------------------------------------------------------------------
-- Util
local function format_probable_strings (probable_strings, amount)
assert(type(probable_strings) == 'table')
assert(#probable_strings > 0)
assert(type(amount) == 'number')
local l = {}
for i = 1, math.min(amount, #probable_strings) do
l[#l+1] = probable_strings[i]
l[#l+1] = ', '
end
if l[#l] == ', ' then l[#l] = nil end
if #l >= 3 then l[#l-1] = ' or ' end
return table.concat(l, '')
end
--------------------------------------------------------------------------------
local function internal_error (self, module_suffix, format_msg, ...)
-- internal_error(error_handler, module_suffix [, format_msg, ...])
-- Error check
assert(type(self) == 'table' and self.is_error_handler)
assert(type(module_suffix) == 'string')
format_msg = format_msg or DEFAULT_ERROR_MSG_NORMAL
assert(type(format_msg) == 'string')
-- Format
error(('[%s%s]: '..format_msg):format(self.module_name, module_suffix, ...), 3)
end
local function external_error (self, module_suffix, format_msg, ...)
-- external_error(error_handler, module_suffix [, format_msg, ...])
-- Error check
assert(type(self) == 'table' and self.is_error_handler)
assert(type(module_suffix) == 'string')
format_msg = format_msg or DEFAULT_ERROR_MSG_INTERNAL
assert(type(format_msg) == 'string')
-- Find error level
local level = 3
while self.registered[debug.getinfo(level, 'f').func] do
level = level + 1
end
-- Format
error(('[%s%s]: '..format_msg):format(self.module_name, module_suffix, ...), 1)
end
local function correct_error (self, module_suffix, format_msg, gotten_string, possible_strings, lvl)
-- correct_error(error_handler, module_suffix [, format_msg], gotten_string, possible_strings)
-- Error check
assert(type(self) == 'table' and self.is_error_handler)
assert(type(module_suffix) == 'string')
format_msg = format_msg or DEFAULT_ERROR_MSG_CORRECT
lvl = lvl or 0
assert(type(format_msg) == 'string', 'Internal errors error: incorrect type for argument format_msg')
assert(type(gotten_string) == 'string', 'Internal errors error: incorrect type for argument gotten_string')
assert(type(possible_strings) == 'table', 'Internal errors error: incorrect type for argument possible_strings')
assert(type(lvl) == 'number', 'Internal errors error: incorrect type for argument lvl')
-- Do stuff
possible_strings = string_dist.strings_with_highest_similarity(gotten_string, possible_strings)
local list_string = #possible_strings > 0 and format_probable_strings(possible_strings, 3) or ""
-- Format
error(('[%s%s]: '..format_msg):format(self.module_name, module_suffix, gotten_string, list_string), lvl + 1)
end
----
local function handle_index_error (err_hdl, t, key)
--
assert(type(err_hdl) == 'table')
assert(type(t) == 'table')
-- Find keys
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
-- Is empty table?
if #keys == 0 then
external_error(err_hdl, '', 'Cannot index unknown key %s in empty table.', key)
end
-- Is sequence?
if #keys == #t then
if type(key) ~= 'number' then
external_error(err_hdl, '', 'Cannot index %s %s, for sequence with length %i.', type(key), key, #t)
elseif key == math.floor(key) then -- Integer
external_error(err_hdl, '', 'Cannot index integer %i, for sequence with length %i.', key, #t)
elseif key ~= math.floor(key) then -- Non-Integer
local format_code = (key == tonumber(tostring(key))) and '%s' or '%f'
external_error(err_hdl, '', 'Cannot index non-integer '..format_code..', for sequence with length %i.', key, #t)
end
end
-- Is map?
for i = 1, #keys do keys[i] = tostring(keys[i]) end
correct_error(err_hdl, '', 'Cannot index unknown key %s, maybe you meant %s?', tostring(key), keys)
end
----
local ErrorHandler_mt = {__call = function (self, ...) internal_error(self, '', ...) end}
return function (module_name)
assert(type(module_name) == 'string')
local err_hdl = setmetatable({}, ErrorHandler_mt)
err_hdl.is_error_handler = true
err_hdl.module_name = module_name
err_hdl.registered = {}
err_hdl.any_registered = false
function err_hdl.internal (...)
local args = {...}
if args[1] == err_hdl then table.remove(args, 1) end
internal_error(err_hdl, '/internal', unpack(args))
end
function err_hdl.external (...)
local args = {...}
if args[1] == err_hdl then table.remove(args, 1) end
external_error(err_hdl, '', unpack(args))
end
function err_hdl.correct (...)
local args = {...}
if args[1] == err_hdl then table.remove(args, 1) end
if #args == 2 then table.insert(args, 1, DEFAULT_ERROR_MSG_CORRECT) end
correct_error(err_hdl, '', unpack(args))
end
function err_hdl.info (...)
local args = {...}
if args[1] == err_hdl then table.remove(args, 1) end
assert(type(args[1]) == 'string')
return io.write(('[%s]: '..args[1]..'\n'):format(err_hdl.module_name, unpack(args, 2)))
end
function err_hdl.register (...)
local args = {...}
if args[1] == err_hdl then table.remove(args, 1) end
assert(#args > 0)
for i = 1, #args do
assert(type(args[i]) == 'function')
err_hdl.registered[args[i]] = true
err_hdl.any_registered = true
end
end
function err_hdl.strict_table (...)
local args = {...}
if args[1] == err_hdl then table.remove(args, 1) end
local t = setmetatable(args[1] or {}, {
__index = function (t, k)
return handle_index_error(err_hdl, t, k)
end
})
return t
end
local function keys_of_table (t)
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
table.sort(keys)
return keys
end
function err_hdl.enable_strict_globals ()
setmetatable(getfenv(2), {
__index = function(env, key)
return err_hdl.correct('Attempting to access unknown global: \'%s\'. Perhaps you meant %s?', key, keys_of_table(env), 2)
end,
__newindex = function (env, key, value)
return err_hdl.correct('Attempting to create new global \'%s\' with value: '..tostring(value)..'. Perhaps you misspelled %s?', key, keys_of_table(env), 2)
end
})
end
err_hdl.register(format_probable_strings, internal_error, external_error, correct_error, handle_index_error, err_hdl.internal, err_hdl.external, err_hdl.correct, err_hdl.register, err_hdl.strict_table)
return err_hdl
end