summaryrefslogtreecommitdiffstats
path: root/test/lua/compat_env.lua
diff options
context:
space:
mode:
Diffstat (limited to 'test/lua/compat_env.lua')
-rw-r--r--test/lua/compat_env.lua391
1 files changed, 391 insertions, 0 deletions
diff --git a/test/lua/compat_env.lua b/test/lua/compat_env.lua
new file mode 100644
index 0000000..2ecd4b6
--- /dev/null
+++ b/test/lua/compat_env.lua
@@ -0,0 +1,391 @@
+--[[
+
+ compat_env v$(_VERSION) - Lua 5.1/5.2 environment compatibility functions
+
+SYNOPSIS
+
+ -- Get load/loadfile compatibility functions only if using 5.1.
+ local CL = pcall(load, '') and _G or require 'compat_env'
+ local load = CL.load
+ local loadfile = CL.loadfile
+
+ -- The following now works in both Lua 5.1 and 5.2:
+ assert(load('return 2*pi', nil, 't', {pi=math.pi}))()
+ assert(loadfile('ex.lua', 't', {print=print}))()
+
+ -- Get getfenv/setfenv compatibility functions only if using 5.2.
+ local getfenv = _G.getfenv or require 'compat_env'.getfenv
+ local setfenv = _G.setfenv or require 'compat_env'.setfenv
+ local function f() return x end
+ setfenv(f, {x=2})
+ print(x, getfenv(f).x) --> 2, 2
+
+DESCRIPTION
+
+ This module provides Lua 5.1/5.2 environment related compatibility functions.
+ This includes implementations of Lua 5.2 style `load` and `loadfile`
+ for use in Lua 5.1. It also includes Lua 5.1 style `getfenv` and `setfenv`
+ for use in Lua 5.2.
+
+API
+
+ local CL = require 'compat_env'
+
+ CL.load (ld [, source [, mode [, env] ] ]) --> f [, err]
+
+ This behaves the same as the Lua 5.2 `load` in both
+ Lua 5.1 and 5.2.
+ http://www.lua.org/manual/5.2/manual.html#pdf-load
+
+ CL.loadfile ([filename [, mode [, env] ] ]) --> f [, err]
+
+ This behaves the same as the Lua 5.2 `loadfile` in both
+ Lua 5.1 and 5.2.
+ http://www.lua.org/manual/5.2/manual.html#pdf-loadfile
+
+ CL.getfenv ([f]) --> t
+
+ This is identical to the Lua 5.1 `getfenv` in Lua 5.1.
+ This behaves similar to the Lua 5.1 `getfenv` in Lua 5.2.
+ When a global environment is to be returned, or when `f` is a
+ C function, this returns `_G` since Lua 5.2 doesn't have
+ (thread) global and C function environments. This will also
+ return `_G` if the Lua function `f` lacks an `_ENV`
+ upvalue, but it will raise an error if uncertain due to lack of
+ debug info. It is not normally considered good design to use
+ this function; when possible, use `load` or `loadfile` instead.
+ http://www.lua.org/manual/5.1/manual.html#pdf-getfenv
+
+ CL.setfenv (f, t)
+
+ This is identical to the Lua 5.1 `setfenv` in Lua 5.1.
+ This behaves similar to the Lua 5.1 `setfenv` in Lua 5.2.
+ This will do nothing if `f` is a Lua function that
+ lacks an `_ENV` upvalue, but it will raise an error if uncertain
+ due to lack of debug info. See also Design Notes below.
+ It is not normally considered good design to use
+ this function; when possible, use `load` or `loadfile` instead.
+ http://www.lua.org/manual/5.1/manual.html#pdf-setfenv
+
+DESIGN NOTES
+
+ This module intends to provide robust and fairly complete reimplementations
+ of the environment related Lua 5.1 and Lua 5.2 functions.
+ No effort is made, however, to simulate rare or difficult to simulate features,
+ such as thread environments, although this is liable to change in the future.
+ Such 5.1 capabilities are discouraged and ideally
+ removed from 5.1 code, thereby allowing your code to work in both 5.1 and 5.2.
+
+ In Lua 5.2, a `setfenv(f, {})`, where `f` lacks any upvalues, will be silently
+ ignored since there is no `_ENV` in this function to write to, and the
+ environment will have no effect inside the function anyway. However,
+ this does mean that `getfenv(setfenv(f, t))` does not necessarily equal `t`,
+ which is incompatible with 5.1 code (a possible workaround would be [1]).
+ If `setfenv(f, {})` has an upvalue but no debug info, then this will raise
+ an error to prevent inadvertently executing potentially untrusted code in the
+ global environment.
+
+ It is not normally considered good design to use `setfenv` and `getfenv`
+ (one reason they were removed in 5.2). When possible, consider replacing
+ these with `load` or `loadfile`, which are more restrictive and have native
+ implementations in 5.2.
+
+ This module might be merged into a more general Lua 5.1/5.2 compatibility
+ library (e.g. a full reimplementation of Lua 5.2 `_G`). However,
+ `load/loadfile/getfenv/setfenv` perhaps are among the more cumbersome
+ functions not to have.
+
+INSTALLATION
+
+ Download compat_env.lua:
+
+ wget https://raw.github.com/gist/1654007/compat_env.lua
+
+ Copy compat_env.lua into your LUA_PATH.
+
+ Alternately, unpack, test, and install into LuaRocks:
+
+ wget https://raw.github.com/gist/1422205/sourceunpack.lua
+ lua sourceunpack.lua compat_env.lua
+ (cd out && luarocks make)
+
+Related work
+
+ http://lua-users.org/wiki/LuaVersionCompatibility
+ https://github.com/stevedonovan/Penlight/blob/master/lua/pl/utils.lua
+ - penlight implementations of getfenv/setfenv
+ http://lua-users.org/lists/lua-l/2010-06/msg00313.html
+ - initial getfenv/setfenv implementation
+
+References
+
+ [1] http://lua-users.org/lists/lua-l/2010-06/msg00315.html
+
+Copyright
+
+(c) 2012 David Manura. Licensed under the same terms as Lua 5.1/5.2 (MIT license).
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+--]]---------------------------------------------------------------------
+
+local M = {_TYPE='module', _NAME='compat_env', _VERSION='0.2.20120124'}
+
+local function check_chunk_type(s, mode)
+ local nmode = mode or 'bt'
+ local is_binary = s and #s > 0 and s:byte(1) == 27
+ if is_binary and not nmode:match'b' then
+ return nil, ("attempt to load a binary chunk (mode is '%s')"):format(mode)
+ elseif not is_binary and not nmode:match't' then
+ return nil, ("attempt to load a text chunk (mode is '%s')"):format(mode)
+ end
+ return true
+end
+
+local IS_52_LOAD = pcall(load, '')
+if IS_52_LOAD then
+ M.load = _G.load
+ M.loadfile = _G.loadfile
+else
+ -- 5.2 style `load` implemented in 5.1
+ function M.load(ld, source, mode, env)
+ local f
+ if type(ld) == 'string' then
+ local s = ld
+ local ok, err = check_chunk_type(s, mode); if not ok then return ok, err end
+ local err; f, err = loadstring(s, source); if not f then return f, err end
+ elseif type(ld) == 'function' then
+ local ld2 = ld
+ if (mode or 'bt') ~= 'bt' then
+ local first = ld()
+ local ok, err = check_chunk_type(first, mode); if not ok then return ok, err end
+ ld2 = function()
+ if first then
+ local chunk=first; first=nil; return chunk
+ else return ld() end
+ end
+ end
+ local err; f, err = load(ld2, source); if not f then return f, err end
+ else
+ error(("bad argument #1 to 'load' (function expected, got %s)"):format(type(ld)), 2)
+ end
+ if env then setfenv(f, env) end
+ return f
+ end
+
+ -- 5.2 style `loadfile` implemented in 5.1
+ function M.loadfile(filename, mode, env)
+ if (mode or 'bt') ~= 'bt' then
+ local ioerr
+ local fh, err = io.open(filename, 'rb'); if not fh then return fh, err end
+ local function ld() local chunk; chunk,ioerr = fh:read(4096); return chunk end
+ local f, err = M.load(ld, filename and '@'..filename, mode, env)
+ fh:close()
+ if not f then return f, err end
+ if ioerr then return nil, ioerr end
+ return f
+ else
+ local f, err = loadfile(filename); if not f then return f, err end
+ if env then setfenv(f, env) end
+ return f
+ end
+ end
+end
+
+if _G.setfenv then -- Lua 5.1
+ M.setfenv = _G.setfenv
+ M.getfenv = _G.getfenv
+else -- >= Lua 5.2
+ -- helper function for `getfenv`/`setfenv`
+ local function envlookup(f)
+ local name, val
+ local up = 0
+ local unknown
+ repeat
+ up=up+1; name, val = debug.getupvalue(f, up)
+ if name == '' then unknown = true end
+ until name == '_ENV' or name == nil
+ if name ~= '_ENV' then
+ up = nil
+ if unknown then error("upvalues not readable in Lua 5.2 when debug info missing", 3) end
+ end
+ return (name == '_ENV') and up, val, unknown
+ end
+
+ -- helper function for `getfenv`/`setfenv`
+ local function envhelper(f, name)
+ if type(f) == 'number' then
+ if f < 0 then
+ error(("bad argument #1 to '%s' (level must be non-negative)"):format(name), 3)
+ elseif f < 1 then
+ error("thread environments unsupported in Lua 5.2", 3) --[*]
+ end
+ f = debug.getinfo(f+2, 'f').func
+ elseif type(f) ~= 'function' then
+ error(("bad argument #1 to '%s' (number expected, got %s)"):format(type(name, f)), 2)
+ end
+ return f
+ end
+ -- [*] might simulate with table keyed by coroutine.running()
+
+ -- 5.1 style `setfenv` implemented in 5.2
+ function M.setfenv(f, t)
+ local f = envhelper(f, 'setfenv')
+ local up, val, unknown = envlookup(f)
+ if up then
+ debug.upvaluejoin(f, up, function() return up end, 1) -- unique upvalue [*]
+ debug.setupvalue(f, up, t)
+ else
+ local what = debug.getinfo(f, 'S').what
+ if what ~= 'Lua' and what ~= 'main' then -- not Lua func
+ error("'setfenv' cannot change environment of given object", 2)
+ end -- else ignore no _ENV upvalue (warning: incompatible with 5.1)
+ end
+ -- added in https://gist.github.com/2255007
+ return f
+ end
+ -- [*] http://lua-users.org/lists/lua-l/2010-06/msg00313.html
+
+ -- 5.1 style `getfenv` implemented in 5.2
+ function M.getfenv(f)
+ if f == 0 or f == nil then return _G end -- simulated behavior
+ local f = envhelper(f, 'setfenv')
+ local up, val = envlookup(f)
+ if not up then return _G end -- simulated behavior [**]
+ return val
+ end
+ -- [**] possible reasons: no _ENV upvalue, C function
+end
+
+
+return M
+
+--[[ FILE rockspec.in
+
+package = 'compat_env'
+version = '$(_VERSION)-1'
+source = {
+ url = 'https://raw.github.com/gist/1654007/$(GITID)/compat_env.lua',
+ --url = 'https://raw.github.com/gist/1654007/compat_env.lua', -- latest raw
+ --url = 'https://gist.github.com/gists/1654007/download',
+ md5 = '$(MD5)'
+}
+description = {
+ summary = 'Lua 5.1/5.2 environment compatibility functions',
+ detailed = [=[
+ Provides Lua 5.1/5.2 environment related compatibility functions.
+ This includes implementations of Lua 5.2 style `load` and `loadfile`
+ for use in Lua 5.1. It also includes Lua 5.1 style `getfenv` and `setfenv`
+ for use in Lua 5.2.
+ ]=],
+ license = 'MIT/X11',
+ homepage = 'https://gist.github.com/1654007',
+ maintainer = 'David Manura'
+}
+dependencies = {} -- Lua 5.1 or 5.2
+build = {
+ type = 'builtin',
+ modules = {
+ ['compat_env'] = 'compat_env.lua'
+ }
+}
+
+--]]---------------------------------------------------------------------
+
+--[[ FILE test.lua
+
+-- test.lua - test suite for compat_env module.
+
+local CL = require 'compat_env'
+local load = CL.load
+local loadfile = CL.loadfile
+local setfenv = CL.setfenv
+local getfenv = CL.getfenv
+
+local function checkeq(a, b, e)
+ if a ~= b then error(
+ 'not equal ['..tostring(a)..'] ['..tostring(b)..'] ['..tostring(e)..']')
+ end
+end
+local function checkerr(pat, ok, err)
+ assert(not ok, 'checkerr')
+ assert(type(err) == 'string' and err:match(pat), err)
+end
+
+-- test `load`
+checkeq(load('return 2')(), 2)
+checkerr('expected near', load'return 2 2')
+checkerr('text chunk', load('return 2', nil, 'b'))
+checkerr('text chunk', load('', nil, 'b'))
+checkerr('binary chunk', load('\027', nil, 't'))
+checkeq(load('return 2*x',nil,'bt',{x=5})(), 10)
+checkeq(debug.getinfo(load('')).source, '')
+checkeq(debug.getinfo(load('', 'foo')).source, 'foo')
+
+-- test `loadfile`
+local fh = assert(io.open('tmp.lua', 'wb'))
+fh:write('return (...) or x')
+fh:close()
+checkeq(loadfile('tmp.lua')(2), 2)
+checkeq(loadfile('tmp.lua', 't')(2), 2)
+checkerr('text chunk', loadfile('tmp.lua', 'b'))
+checkeq(loadfile('tmp.lua', nil, {x=3})(), 3)
+checkeq(debug.getinfo(loadfile('tmp.lua')).source, '@tmp.lua')
+checkeq(debug.getinfo(loadfile('tmp.lua', 't', {})).source, '@tmp.lua')
+os.remove'tmp.lua'
+
+-- test `setfenv`/`getfenv`
+x = 5
+local a,b=true; local function f(c) if a then return x,b,c end end
+setfenv(f, {x=3})
+checkeq(f(), 3)
+checkeq(getfenv(f).x, 3)
+checkerr('cannot change', pcall(setfenv, string.len, {})) -- C function
+checkeq(getfenv(string.len), _G) -- C function
+local function g()
+ setfenv(1, {x=4})
+ checkeq(getfenv(1).x, 4)
+ return x
+end
+checkeq(g(), 4) -- numeric level
+if _G._VERSION ~= 'Lua 5.1' then
+ checkerr('unsupported', pcall(setfenv, 0, {}))
+end
+checkeq(getfenv(0), _G)
+checkeq(getfenv(), _G) -- no arg
+checkeq(x, 5) -- main unaltered
+setfenv(function()end, {}) -- no upvalues, ignore
+checkeq(getfenv(function()end), _G) -- no upvaluse
+if _G._VERSION ~= 'Lua 5.1' then
+ checkeq(getfenv(setfenv(function()end, {})), _G) -- warning: incompatible with 5.1
+end
+x = nil
+
+print 'OK'
+
+--]]---------------------------------------------------------------------
+
+--[[ FILE CHANGES.txt
+0.2.20120124
+ Renamed module to compat_env (from compat_load)
+ Add getfenv/setfenv functions
+
+0.1.20120121
+ Initial public release
+--]]