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 2:16 am GMT (diff)