Lua Carp

lua-users home
wiki

Lua's [error] function accepts an optional 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.

Solution A

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.

Alternate Solutions

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.

--DavidManura


RecentChanges · preferences
edit · history
Last edited August 9, 2007 3:16 am GMT (diff)