[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Monkey Patching is Bad, Unless Really Needed (was Re: A guide to building Lua modules)
- From: Philipp Janda <siffiejoe@...>
- Date: Wed, 16 Apr 2014 19:50:02 +0200
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