lua-users home
lua-l archive

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


Am 16.04.2014 17:50 schröbte Rena:
I had a thought about monkeypatching global tables:

#!/usr/bin/env lua5.2

do
     local _ENV = setmetatable({}, {__index=_G})

     local smeta = getmetatable('')
     smeta.__index = function(self, key)
         return _ENV._string and _ENV._string[key] or string[key]
     end

     _ENV._string = setmetatable({}, {__index=string})
     _ENV._string.mogrify = function(self) return self:reverse() end

     print(("this is a string"):mogrify())
end

print(("this is a string"):mogrify()) --this shouldn't work

It doesn't work as intended though, since the new __index has _ENV as an
upvalue, so continues to look in it even when it's out of scope. If we
could get the current scope's _ENV then I think it'd work.


You can do that using the `debug` module to search for the innermost `_ENV` variable in scope in the calling function:

    local debug = debug

    local function caller_env()
      -- skip this function and the calling __index function
      local lvl, info = 3, debug.getinfo( 3, "fS" )
      while info do
        if info.what == "C" then
          return _ENV -- caller is C function -> consider almighty
        else
          -- try locals to find _ENV (last occurence counts)
          local found, env = false
          local j, name, value = 1, debug.getlocal( lvl, 1 )
          while name do
            if name == "_ENV" then
              found, env = true, value
            end
            j, name, value = j+1, debug.getlocal( lvl, j+1 )
          end
          if found then return env end
          -- try upvalues to find _ENV
          local f = info.func
          j, name, env = 1, debug.getupvalue( f, 1 )
          while name do
            if name == "_ENV" then
              return env
            end
            j, name, env = j+1, debug.getupvalue( f, j+1 )
          end
        end
        lvl, info = lvl+1, debug.getinfo( lvl+1, "fS" )
      end
      return _ENV -- default (no debug info?)
    end

    getmetatable( "" ).__index = function( self, k )
      return caller_env().string[ k ]
    end


    -- test code
    function string.mogrify()
      return "mogrify 1"
    end

    -- should use the global string.mogrify (mogrify 1)
    print( (""):mogrify() )
    print( ("").gsub )

    local function foo()
      print( (""):mogrify() )
      print( ("").gsub )
    end

    local _ENV = {
      print = print,
      string = {
        mogrify = function() return "mogrify 2" end,
        gsub = "no gsub"
      }
    }
    -- should use string.mogrify of local _ENV (mogrify 2)
    print( (""):mogrify() )
    print( ("").gsub )

    local bar
    do
      local _ENV = {
        print = print,
        string = {
          mogrify = function() return "mogrify 3" end,
          gsub = "still no gsub"
        }
      }
      function bar()
        print( (""):mogrify() )
        print( ("").gsub )
      end
    end
    -- should use string.mogrify of upvalue _ENV (mogrify 3)
    bar()
    -- should use string.mogrify of local _ENV again (mogrify 2)
    print( (""):mogrify() )
    print( ("").gsub )
    -- should use global string.mogrify again (mogrify 1)
    foo()


There are still loop-holes (e.g. when a function has no `_ENV` upvalue but calls a string function using method syntax, and the enclosing scope defines a local `_ENV` after the function definition), but it's close ...

Philipp