-- 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} local errors = {} function errors.new(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 return setmetatable(errors, {__call = function(self, ...) return errors.new(...) end})