-
-
Save norman/2255007 to your computer and use it in GitHub Desktop.
Lua module 'compat_env' - Lua 5.1/5.2 environment compatibility functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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 | |
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 | |
--]] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment