batteries/assert.lua

119 lines
2.5 KiB
Lua

--[[
various intuitive assertions that
- avoid garbage generation upon success,
- build nice formatted error messages
- post one level above the call site by default
- return their first argument so they can be used inline
default call is builtin global assert
can call nop() to dummy out everything for "release mode"
(if you're worried about that sort of thing)
]]
local _assert = assert
--proxy calls to global assert
local assert = setmetatable({}, {
__call = function(self, ...)
return _assert(...)
end,
})
local function _extra(msg)
if not msg then
return ""
end
return "\n\n\t(note: " .. msg .. ")"
end
--assert a value is not nil
--return the value, so this can be chained
function assert:some(v, msg, stack_level)
if v == nil then
error(("assertion failed: value is nil %s"):format(
_extra(msg)
), 2 + (stack_level or 0))
end
return v
end
--assert two values are equal
function assert:equal(a, b, msg, stack_level)
if a ~= b then
error(("assertion failed: %s is not equal to %s %s"):format(
tostring(a),
tostring(b),
_extra(msg)
), 2 + (stack_level or 0))
end
return a
end
--assert two values are not equal
function assert:not_equal(a, b, msg, stack_level)
if a == b then
error(("assertion failed: values are equal %s"):format(
_extra(msg)
), 2 + (stack_level or 0))
end
return a
end
--assert a value is of a certain type
function assert:type(a, t, msg, stack_level)
if type(a) ~= t then
error(("assertion failed: %s (%s) not of type %s %s"):format(
tostring(a),
type(a),
tostring(t),
_extra(msg)
), 2 + (stack_level or 0))
end
return a
end
--assert a value is nil or a certain type.
-- useful for optional parameters.
function assert:type_or_nil(a, t, msg, stack_level)
if a ~= nil then
assert:type(a, t, msg, stack_level + 1)
end
return a
end
--assert a value is one of those in a table of options
function assert:one_of(a, t, msg, stack_level)
for _, value in ipairs(t) do
if value == a then
return a
end
end
local values = {}
for index = 1, #t do
values[index] = tostring(t[index])
end
error(("assertion failed: %s not one of %s %s"):format(
tostring(a),
table.concat(values, ", "),
_extra(msg)
), 2 + (stack_level or 0))
end
--replace everything in assert with nop functions that just return their second argument, for near-zero overhead on release
function assert:nop()
local nop = function(_, a)
return a
end
setmetatable(self, {
__call = nop,
})
for k, v in pairs(self) do
self[k] = nop
end
end
return assert