Lua Carp |
|
level
parameter indicating who up the call stack is to be blamed for the error when generating the error message. Now, this is not always scalable since keeping track of the level can be error prone (especially following refactoring), and a function that uses error
may be called at multiple different levels.
The solution below is based on [Perl's Carp] module but adapted to Lua. This provides a croak
function as a replacement for error
. croak
calls error
but before doing so, it determines which level to use by looking up the call stack until the current environment changes.
First, here's the main module:
-- Carp.lua -- This package is based on Perl Carp -- (http://search.cpan.org/~nwclark/perl-5.8.8/lib/Carp.pm) -- David Manura, 2006-09, 2007-07 local M = {} function M.croak(message) local current_env = getfenv(2) local level = 2 while true do local is_success, result = pcall(function() return getfenv(level + 2) end) if is_success then local env = result --print("DEBUG:level", level, env._NAME) if env ~= current_env then --print("DEBUG:found", level, env._NAME) error(message, level) end elseif string.find(result, "(invalid level)") then break end level = level + 1 end end return M
Now let's say you write a module:
-- Calculator.lua -- Create environment for module (needed for Carp) local env = setmetatable({}, {__index = _G}) setfenv(1, env) local M = {} local Carp = require "Carp" function M.calculate3() Carp.croak("calculation failed") return 1 end function M.calculate2() local result = M.calculate3() return result + 1 end function M.calculate() return M.calculate2() end return M
And you write a program that uses the module:
-- example.lua -- This uses the calculator module. local Calc = require "Calculator" local function main() local result = Calc.calculate() print(result) end main()
Here's the output:
lua: example.lua:7: calculation failed stack traceback: [C]: in function 'error' ./Carp.lua:20: in function 'croak' ./Calculator.lua:10: in function 'calculate3' ./Calculator.lua:15: in function <./Calculator.lua:14> (tail call): ? example.lua:7: in function 'main' example.lua:11: in main chunk [C]: ?
Note: this might not work correctly with tail calls. Tail calls will be skipped since Lua does not report an environment for them.
A second, alternate approach mentioned by RiciLake was to write a custom traceback function, the idea being that the traceback function checks the env table at each level and doesn't start producing tracebacks until it changes from the current one.
A third approach mentioned by RiciLake was to simply check the object at stack index 1 in each frame, the idea being that if you were nesting method calls, you'd always have the same self, but that sometimes overshot. There was a desire to in some way to mark the stack, but it wasn't obvious how to do that.
TODO--comments on the merits of these approaches, anyone?
Note: Code is Lua 5.1 (not 5.0) compatible.