123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- --[[
-
- 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
- --]]
|