You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

compat_env.lua 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. --[[
  2. compat_env v$(_VERSION) - Lua 5.1/5.2 environment compatibility functions
  3. SYNOPSIS
  4. -- Get load/loadfile compatibility functions only if using 5.1.
  5. local CL = pcall(load, '') and _G or require 'compat_env'
  6. local load = CL.load
  7. local loadfile = CL.loadfile
  8. -- The following now works in both Lua 5.1 and 5.2:
  9. assert(load('return 2*pi', nil, 't', {pi=math.pi}))()
  10. assert(loadfile('ex.lua', 't', {print=print}))()
  11. -- Get getfenv/setfenv compatibility functions only if using 5.2.
  12. local getfenv = _G.getfenv or require 'compat_env'.getfenv
  13. local setfenv = _G.setfenv or require 'compat_env'.setfenv
  14. local function f() return x end
  15. setfenv(f, {x=2})
  16. print(x, getfenv(f).x) --> 2, 2
  17. DESCRIPTION
  18. This module provides Lua 5.1/5.2 environment related compatibility functions.
  19. This includes implementations of Lua 5.2 style `load` and `loadfile`
  20. for use in Lua 5.1. It also includes Lua 5.1 style `getfenv` and `setfenv`
  21. for use in Lua 5.2.
  22. API
  23. local CL = require 'compat_env'
  24. CL.load (ld [, source [, mode [, env] ] ]) --> f [, err]
  25. This behaves the same as the Lua 5.2 `load` in both
  26. Lua 5.1 and 5.2.
  27. http://www.lua.org/manual/5.2/manual.html#pdf-load
  28. CL.loadfile ([filename [, mode [, env] ] ]) --> f [, err]
  29. This behaves the same as the Lua 5.2 `loadfile` in both
  30. Lua 5.1 and 5.2.
  31. http://www.lua.org/manual/5.2/manual.html#pdf-loadfile
  32. CL.getfenv ([f]) --> t
  33. This is identical to the Lua 5.1 `getfenv` in Lua 5.1.
  34. This behaves similar to the Lua 5.1 `getfenv` in Lua 5.2.
  35. When a global environment is to be returned, or when `f` is a
  36. C function, this returns `_G` since Lua 5.2 doesn't have
  37. (thread) global and C function environments. This will also
  38. return `_G` if the Lua function `f` lacks an `_ENV`
  39. upvalue, but it will raise an error if uncertain due to lack of
  40. debug info. It is not normally considered good design to use
  41. this function; when possible, use `load` or `loadfile` instead.
  42. http://www.lua.org/manual/5.1/manual.html#pdf-getfenv
  43. CL.setfenv (f, t)
  44. This is identical to the Lua 5.1 `setfenv` in Lua 5.1.
  45. This behaves similar to the Lua 5.1 `setfenv` in Lua 5.2.
  46. This will do nothing if `f` is a Lua function that
  47. lacks an `_ENV` upvalue, but it will raise an error if uncertain
  48. due to lack of debug info. See also Design Notes below.
  49. It is not normally considered good design to use
  50. this function; when possible, use `load` or `loadfile` instead.
  51. http://www.lua.org/manual/5.1/manual.html#pdf-setfenv
  52. DESIGN NOTES
  53. This module intends to provide robust and fairly complete reimplementations
  54. of the environment related Lua 5.1 and Lua 5.2 functions.
  55. No effort is made, however, to simulate rare or difficult to simulate features,
  56. such as thread environments, although this is liable to change in the future.
  57. Such 5.1 capabilities are discouraged and ideally
  58. removed from 5.1 code, thereby allowing your code to work in both 5.1 and 5.2.
  59. In Lua 5.2, a `setfenv(f, {})`, where `f` lacks any upvalues, will be silently
  60. ignored since there is no `_ENV` in this function to write to, and the
  61. environment will have no effect inside the function anyway. However,
  62. this does mean that `getfenv(setfenv(f, t))` does not necessarily equal `t`,
  63. which is incompatible with 5.1 code (a possible workaround would be [1]).
  64. If `setfenv(f, {})` has an upvalue but no debug info, then this will raise
  65. an error to prevent inadvertently executing potentially untrusted code in the
  66. global environment.
  67. It is not normally considered good design to use `setfenv` and `getfenv`
  68. (one reason they were removed in 5.2). When possible, consider replacing
  69. these with `load` or `loadfile`, which are more restrictive and have native
  70. implementations in 5.2.
  71. This module might be merged into a more general Lua 5.1/5.2 compatibility
  72. library (e.g. a full reimplementation of Lua 5.2 `_G`). However,
  73. `load/loadfile/getfenv/setfenv` perhaps are among the more cumbersome
  74. functions not to have.
  75. INSTALLATION
  76. Download compat_env.lua:
  77. wget https://raw.github.com/gist/1654007/compat_env.lua
  78. Copy compat_env.lua into your LUA_PATH.
  79. Alternately, unpack, test, and install into LuaRocks:
  80. wget https://raw.github.com/gist/1422205/sourceunpack.lua
  81. lua sourceunpack.lua compat_env.lua
  82. (cd out && luarocks make)
  83. Related work
  84. http://lua-users.org/wiki/LuaVersionCompatibility
  85. https://github.com/stevedonovan/Penlight/blob/master/lua/pl/utils.lua
  86. - penlight implementations of getfenv/setfenv
  87. http://lua-users.org/lists/lua-l/2010-06/msg00313.html
  88. - initial getfenv/setfenv implementation
  89. References
  90. [1] http://lua-users.org/lists/lua-l/2010-06/msg00315.html
  91. Copyright
  92. (c) 2012 David Manura. Licensed under the same terms as Lua 5.1/5.2 (MIT license).
  93. Permission is hereby granted, free of charge, to any person obtaining a copy
  94. of this software and associated documentation files (the "Software"), to deal
  95. in the Software without restriction, including without limitation the rights
  96. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  97. copies of the Software, and to permit persons to whom the Software is
  98. furnished to do so, subject to the following conditions:
  99. The above copyright notice and this permission notice shall be included in
  100. all copies or substantial portions of the Software.
  101. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  102. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  103. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  104. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  105. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  106. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  107. THE SOFTWARE.
  108. --]]---------------------------------------------------------------------
  109. local M = {_TYPE='module', _NAME='compat_env', _VERSION='0.2.20120124'}
  110. local function check_chunk_type(s, mode)
  111. local nmode = mode or 'bt'
  112. local is_binary = s and #s > 0 and s:byte(1) == 27
  113. if is_binary and not nmode:match'b' then
  114. return nil, ("attempt to load a binary chunk (mode is '%s')"):format(mode)
  115. elseif not is_binary and not nmode:match't' then
  116. return nil, ("attempt to load a text chunk (mode is '%s')"):format(mode)
  117. end
  118. return true
  119. end
  120. local IS_52_LOAD = pcall(load, '')
  121. if IS_52_LOAD then
  122. M.load = _G.load
  123. M.loadfile = _G.loadfile
  124. else
  125. -- 5.2 style `load` implemented in 5.1
  126. function M.load(ld, source, mode, env)
  127. local f
  128. if type(ld) == 'string' then
  129. local s = ld
  130. local ok, err = check_chunk_type(s, mode); if not ok then return ok, err end
  131. local err; f, err = loadstring(s, source); if not f then return f, err end
  132. elseif type(ld) == 'function' then
  133. local ld2 = ld
  134. if (mode or 'bt') ~= 'bt' then
  135. local first = ld()
  136. local ok, err = check_chunk_type(first, mode); if not ok then return ok, err end
  137. ld2 = function()
  138. if first then
  139. local chunk=first; first=nil; return chunk
  140. else return ld() end
  141. end
  142. end
  143. local err; f, err = load(ld2, source); if not f then return f, err end
  144. else
  145. error(("bad argument #1 to 'load' (function expected, got %s)"):format(type(ld)), 2)
  146. end
  147. if env then setfenv(f, env) end
  148. return f
  149. end
  150. -- 5.2 style `loadfile` implemented in 5.1
  151. function M.loadfile(filename, mode, env)
  152. if (mode or 'bt') ~= 'bt' then
  153. local ioerr
  154. local fh, err = io.open(filename, 'rb'); if not fh then return fh, err end
  155. local function ld() local chunk; chunk,ioerr = fh:read(4096); return chunk end
  156. local f, err = M.load(ld, filename and '@'..filename, mode, env)
  157. fh:close()
  158. if not f then return f, err end
  159. if ioerr then return nil, ioerr end
  160. return f
  161. else
  162. local f, err = loadfile(filename); if not f then return f, err end
  163. if env then setfenv(f, env) end
  164. return f
  165. end
  166. end
  167. end
  168. if _G.setfenv then -- Lua 5.1
  169. M.setfenv = _G.setfenv
  170. M.getfenv = _G.getfenv
  171. else -- >= Lua 5.2
  172. -- helper function for `getfenv`/`setfenv`
  173. local function envlookup(f)
  174. local name, val
  175. local up = 0
  176. local unknown
  177. repeat
  178. up=up+1; name, val = debug.getupvalue(f, up)
  179. if name == '' then unknown = true end
  180. until name == '_ENV' or name == nil
  181. if name ~= '_ENV' then
  182. up = nil
  183. if unknown then error("upvalues not readable in Lua 5.2 when debug info missing", 3) end
  184. end
  185. return (name == '_ENV') and up, val, unknown
  186. end
  187. -- helper function for `getfenv`/`setfenv`
  188. local function envhelper(f, name)
  189. if type(f) == 'number' then
  190. if f < 0 then
  191. error(("bad argument #1 to '%s' (level must be non-negative)"):format(name), 3)
  192. elseif f < 1 then
  193. error("thread environments unsupported in Lua 5.2", 3) --[*]
  194. end
  195. f = debug.getinfo(f+2, 'f').func
  196. elseif type(f) ~= 'function' then
  197. error(("bad argument #1 to '%s' (number expected, got %s)"):format(type(name, f)), 2)
  198. end
  199. return f
  200. end
  201. -- [*] might simulate with table keyed by coroutine.running()
  202. -- 5.1 style `setfenv` implemented in 5.2
  203. function M.setfenv(f, t)
  204. local f = envhelper(f, 'setfenv')
  205. local up, val, unknown = envlookup(f)
  206. if up then
  207. debug.upvaluejoin(f, up, function() return up end, 1) -- unique upvalue [*]
  208. debug.setupvalue(f, up, t)
  209. else
  210. local what = debug.getinfo(f, 'S').what
  211. if what ~= 'Lua' and what ~= 'main' then -- not Lua func
  212. error("'setfenv' cannot change environment of given object", 2)
  213. end -- else ignore no _ENV upvalue (warning: incompatible with 5.1)
  214. end
  215. -- added in https://gist.github.com/2255007
  216. return f
  217. end
  218. -- [*] http://lua-users.org/lists/lua-l/2010-06/msg00313.html
  219. -- 5.1 style `getfenv` implemented in 5.2
  220. function M.getfenv(f)
  221. if f == 0 or f == nil then return _G end -- simulated behavior
  222. local f = envhelper(f, 'setfenv')
  223. local up, val = envlookup(f)
  224. if not up then return _G end -- simulated behavior [**]
  225. return val
  226. end
  227. -- [**] possible reasons: no _ENV upvalue, C function
  228. end
  229. return M
  230. --[[ FILE rockspec.in
  231. package = 'compat_env'
  232. version = '$(_VERSION)-1'
  233. source = {
  234. url = 'https://raw.github.com/gist/1654007/$(GITID)/compat_env.lua',
  235. --url = 'https://raw.github.com/gist/1654007/compat_env.lua', -- latest raw
  236. --url = 'https://gist.github.com/gists/1654007/download',
  237. md5 = '$(MD5)'
  238. }
  239. description = {
  240. summary = 'Lua 5.1/5.2 environment compatibility functions',
  241. detailed = [=[
  242. Provides Lua 5.1/5.2 environment related compatibility functions.
  243. This includes implementations of Lua 5.2 style `load` and `loadfile`
  244. for use in Lua 5.1. It also includes Lua 5.1 style `getfenv` and `setfenv`
  245. for use in Lua 5.2.
  246. ]=],
  247. license = 'MIT/X11',
  248. homepage = 'https://gist.github.com/1654007',
  249. maintainer = 'David Manura'
  250. }
  251. dependencies = {} -- Lua 5.1 or 5.2
  252. build = {
  253. type = 'builtin',
  254. modules = {
  255. ['compat_env'] = 'compat_env.lua'
  256. }
  257. }
  258. --]]---------------------------------------------------------------------
  259. --[[ FILE test.lua
  260. -- test.lua - test suite for compat_env module.
  261. local CL = require 'compat_env'
  262. local load = CL.load
  263. local loadfile = CL.loadfile
  264. local setfenv = CL.setfenv
  265. local getfenv = CL.getfenv
  266. local function checkeq(a, b, e)
  267. if a ~= b then error(
  268. 'not equal ['..tostring(a)..'] ['..tostring(b)..'] ['..tostring(e)..']')
  269. end
  270. end
  271. local function checkerr(pat, ok, err)
  272. assert(not ok, 'checkerr')
  273. assert(type(err) == 'string' and err:match(pat), err)
  274. end
  275. -- test `load`
  276. checkeq(load('return 2')(), 2)
  277. checkerr('expected near', load'return 2 2')
  278. checkerr('text chunk', load('return 2', nil, 'b'))
  279. checkerr('text chunk', load('', nil, 'b'))
  280. checkerr('binary chunk', load('\027', nil, 't'))
  281. checkeq(load('return 2*x',nil,'bt',{x=5})(), 10)
  282. checkeq(debug.getinfo(load('')).source, '')
  283. checkeq(debug.getinfo(load('', 'foo')).source, 'foo')
  284. -- test `loadfile`
  285. local fh = assert(io.open('tmp.lua', 'wb'))
  286. fh:write('return (...) or x')
  287. fh:close()
  288. checkeq(loadfile('tmp.lua')(2), 2)
  289. checkeq(loadfile('tmp.lua', 't')(2), 2)
  290. checkerr('text chunk', loadfile('tmp.lua', 'b'))
  291. checkeq(loadfile('tmp.lua', nil, {x=3})(), 3)
  292. checkeq(debug.getinfo(loadfile('tmp.lua')).source, '@tmp.lua')
  293. checkeq(debug.getinfo(loadfile('tmp.lua', 't', {})).source, '@tmp.lua')
  294. os.remove'tmp.lua'
  295. -- test `setfenv`/`getfenv`
  296. x = 5
  297. local a,b=true; local function f(c) if a then return x,b,c end end
  298. setfenv(f, {x=3})
  299. checkeq(f(), 3)
  300. checkeq(getfenv(f).x, 3)
  301. checkerr('cannot change', pcall(setfenv, string.len, {})) -- C function
  302. checkeq(getfenv(string.len), _G) -- C function
  303. local function g()
  304. setfenv(1, {x=4})
  305. checkeq(getfenv(1).x, 4)
  306. return x
  307. end
  308. checkeq(g(), 4) -- numeric level
  309. if _G._VERSION ~= 'Lua 5.1' then
  310. checkerr('unsupported', pcall(setfenv, 0, {}))
  311. end
  312. checkeq(getfenv(0), _G)
  313. checkeq(getfenv(), _G) -- no arg
  314. checkeq(x, 5) -- main unaltered
  315. setfenv(function()end, {}) -- no upvalues, ignore
  316. checkeq(getfenv(function()end), _G) -- no upvaluse
  317. if _G._VERSION ~= 'Lua 5.1' then
  318. checkeq(getfenv(setfenv(function()end, {})), _G) -- warning: incompatible with 5.1
  319. end
  320. x = nil
  321. print 'OK'
  322. --]]---------------------------------------------------------------------
  323. --[[ FILE CHANGES.txt
  324. 0.2.20120124
  325. Renamed module to compat_env (from compat_load)
  326. Add getfenv/setfenv functions
  327. 0.1.20120121
  328. Initial public release
  329. --]]