Skip to content

Commit c87038c

Browse files
committed
Merge branch 'isolated'
Conflicts: README.md
2 parents ec5331c + 17fdace commit c87038c

4 files changed

Lines changed: 559 additions & 383 deletions

File tree

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ string packing modules automatically. If unsuccessful, pure Lua
4242
versions of the new `table` functions are used as a fallback, and
4343
[Roberto's struct library][1] is tried for string packing.
4444

45+
#### Lua submodules
46+
47+
```lua
48+
local _ENV = require("compat53.module")
49+
if setfenv then setfenv(1, _ENV) end
50+
```
51+
52+
The `compat53.module` module does not modify the global environment,
53+
and so it is safe to use in modules without affecting other Lua files.
54+
It is supposed to be set as the current environment (see above), i.e.
55+
cherry picking individual functions from this module is expressly
56+
*not* supported!). Not all features are available when using this
57+
module (e.g. yieldable (x)pcall support, string/file methods, etc.),
58+
so it is recommended to use plain `require("compat53")` whenever
59+
possible.
60+
4561
### C code
4662

4763
There are two ways of adding the C API compatibility functions/macros to
@@ -67,7 +83,7 @@ your project:
6783
5.3 sources or from the `struct` module. (`struct` is not 100%
6884
compatible to Lua 5.3's string packing!) (See [here][4])
6985
* `math.maxinteger` and `math.mininteger`, `math.tointeger`, `math.type`,
70-
and `math.ult` (See [here][5])
86+
and `math.ult` (see [here][5])
7187
* `ipairs` respects `__index` metamethod
7288
* `table.move`
7389
* `table` library respects metamethods

compat53/init.lua

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
local _G, _VERSION, type, pairs, require =
2+
_G, _VERSION, type, pairs, require
3+
4+
local M = require("compat53.module")
5+
local lua_version = _VERSION:sub(-3)
6+
7+
8+
-- apply other global effects
9+
if lua_version == "5.1" then
10+
11+
-- cache globals
12+
local error, pcall, rawset, select, setmetatable, tostring, unpack, xpcall =
13+
error, pcall, rawset, select, setmetatable, tostring, unpack, xpcall
14+
local coroutine, debug, io, package, string =
15+
coroutine, debug, io, package, string
16+
local coroutine_create = coroutine.create
17+
local coroutine_resume = coroutine.resume
18+
local coroutine_running = coroutine.running
19+
local coroutine_status = coroutine.status
20+
local coroutine_yield = coroutine.yield
21+
local io_type, io_stdout = io.type, io.stdout
22+
23+
-- select the most powerful getmetatable function available
24+
local gmt = type(debug) == "table" and debug.getmetatable or
25+
getmetatable or function() return false end
26+
27+
-- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag)
28+
local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ"
29+
local is_luajit52 = is_luajit and
30+
#setmetatable({}, { __len = function() return 1 end }) == 1
31+
32+
33+
-- make package.searchers available as an alias for package.loaders
34+
local p_index = { searchers = package.loaders }
35+
setmetatable(package, {
36+
__index = p_index,
37+
__newindex = function(p, k, v)
38+
if k == "searchers" then
39+
rawset(p, "loaders", v)
40+
p_index.searchers = v
41+
else
42+
rawset(p, k, v)
43+
end
44+
end
45+
})
46+
47+
48+
if not is_luajit then
49+
local function helper(st, var_1, ...)
50+
if var_1 == nil then
51+
if (...) ~= nil then
52+
error((...), 2)
53+
end
54+
end
55+
return var_1, ...
56+
end
57+
58+
local function lines_iterator(st)
59+
return helper(st, st.f:read(unpack(st, 1, st.n)))
60+
end
61+
62+
local valid_format = { ["*l"] = true, ["*n"] = true, ["*a"] = true }
63+
64+
local file_meta = gmt(io_stdout)
65+
if type(file_meta) == "table" and type(file_meta.__index) == "table" then
66+
local file_write = file_meta.__index.write
67+
file_meta.__index.write = function(self, ...)
68+
local res, msg, errno = file_write(self, ...)
69+
if res then
70+
return self
71+
else
72+
return nil, msg, errno
73+
end
74+
end
75+
76+
file_meta.__index.lines = function(self, ...)
77+
if io_type(self) == "closed file" then
78+
error("attempt to use a closed file", 2)
79+
end
80+
local st = { f=self, n=select('#', ...), ... }
81+
for i = 1, st.n do
82+
if type(st[i]) ~= "number" and not valid_format[st[i]] then
83+
error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2)
84+
end
85+
end
86+
return lines_iterator, st
87+
end
88+
end
89+
end -- not luajit
90+
91+
92+
-- the (x)pcall implementations start a new coroutine internally
93+
-- to allow yielding even in Lua 5.1. to allow for accurate
94+
-- stack traces we keep track of the nested coroutine activations
95+
-- in the weak tables below:
96+
local weak_meta = { __mode = "kv" }
97+
-- maps the internal pcall coroutines to the user coroutine that
98+
-- *should* be running if pcall didn't use coroutines internally
99+
local pcall_mainOf = setmetatable({}, weak_meta)
100+
-- table that maps each running coroutine started by pcall to
101+
-- the coroutine that resumed it (user coroutine *or* pcall
102+
-- coroutine!)
103+
local pcall_previous = setmetatable({}, weak_meta)
104+
-- reverse of `pcall_mainOf`. maps a user coroutine to the
105+
-- currently active pcall coroutine started within it
106+
local pcall_callOf = setmetatable({}, weak_meta)
107+
-- similar to `pcall_mainOf` but is used only while executing
108+
-- the error handler of xpcall (thus no nesting is necessary!)
109+
local xpcall_running = setmetatable({}, weak_meta)
110+
111+
-- handle debug functions
112+
if type(debug) == "table" then
113+
local debug_getinfo = debug.getinfo
114+
local debug_traceback = debug.traceback
115+
116+
if not is_luajit then
117+
local function calculate_trace_level(co, level)
118+
if level ~= nil then
119+
for out = 1, 1/0 do
120+
local info = (co==nil) and debug_getinfo(out, "") or debug_getinfo(co, out, "")
121+
if info == nil then
122+
local max = out-1
123+
if level <= max then
124+
return level
125+
end
126+
return nil, level-max
127+
end
128+
end
129+
end
130+
return 1
131+
end
132+
133+
local stack_pattern = "\nstack traceback:"
134+
local stack_replace = ""
135+
function debug.traceback(co, msg, level)
136+
local lvl
137+
local nilmsg
138+
if type(co) ~= "thread" then
139+
co, msg, level = coroutine_running(), co, msg
140+
end
141+
if msg == nil then
142+
msg = ""
143+
nilmsg = true
144+
elseif type(msg) ~= "string" then
145+
return msg
146+
end
147+
if co == nil then
148+
msg = debug_traceback(msg, level or 1)
149+
else
150+
local xpco = xpcall_running[co]
151+
if xpco ~= nil then
152+
lvl, level = calculate_trace_level(xpco, level)
153+
if lvl then
154+
msg = debug_traceback(xpco, msg, lvl)
155+
else
156+
msg = msg..stack_pattern
157+
end
158+
lvl, level = calculate_trace_level(co, level)
159+
if lvl then
160+
local trace = debug_traceback(co, "", lvl)
161+
msg = msg..trace:gsub(stack_pattern, stack_replace)
162+
end
163+
else
164+
co = pcall_callOf[co] or co
165+
lvl, level = calculate_trace_level(co, level)
166+
if lvl then
167+
msg = debug_traceback(co, msg, lvl)
168+
else
169+
msg = msg..stack_pattern
170+
end
171+
end
172+
co = pcall_previous[co]
173+
while co ~= nil do
174+
lvl, level = calculate_trace_level(co, level)
175+
if lvl then
176+
local trace = debug_traceback(co, "", lvl)
177+
msg = msg..trace:gsub(stack_pattern, stack_replace)
178+
end
179+
co = pcall_previous[co]
180+
end
181+
end
182+
if nilmsg then
183+
msg = msg:gsub("^\n", "")
184+
end
185+
msg = msg:gsub("\n\t%(tail call%): %?", "\000")
186+
msg = msg:gsub("\n\t%.%.%.\n", "\001\n")
187+
msg = msg:gsub("\n\t%.%.%.$", "\001")
188+
msg = msg:gsub("(%z+)\001(%z+)", function(some, other)
189+
return "\n\t(..."..#some+#other.."+ tail call(s)...)"
190+
end)
191+
msg = msg:gsub("\001(%z+)", function(zeros)
192+
return "\n\t(..."..#zeros.."+ tail call(s)...)"
193+
end)
194+
msg = msg:gsub("(%z+)\001", function(zeros)
195+
return "\n\t(..."..#zeros.."+ tail call(s)...)"
196+
end)
197+
msg = msg:gsub("%z+", function(zeros)
198+
return "\n\t(..."..#zeros.." tail call(s)...)"
199+
end)
200+
msg = msg:gsub("\001", function(zeros)
201+
return "\n\t..."
202+
end)
203+
return msg
204+
end
205+
end -- is not luajit
206+
end -- debug table available
207+
208+
209+
local main_coroutine = coroutine_create(function() end)
210+
211+
if not is_luajit52 then
212+
function M.coroutine.running()
213+
local co = coroutine_running()
214+
if co then
215+
return pcall_mainOf[co] or co, false
216+
else
217+
return main_coroutine, true
218+
end
219+
end
220+
end
221+
222+
if not is_luajit then
223+
function M.coroutine.resume(co, ...)
224+
if co == main_coroutine then
225+
return false, "cannot resume non-suspended coroutine"
226+
else
227+
return coroutine_resume(co, ...)
228+
end
229+
end
230+
231+
function M.coroutine.status(co)
232+
local notmain = coroutine_running()
233+
if co == main_coroutine then
234+
return notmain and "normal" or "running"
235+
else
236+
return coroutine_status(co)
237+
end
238+
end
239+
240+
local function pcall_results(current, call, success, ...)
241+
if coroutine_status(call) == "suspended" then
242+
return pcall_results(current, call, coroutine_resume(call, coroutine_yield(...)))
243+
end
244+
if pcall_previous then
245+
pcall_previous[call] = nil
246+
local main = pcall_mainOf[call]
247+
if main == current then current = nil end
248+
pcall_callOf[main] = current
249+
end
250+
pcall_mainOf[call] = nil
251+
return success, ...
252+
end
253+
254+
local function pcall_exec(current, call, ...)
255+
local main = pcall_mainOf[current] or current
256+
pcall_mainOf[call] = main
257+
if pcall_previous then
258+
pcall_previous[call] = current
259+
pcall_callOf[main] = call
260+
end
261+
return pcall_results(current, call, coroutine_resume(call, ...))
262+
end
263+
264+
local coroutine_create52 = M.coroutine.create
265+
266+
local function pcall_coroutine(func)
267+
if type(func) ~= "function" then
268+
local callable = func
269+
func = function (...) return callable(...) end
270+
end
271+
return coroutine_create52(func)
272+
end
273+
274+
function M.pcall(func, ...)
275+
local current = coroutine_running()
276+
if not current then return pcall(func, ...) end
277+
return pcall_exec(current, pcall_coroutine(func), ...)
278+
end
279+
280+
local function xpcall_catch(current, call, msgh, success, ...)
281+
if not success then
282+
xpcall_running[current] = call
283+
local ok, result = pcall(msgh, ...)
284+
xpcall_running[current] = nil
285+
if not ok then
286+
return false, "error in error handling ("..tostring(result)..")"
287+
end
288+
return false, result
289+
end
290+
return true, ...
291+
end
292+
293+
function M.xpcall(f, msgh, ...)
294+
local current = coroutine_running()
295+
if not current then
296+
local args, n = { ... }, select('#', ...)
297+
return xpcall(function() return f(unpack(args, 1, n)) end, msgh)
298+
end
299+
local call = pcall_coroutine(f)
300+
return xpcall_catch(current, call, msgh, pcall_exec(current, call, ...))
301+
end
302+
end -- not luajit
303+
304+
end -- lua == 5.1
305+
306+
307+
-- handle exporting to global scope
308+
local function extend_table(from, to)
309+
if from ~= to then
310+
for k,v in pairs(from) do
311+
if type(v) == "table" and
312+
type(to[k]) == "table" and
313+
v ~= to[k] then
314+
extend_table(v, to[k])
315+
else
316+
to[k] = v
317+
end
318+
end
319+
end
320+
end
321+
322+
extend_table(M, _G)
323+
324+
-- vi: set expandtab softtabstop=3 shiftwidth=3 :

0 commit comments

Comments
 (0)