lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Wed, Sep 16, 2009 at 9:22 AM, Luiz Henrique de Figueiredo wrote:
>> You do need to increase the stack level, but I think that David meant
>> some method to make the extra stack level(s) invisible to the debug API.
>
> This can be done in the traceback function itself, can't it?
> I mean, just scan backward and skip your "hidden" functions.
> Of course, you'll need a way to mark those: add them to a table or
> name them with a predefined prefix.

The problems with level in the error function have been described
earlier [1].  However, the concern more generally affects the use of
any stack manipulation function (e.g. getfenv, setfenv, error,
debug.getinfo, etc.)  We may redefine these functions in the way you
suggest:

  -- Utility function: identity
  local function identity(...) return ... end

  -- Tells getlevel() to ignore function f.
  local ignore = setmetatable({}, {__mode='k'})
  function ignorelevel(f)
    f = f or debug.getinfo(2,'f').func
    ignore[f] = true
    return f
  end

  -- Calls f with given parameters in a way that getlevel()
  -- ignores it.
  function ignorecall(f, ...)
    return identity(f(...)) -- prevent tail call
  end
  ignorelevel(ignorecall)

  -- Like debug.getinfo(n,'f').func, but skips ignored functions.
  function getlevel(n)
    local fc
    local j=1
    for i=1,n do
      repeat
        fc = debug.getinfo(1+j,'f').func
        if fc == ignorecall then j=j+1 end -- ignore caller too
        j=j+1
      until not ignore[fc]
    end
    return fc
  end

  -- Redefine getfenv with getlevel behavior.
  local getfenv_old = getfenv
  function getfenv(f)
    if type(f) == 'number' and f > 0 then
      f = getlevel(f+1)
    end
    return getfenv_old(f)
  end

  ---- Test

  -- Some arbitrary function that deals with stack levels.
  local function import(x)
    getfenv(2)[x] = {}
  end

  -- Some code wrap the above function (two alternate ways)
  local function tuple(...)
    return {n=select('#',...), ...}
  end
  local function trace1(f)
    return ignorelevel(function(...)
      print'begin'
      local t = tuple(f(...))
      print'end'
      return unpack(t, 1, t.n)
    end)
  end
  local function trace2(f)
    return function(...)
      print'begin'
      local t = tuple(ignorecall(f,...))
      print'end'
      return unpack(t, 1, t.n)
    end
  end
  local import1 = trace1(import)
  local import2 = trace2(import)

  -- Use it.
  setfenv(1,{_G=_G})
  import1 'x'
  import2 'y'
  _G.assert(x)
  _G.assert(y)
  _G.print 'done'


[1] http://lua-users.org/wiki/LuaCarp