|
1 --[[ |
|
2 compat_env - see README for details. |
|
3 (c) 2012 David Manura. Licensed under Lua 5.1/5.2 terms (MIT license). |
|
4 --]] |
|
5 |
|
6 local M = {_TYPE='module', _NAME='compat_env', _VERSION='0.2.2.20120406'} |
|
7 |
|
8 local function check_chunk_type(s, mode) |
|
9 local nmode = mode or 'bt' |
|
10 local is_binary = s and #s > 0 and s:byte(1) == 27 |
|
11 if is_binary and not nmode:match'b' then |
|
12 return nil, ("attempt to load a binary chunk (mode is '%s')"):format(mode) |
|
13 elseif not is_binary and not nmode:match't' then |
|
14 return nil, ("attempt to load a text chunk (mode is '%s')"):format(mode) |
|
15 end |
|
16 return true |
|
17 end |
|
18 |
|
19 local IS_52_LOAD = pcall(load, '') |
|
20 if IS_52_LOAD then |
|
21 M.load = _G.load |
|
22 M.loadfile = _G.loadfile |
|
23 else |
|
24 -- 5.2 style `load` implemented in 5.1 |
|
25 function M.load(ld, source, mode, env) |
|
26 local f |
|
27 if type(ld) == 'string' then |
|
28 local s = ld |
|
29 local ok, err = check_chunk_type(s, mode) |
|
30 if not ok then return ok, err end |
|
31 local err; f, err = loadstring(s, source) |
|
32 if not f then return f, err end |
|
33 elseif type(ld) == 'function' then |
|
34 local ld2 = ld |
|
35 if (mode or 'bt') ~= 'bt' then |
|
36 local first = ld() |
|
37 local ok, err = check_chunk_type(first, mode) |
|
38 if not ok then return ok, err end |
|
39 ld2 = function() |
|
40 if first then |
|
41 local chunk=first; first=nil; return chunk |
|
42 else return ld() end |
|
43 end |
|
44 end |
|
45 local err; f, err = load(ld2, source); if not f then return f, err end |
|
46 else |
|
47 error(("bad argument #1 to 'load' (function expected, got %s)") |
|
48 :format(type(ld)), 2) |
|
49 end |
|
50 if env then setfenv(f, env) end |
|
51 return f |
|
52 end |
|
53 |
|
54 -- 5.2 style `loadfile` implemented in 5.1 |
|
55 function M.loadfile(filename, mode, env) |
|
56 if (mode or 'bt') ~= 'bt' then |
|
57 local ioerr |
|
58 local fh, err = io.open(filename, 'rb'); if not fh then return fh,err end |
|
59 local function ld() |
|
60 local chunk; chunk,ioerr = fh:read(4096); return chunk |
|
61 end |
|
62 local f, err = M.load(ld, filename and '@'..filename, mode, env) |
|
63 fh:close() |
|
64 if not f then return f, err end |
|
65 if ioerr then return nil, ioerr end |
|
66 return f |
|
67 else |
|
68 local f, err = loadfile(filename); if not f then return f, err end |
|
69 if env then setfenv(f, env) end |
|
70 return f |
|
71 end |
|
72 end |
|
73 end |
|
74 |
|
75 if _G.setfenv then -- Lua 5.1 |
|
76 M.setfenv = _G.setfenv |
|
77 M.getfenv = _G.getfenv |
|
78 else -- >= Lua 5.2 |
|
79 -- helper function for `getfenv`/`setfenv` |
|
80 local function envlookup(f) |
|
81 local name, val |
|
82 local up = 0 |
|
83 local unknown |
|
84 repeat |
|
85 up=up+1; name, val = debug.getupvalue(f, up) |
|
86 if name == '' then unknown = true end |
|
87 until name == '_ENV' or name == nil |
|
88 if name ~= '_ENV' then |
|
89 up = nil |
|
90 if unknown then |
|
91 error("upvalues not readable in Lua 5.2 when debug info missing", 3) |
|
92 end |
|
93 end |
|
94 return (name == '_ENV') and up, val, unknown |
|
95 end |
|
96 |
|
97 -- helper function for `getfenv`/`setfenv` |
|
98 local function envhelper(f, name) |
|
99 if type(f) == 'number' then |
|
100 if f < 0 then |
|
101 error(("bad argument #1 to '%s' (level must be non-negative)") |
|
102 :format(name), 3) |
|
103 elseif f < 1 then |
|
104 error("thread environments unsupported in Lua 5.2", 3) --[*] |
|
105 end |
|
106 f = debug.getinfo(f+2, 'f').func |
|
107 elseif type(f) ~= 'function' then |
|
108 error(("bad argument #1 to '%s' (number expected, got %s)") |
|
109 :format(type(name, f)), 2) |
|
110 end |
|
111 return f |
|
112 end |
|
113 -- [*] might simulate with table keyed by coroutine.running() |
|
114 |
|
115 -- 5.1 style `setfenv` implemented in 5.2 |
|
116 function M.setfenv(f, t) |
|
117 local f = envhelper(f, 'setfenv') |
|
118 local up, val, unknown = envlookup(f) |
|
119 if up then |
|
120 debug.upvaluejoin(f, up, function() return up end, 1) --unique upval[*] |
|
121 debug.setupvalue(f, up, t) |
|
122 else |
|
123 local what = debug.getinfo(f, 'S').what |
|
124 if what ~= 'Lua' and what ~= 'main' then -- not Lua func |
|
125 error("'setfenv' cannot change environment of given object", 2) |
|
126 end -- else ignore no _ENV upvalue (warning: incompatible with 5.1) |
|
127 end |
|
128 return f -- invariant: original f ~= 0 |
|
129 end |
|
130 -- [*] http://lua-users.org/lists/lua-l/2010-06/msg00313.html |
|
131 |
|
132 -- 5.1 style `getfenv` implemented in 5.2 |
|
133 function M.getfenv(f) |
|
134 if f == 0 or f == nil then return _G end -- simulated behavior |
|
135 local f = envhelper(f, 'setfenv') |
|
136 local up, val = envlookup(f) |
|
137 if not up then return _G end -- simulated behavior [**] |
|
138 return val |
|
139 end |
|
140 -- [**] possible reasons: no _ENV upvalue, C function |
|
141 end |
|
142 |
|
143 |
|
144 return M |