local lua_version = _VERSION:sub(-3) if lua_version < "5.3" then local _G, pairs, require, select, type = _G, pairs, require, select, type local debug, io = debug, io local unpack = lua_version == "5.1" and unpack or table.unpack local M = require("compat53.module") -- select the most powerful getmetatable function available local gmt = type(debug) == "table" and debug.getmetatable or getmetatable or function() return false end -- metatable for file objects from Lua's standard io library local file_meta = gmt(io.stdout) -- make '*' optional for file:read and file:lines if type(file_meta) == "table" and type(file_meta.__index) == "table" then local function addasterisk(fmt) if type(fmt) == "string" and fmt:sub(1, 1) ~= "*" then return "*"..fmt else return fmt end end local file_lines = file_meta.__index.lines file_meta.__index.lines = function(self, ...) local n = select('#', ...) for i = 1, n do local a = select(i, ...) local b = addasterisk(a) -- as an optimization we only allocate a table for the -- modified format arguments when we have a '*' somewhere if a ~= b then local args = { ... } args[i] = b for j = i+1, n do args[j] = addasterisk(args[j]) end return file_lines(self, unpack(args, 1, n)) end end return file_lines(self, ...) end local file_read = file_meta.__index.read file_meta.__index.read = function(self, ...) local n = select('#', ...) for i = 1, n do local a = select(i, ...) local b = addasterisk(a) -- as an optimization we only allocate a table for the -- modified format arguments when we have a '*' somewhere if a ~= b then local args = { ... } args[i] = b for j = i+1, n do args[j] = addasterisk(args[j]) end return file_read(self, unpack(args, 1, n)) end end return file_read(self, ...) end end -- got a valid metatable for file objects -- changes for Lua 5.1 only if lua_version == "5.1" then -- cache globals local error, pcall, rawset, setmetatable, tostring, xpcall = error, pcall, rawset, setmetatable, tostring, xpcall local coroutine, package, string = coroutine, package, string local coroutine_resume = coroutine.resume local coroutine_running = coroutine.running local coroutine_status = coroutine.status local coroutine_yield = coroutine.yield local io_type = io.type -- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag) local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ" local is_luajit52 = is_luajit and #setmetatable({}, { __len = function() return 1 end }) == 1 -- make package.searchers available as an alias for package.loaders local p_index = { searchers = package.loaders } setmetatable(package, { __index = p_index, __newindex = function(p, k, v) if k == "searchers" then rawset(p, "loaders", v) p_index.searchers = v else rawset(p, k, v) end end }) if type(file_meta) == "table" and type(file_meta.__index) == "table" then if not is_luajit then local function helper(_, var_1, ...) if var_1 == nil then if (...) ~= nil then error((...), 2) end end return var_1, ... end local function lines_iterator(st) return helper(st, st.f:read(unpack(st, 1, st.n))) end local file_write = file_meta.__index.write file_meta.__index.write = function(self, ...) local res, msg, errno = file_write(self, ...) if res then return self else return nil, msg, errno end end file_meta.__index.lines = function(self, ...) if io_type(self) == "closed file" then error("attempt to use a closed file", 2) end local st = { f=self, n=select('#', ...), ... } for i = 1, st.n do local t = type(st[i]) if t == "string" then local fmt = st[i]:match("^*?([aln])") if not fmt then error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2) end st[i] = "*"..fmt elseif t ~= "number" then error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2) end end return lines_iterator, st end end -- not luajit end -- file_meta valid -- the (x)pcall implementations start a new coroutine internally -- to allow yielding even in Lua 5.1. to allow for accurate -- stack traces we keep track of the nested coroutine activations -- in the weak tables below: local weak_meta = { __mode = "kv" } -- maps the internal pcall coroutines to the user coroutine that -- *should* be running if pcall didn't use coroutines internally local pcall_mainOf = setmetatable({}, weak_meta) -- table that maps each running coroutine started by pcall to -- the coroutine that resumed it (user coroutine *or* pcall -- coroutine!) local pcall_previous = setmetatable({}, weak_meta) -- reverse of `pcall_mainOf`. maps a user coroutine to the -- currently active pcall coroutine started within it local pcall_callOf = setmetatable({}, weak_meta) -- similar to `pcall_mainOf` but is used only while executing -- the error handler of xpcall (thus no nesting is necessary!) local xpcall_running = setmetatable({}, weak_meta) -- handle debug functions if type(debug) == "table" then local debug_getinfo = debug.getinfo local debug_traceback = debug.traceback if not is_luajit then local function calculate_trace_level(co, level) if level ~= nil then for out = 1, 1/0 do local info = (co==nil) and debug_getinfo(out, "") or debug_getinfo(co, out, "") if info == nil then local max = out-1 if level <= max then return level end return nil, level-max end end end return 1 end local stack_pattern = "\nstack traceback:" local stack_replace = "" function debug.traceback(co, msg, level) local lvl local nilmsg if type(co) ~= "thread" then co, msg, level = coroutine_running(), co, msg end if msg == nil then msg = "" nilmsg = true elseif type(msg) ~= "string" then return msg end if co == nil then msg = debug_traceback(msg, level or 1) else local xpco = xpcall_running[co] if xpco ~= nil then lvl, level = calculate_trace_level(xpco, level) if lvl then msg = debug_traceback(xpco, msg, lvl) else msg = msg..stack_pattern end lvl, level = calculate_trace_level(co, level) if lvl then local trace = debug_traceback(co, "", lvl) msg = msg..trace:gsub(stack_pattern, stack_replace) end else co = pcall_callOf[co] or co lvl, level = calculate_trace_level(co, level) if lvl then msg = debug_traceback(co, msg, lvl) else msg = msg..stack_pattern end end co = pcall_previous[co] while co ~= nil do lvl, level = calculate_trace_level(co, level) if lvl then local trace = debug_traceback(co, "", lvl) msg = msg..trace:gsub(stack_pattern, stack_replace) end co = pcall_previous[co] end end if nilmsg then msg = msg:gsub("^\n", "") end msg = msg:gsub("\n\t%(tail call%): %?", "\000") msg = msg:gsub("\n\t%.%.%.\n", "\001\n") msg = msg:gsub("\n\t%.%.%.$", "\001") msg = msg:gsub("(%z+)\001(%z+)", function(some, other) return "\n\t(..."..#some+#other.."+ tail call(s)...)" end) msg = msg:gsub("\001(%z+)", function(zeros) return "\n\t(..."..#zeros.."+ tail call(s)...)" end) msg = msg:gsub("(%z+)\001", function(zeros) return "\n\t(..."..#zeros.."+ tail call(s)...)" end) msg = msg:gsub("%z+", function(zeros) return "\n\t(..."..#zeros.." tail call(s)...)" end) msg = msg:gsub("\001", function() return "\n\t..." end) return msg end end -- is not luajit end -- debug table available if not is_luajit52 then local coroutine_running52 = M.coroutine.running function M.coroutine.running() local co, ismain = coroutine_running52() if ismain then return co, true else return pcall_mainOf[co] or co, false end end end if not is_luajit then local function pcall_results(current, call, success, ...) if coroutine_status(call) == "suspended" then return pcall_results(current, call, coroutine_resume(call, coroutine_yield(...))) end if pcall_previous then pcall_previous[call] = nil local main = pcall_mainOf[call] if main == current then current = nil end pcall_callOf[main] = current end pcall_mainOf[call] = nil return success, ... end local function pcall_exec(current, call, ...) local main = pcall_mainOf[current] or current pcall_mainOf[call] = main if pcall_previous then pcall_previous[call] = current pcall_callOf[main] = call end return pcall_results(current, call, coroutine_resume(call, ...)) end local coroutine_create52 = M.coroutine.create local function pcall_coroutine(func) if type(func) ~= "function" then local callable = func func = function (...) return callable(...) end end return coroutine_create52(func) end function M.pcall(func, ...) local current = coroutine_running() if not current then return pcall(func, ...) end return pcall_exec(current, pcall_coroutine(func), ...) end local function xpcall_catch(current, call, msgh, success, ...) if not success then xpcall_running[current] = call local ok, result = pcall(msgh, ...) xpcall_running[current] = nil if not ok then return false, "error in error handling ("..tostring(result)..")" end return false, result end return true, ... end function M.xpcall(f, msgh, ...) local current = coroutine_running() if not current then local args, n = { ... }, select('#', ...) return xpcall(function() return f(unpack(args, 1, n)) end, msgh) end local call = pcall_coroutine(f) return xpcall_catch(current, call, msgh, pcall_exec(current, call, ...)) end end -- not luajit end -- lua 5.1 -- handle exporting to global scope local function extend_table(from, to) if from ~= to then for k,v in pairs(from) do if type(v) == "table" and type(to[k]) == "table" and v ~= to[k] then extend_table(v, to[k]) else to[k] = v end end end end extend_table(M, _G) end -- lua < 5.3 -- vi: set expandtab softtabstop=3 shiftwidth=3 :