Include offending type in precondition failure msg

Output the type that was incorrect to make it more obvious what the user
is doing wrong without them adding logging.

To avoid string building during lots of vector math, introduced a
private precond module that only builds the strings when an error
occurs. It uses error() to put the resulting error message at the caller
to cpml.

Before:

  lua: ~/cpml/modules/vec2.lua:52: new: Wrong argument type for x (<number> expected)
  lua: ~/cpml/modules/vec2.lua:424: __add: Wrong argument type for right hand operand. (<cpml.vec2> expected)

  example stack traceback:
  	[C]: in function 'assert'
  	~/cpml/modules/vec2.lua:424: in metamethod '__add'
  	test_cpml.lua:32: in main chunk
  	[C]: in ?

After:

  lua: test_cpml.lua:31: new: Wrong argument type for x: string (<number> expected)
  lua: test_cpml.lua:32: __add: Wrong argument type 'string' for right hand operand. (<cpml.vec2> expected)

  example stack traceback:
  	[C]: in function 'error'
  	~/cpml/modules/_private_precond.lua:13: in function 'modules._private_precond.assert'
  	~/cpml/modules/vec2.lua:425: in metamethod '__add'
  	test_cpml.lua:32: in main chunk
  	[C]: in ?

The tracebacks are longer, but the initial error is at the location of
the mistake and the output includes the input type.
This commit is contained in:
David Briscoe 2022-03-26 17:16:09 -07:00
parent c50d292cb6
commit c26cc1a508
6 changed files with 70 additions and 48 deletions

View File

@ -0,0 +1,17 @@
-- Preconditions for cpml functions.
local precond = {}
function precond.typeof(t, expected, msg)
if type(t) ~= expected then
error(("%s: %s (<%s> expected)"):format(msg, type(t), expected), 3)
end
end
function precond.assert(cond, msg, ...)
if not cond then
error(msg:format(...), 3)
end
end
return precond

View File

@ -3,6 +3,7 @@
local modules = (...):gsub('%.[^%.]+$', '') .. "."
local utils = require(modules .. "utils")
local precond = require(modules .. "_private_precond")
local color = {}
local color_mt = {}
@ -103,20 +104,20 @@ end
function color.new(r, g, b, a)
-- number, number, number, number
if r and g and b and a then
assert(type(r) == "number", "new: Wrong argument type for r (<number> expected)")
assert(type(g) == "number", "new: Wrong argument type for g (<number> expected)")
assert(type(b) == "number", "new: Wrong argument type for b (<number> expected)")
assert(type(a) == "number", "new: Wrong argument type for a (<number> expected)")
precond.typeof(r, "number", "new: Wrong argument type for r")
precond.typeof(g, "number", "new: Wrong argument type for g")
precond.typeof(b, "number", "new: Wrong argument type for b")
precond.typeof(a, "number", "new: Wrong argument type for a")
return new(r, g, b, a)
-- {r, g, b, a}
elseif type(r) == "table" then
local rr, gg, bb, aa = r[1], r[2], r[3], r[4]
assert(type(rr) == "number", "new: Wrong argument type for r (<number> expected)")
assert(type(gg) == "number", "new: Wrong argument type for g (<number> expected)")
assert(type(bb) == "number", "new: Wrong argument type for b (<number> expected)")
assert(type(aa) == "number", "new: Wrong argument type for a (<number> expected)")
precond.typeof(rr, "number", "new: Wrong argument type for r")
precond.typeof(gg, "number", "new: Wrong argument type for g")
precond.typeof(bb, "number", "new: Wrong argument type for b")
precond.typeof(aa, "number", "new: Wrong argument type for a")
return new(rr, gg, bb, aa)
end

View File

@ -6,6 +6,7 @@ local vec2 = require(modules .. "vec2")
local vec3 = require(modules .. "vec3")
local quat = require(modules .. "quat")
local utils = require(modules .. "utils")
local precond = require(modules .. "_private_precond")
local private = require(modules .. "_private_utils")
local sqrt = math.sqrt
local cos = math.cos
@ -861,7 +862,7 @@ function mat4_mt.__eq(a, b)
end
function mat4_mt.__mul(a, b)
assert(mat4.is_mat4(a), "__mul: Wrong argument type for left hand operand. (<cpml.mat4> expected)")
precond.assert(mat4.is_mat4(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.mat4> expected)", type(a))
if vec3.is_vec3(b) then
return vec3(mat4.mul_vec4({}, a, { b.x, b.y, b.z, 1 }))

View File

@ -4,6 +4,7 @@
local modules = (...):gsub('%.[^%.]+$', '') .. "."
local constants = require(modules .. "constants")
local vec3 = require(modules .. "vec3")
local precond = require(modules .. "_private_precond")
local private = require(modules .. "_private_utils")
local DOT_THRESHOLD = constants.DOT_THRESHOLD
local DBL_EPSILON = constants.DBL_EPSILON
@ -58,20 +59,20 @@ quat.zero = new(0, 0, 0, 0)
function quat.new(x, y, z, w)
-- number, number, number, number
if x and y and z and w then
assert(type(x) == "number", "new: Wrong argument type for x (<number> expected)")
assert(type(y) == "number", "new: Wrong argument type for y (<number> expected)")
assert(type(z) == "number", "new: Wrong argument type for z (<number> expected)")
assert(type(w) == "number", "new: Wrong argument type for w (<number> expected)")
precond.typeof(x, "number", "new: Wrong argument type for x")
precond.typeof(y, "number", "new: Wrong argument type for y")
precond.typeof(z, "number", "new: Wrong argument type for z")
precond.typeof(w, "number", "new: Wrong argument type for w")
return new(x, y, z, w)
-- {x, y, z, w} or {x=x, y=y, z=z, w=w}
elseif type(x) == "table" then
local xx, yy, zz, ww = x.x or x[1], x.y or x[2], x.z or x[3], x.w or x[4]
assert(type(xx) == "number", "new: Wrong argument type for x (<number> expected)")
assert(type(yy) == "number", "new: Wrong argument type for y (<number> expected)")
assert(type(zz) == "number", "new: Wrong argument type for z (<number> expected)")
assert(type(ww) == "number", "new: Wrong argument type for w (<number> expected)")
precond.typeof(xx, "number", "new: Wrong argument type for x")
precond.typeof(yy, "number", "new: Wrong argument type for y")
precond.typeof(zz, "number", "new: Wrong argument type for z")
precond.typeof(ww, "number", "new: Wrong argument type for w")
return new(xx, yy, zz, ww)
end
@ -456,19 +457,19 @@ function quat_mt.__eq(a,b)
end
function quat_mt.__add(a, b)
assert(quat.is_quat(a), "__add: Wrong argument type for left hand operand. (<cpml.quat> expected)")
assert(quat.is_quat(b), "__add: Wrong argument type for right hand operand. (<cpml.quat> expected)")
precond.assert(quat.is_quat(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
precond.assert(quat.is_quat(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.quat> expected)", type(b))
return a:add(b)
end
function quat_mt.__sub(a, b)
assert(quat.is_quat(a), "__sub: Wrong argument type for left hand operand. (<cpml.quat> expected)")
assert(quat.is_quat(b), "__sub: Wrong argument type for right hand operand. (<cpml.quat> expected)")
precond.assert(quat.is_quat(a), "__sub: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
precond.assert(quat.is_quat(b), "__sub: Wrong argument type '%s' for right hand operand. (<cpml.quat> expected)", type(b))
return a:sub(b)
end
function quat_mt.__mul(a, b)
assert(quat.is_quat(a), "__mul: Wrong argument type for left hand operand. (<cpml.quat> expected)")
precond.assert(quat.is_quat(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
assert(quat.is_quat(b) or vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operand. (<cpml.quat> or <cpml.vec3> or <number> expected)")
if quat.is_quat(b) then
@ -483,8 +484,8 @@ function quat_mt.__mul(a, b)
end
function quat_mt.__pow(a, n)
assert(quat.is_quat(a), "__pow: Wrong argument type for left hand operand. (<cpml.quat> expected)")
assert(type(n) == "number", "__pow: Wrong argument type for right hand operand. (<number> expected)")
precond.assert(quat.is_quat(a), "__pow: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
precond.typeof(n, "number", "__pow: Wrong argument type for right hand operand.")
return a:pow(n)
end

View File

@ -3,6 +3,7 @@
local modules = (...):gsub('%.[^%.]+$', '') .. "."
local vec3 = require(modules .. "vec3")
local precond = require(modules .. "_private_precond")
local private = require(modules .. "_private_utils")
local acos = math.acos
local atan2 = math.atan2
@ -49,16 +50,16 @@ vec2.zero = new(0, 0)
function vec2.new(x, y)
-- number, number
if x and y then
assert(type(x) == "number", "new: Wrong argument type for x (<number> expected)")
assert(type(y) == "number", "new: Wrong argument type for y (<number> expected)")
precond.typeof(x, "number", "new: Wrong argument type for x")
precond.typeof(y, "number", "new: Wrong argument type for y")
return new(x, y)
-- {x, y} or {x=x, y=y}
elseif type(x) == "table" or type(x) == "cdata" then -- table in vanilla lua, cdata in luajit
local xx, yy = x.x or x[1], x.y or x[2]
assert(type(xx) == "number", "new: Wrong argument type for x (<number> expected)")
assert(type(yy) == "number", "new: Wrong argument type for y (<number> expected)")
precond.typeof(xx, "number", "new: Wrong argument type for x")
precond.typeof(yy, "number", "new: Wrong argument type for y")
return new(xx, yy)
@ -401,19 +402,19 @@ function vec2_mt.__eq(a, b)
end
function vec2_mt.__add(a, b)
assert(vec2.is_vec2(a), "__add: Wrong argument type for left hand operand. (<cpml.vec2> expected)")
assert(vec2.is_vec2(b), "__add: Wrong argument type for right hand operand. (<cpml.vec2> expected)")
precond.assert(vec2.is_vec2(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
precond.assert(vec2.is_vec2(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec2> expected)", type(b))
return a:add(b)
end
function vec2_mt.__sub(a, b)
assert(vec2.is_vec2(a), "__add: Wrong argument type for left hand operand. (<cpml.vec2> expected)")
assert(vec2.is_vec2(b), "__add: Wrong argument type for right hand operand. (<cpml.vec2> expected)")
precond.assert(vec2.is_vec2(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
precond.assert(vec2.is_vec2(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec2> expected)", type(b))
return a:sub(b)
end
function vec2_mt.__mul(a, b)
assert(vec2.is_vec2(a), "__mul: Wrong argument type for left hand operand. (<cpml.vec2> expected)")
precond.assert(vec2.is_vec2(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
assert(vec2.is_vec2(b) or type(b) == "number", "__mul: Wrong argument type for right hand operand. (<cpml.vec2> or <number> expected)")
if vec2.is_vec2(b) then
@ -424,7 +425,7 @@ function vec2_mt.__mul(a, b)
end
function vec2_mt.__div(a, b)
assert(vec2.is_vec2(a), "__div: Wrong argument type for left hand operand. (<cpml.vec2> expected)")
precond.assert(vec2.is_vec2(a), "__div: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
assert(vec2.is_vec2(b) or type(b) == "number", "__div: Wrong argument type for right hand operand. (<cpml.vec2> or <number> expected)")
if vec2.is_vec2(b) then

View File

@ -2,6 +2,7 @@
-- @module vec3
local modules = (...):gsub('%.[^%.]+$', '') .. "."
local precond = require(modules .. "_private_precond")
local private = require(modules .. "_private_utils")
local sqrt = math.sqrt
local cos = math.cos
@ -50,18 +51,18 @@ vec3.zero = new(0, 0, 0)
function vec3.new(x, y, z)
-- number, number, number
if x and y and z then
assert(type(x) == "number", "new: Wrong argument type for x (<number> expected)")
assert(type(y) == "number", "new: Wrong argument type for y (<number> expected)")
assert(type(z) == "number", "new: Wrong argument type for z (<number> expected)")
precond.typeof(x, "number", "new: Wrong argument type for x")
precond.typeof(y, "number", "new: Wrong argument type for y")
precond.typeof(z, "number", "new: Wrong argument type for z")
return new(x, y, z)
-- {x, y, z} or {x=x, y=y, z=z}
elseif type(x) == "table" or type(x) == "cdata" then -- table in vanilla lua, cdata in luajit
local xx, yy, zz = x.x or x[1], x.y or x[2], x.z or x[3]
assert(type(xx) == "number", "new: Wrong argument type for x (<number> expected)")
assert(type(yy) == "number", "new: Wrong argument type for y (<number> expected)")
assert(type(zz) == "number", "new: Wrong argument type for z (<number> expected)")
precond.typeof(xx, "number", "new: Wrong argument type for x")
precond.typeof(yy, "number", "new: Wrong argument type for y")
precond.typeof(zz, "number", "new: Wrong argument type for z")
return new(xx, yy, zz)
@ -374,20 +375,20 @@ function vec3_mt.__eq(a, b)
end
function vec3_mt.__add(a, b)
assert(vec3.is_vec3(a), "__add: Wrong argument type for left hand operand. (<cpml.vec3> expected)")
assert(vec3.is_vec3(b), "__add: Wrong argument type for right hand operand. (<cpml.vec3> expected)")
precond.assert(vec3.is_vec3(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
precond.assert(vec3.is_vec3(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)", type(b))
return a:add(b)
end
function vec3_mt.__sub(a, b)
assert(vec3.is_vec3(a), "__sub: Wrong argument type for left hand operand. (<cpml.vec3> expected)")
assert(vec3.is_vec3(b), "__sub: Wrong argument type for right hand operand. (<cpml.vec3> expected)")
precond.assert(vec3.is_vec3(a), "__sub: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
precond.assert(vec3.is_vec3(b), "__sub: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)", type(b))
return a:sub(b)
end
function vec3_mt.__mul(a, b)
assert(vec3.is_vec3(a), "__mul: Wrong argument type for left hand operand. (<cpml.vec3> expected)")
assert(vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operand. (<cpml.vec3> or <number> expected)")
precond.assert(vec3.is_vec3(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
precond.assert(vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type '%s' for right hand operand. (<cpml.vec3> or <number> expected)", type(b))
if vec3.is_vec3(b) then
return a:mul(b)
@ -397,8 +398,8 @@ function vec3_mt.__mul(a, b)
end
function vec3_mt.__div(a, b)
assert(vec3.is_vec3(a), "__div: Wrong argument type for left hand operand. (<cpml.vec3> expected)")
assert(vec3.is_vec3(b) or type(b) == "number", "__div: Wrong argument type for right hand operand. (<cpml.vec3> or <number> expected)")
precond.assert(vec3.is_vec3(a), "__div: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
precond.assert(vec3.is_vec3(b) or type(b) == "number", "__div: Wrong argument type '%s' for right hand operand. (<cpml.vec3> or <number> expected)", type(b))
if vec3.is_vec3(b) then
return a:div(b)