local _G, _VERSION = _G, _VERSION
local lua_version = _VERSION:sub(-3)
local M = _G
if lua_version < "5.3" then
-- cache globals in upvalues
local error, ipairs, pairs, pcall, require, select, setmetatable, type =
error, ipairs, pairs, pcall, require, select, setmetatable, type
local debug, io, math, package, string, table =
debug, io, math, package, string, table
local io_lines = io.lines
local io_read = io.read
local unpack = lua_version == "5.1" and unpack or table.unpack
-- create module table
M = {}
local M_meta = {
__index = _G,
-- __newindex is set at the end
}
setmetatable(M, M_meta)
-- create subtables
M.io = setmetatable({}, { __index = io })
M.math = setmetatable({}, { __index = math })
M.string = setmetatable({}, { __index = string })
M.table = setmetatable({}, { __index = table })
M.utf8 = {}
-- select the most powerful getmetatable function available
local gmt = type(debug) == "table" and debug.getmetatable or
getmetatable or function() return false end
-- type checking functions
local checkinteger -- forward declararation
local function argcheck(cond, i, f, extra)
if not cond then
error("bad argument #"..i.." to '"..f.."' ("..extra..")", 0)
end
end
-- load utf8 library
local utf8_ok, utf8lib = pcall(require, "compat53.utf8")
if utf8_ok then
if lua_version == "5.1" then
utf8lib.charpattern = "[%z\1-\127\194-\244][\128-\191]*"
end
for k,v in pairs(utf8lib) do
M.utf8[k] = v
end
package.loaded["utf8"] = M.utf8
end
-- load table library
local table_ok, tablib = pcall(require, "compat53.table")
if table_ok then
for k,v in pairs(tablib) do
M.table[k] = v
end
end
-- load string packing functions
local str_ok, strlib = pcall(require, "compat53.string")
if str_ok then
for k,v in pairs(strlib) do
M.string[k] = v
end
end
-- try Roberto's struct module for string packing/unpacking if
-- compat53.string is unavailable
if not str_ok then
local struct_ok, struct = pcall(require, "struct")
if struct_ok then
M.string.pack = struct.pack
M.string.packsize = struct.size
M.string.unpack = struct.unpack
end
end
-- update math library
do
local maxint, minint = 1
while maxint+1 > maxint and 2*maxint > maxint do
maxint = maxint * 2
end
if 2*maxint <= maxint then
maxint = 2*maxint-1
minint = -maxint-1
else
maxint = maxint
minint = -maxint
end
M.math.maxinteger = maxint
M.math.mininteger = minint
function M.math.tointeger(n)
if type(n) == "number" and n <= maxint and n >= minint and n % 1 == 0 then
return n
end
return nil
end
function M.math.type(n)
if type(n) == "number" then
if n <= maxint and n >= minint and n % 1 == 0 then
return "integer"
else
return "float"
end
else
return nil
end
end
function checkinteger(x, i, f)
local t = type(x)
if t ~= "number" then
error("bad argument #"..i.." to '"..f..
"' (number expected, got "..t..")", 0)
elseif x > maxint or x < minint or x % 1 ~= 0 then
error("bad argument #"..i.." to '"..f..
"' (number has no integer representation)", 0)
else
return x
end
end
function M.math.ult(m, n)
m = checkinteger(m, "1", "math.ult")
n = checkinteger(n, "2", "math.ult")
if m >= 0 and n < 0 then
return true
elseif m < 0 and n >= 0 then
return false
else
return m < n
end
end
end
-- assert should allow non-string error objects
function M.assert(cond, ...)
if cond then
return cond, ...
elseif select('#', ...) > 0 then
error((...), 0)
else
error("assertion failed!", 0)
end
end
-- ipairs should respect __index metamethod
do
local function ipairs_iterator(st, var)
var = var + 1
local val = st[var]
if val ~= nil then
return var, st[var]
end
end
function M.ipairs(t)
if gmt(t) ~= nil then -- t has metatable
return ipairs_iterator, t, 0
else
return ipairs(t)
end
end
end
-- make '*' optional for io.read and io.lines
do
local function addasterisk(fmt)
if type(fmt) == "string" and fmt:sub(1, 1) ~= "*" then
return "*"..fmt
else
return fmt
end
end
function M.io.read(...)
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 io_read(unpack(args, 1, n))
end
end
return io_read(...)
end
-- PUC-Rio Lua 5.1 uses a different implementation for io.lines!
function M.io.lines(...)
local n = select('#', ...)
for i = 2, 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 io_lines(unpack(args, 1, n))
end
end
return io_lines(...)
end
end
-- update table library (if C module not available)
if not table_ok then
local table_concat = table.concat
local table_insert = table.insert
local table_remove = table.remove
local table_sort = table.sort
function M.table.concat(list, sep, i, j)
local mt = gmt(list)
if type(mt) == "table" and type(mt.__len) == "function" then
local src = list
list, i, j = {}, i or 1, j or mt.__len(src)
for k = i, j do
list[k] = src[k]
end
end
return table_concat(list, sep, i, j)
end
function M.table.insert(list, ...)
local mt = gmt(list)
local has_mt = type(mt) == "table"
local has_len = has_mt and type(mt.__len) == "function"
if has_mt and (has_len or mt.__index or mt.__newindex) then
local e = (has_len and mt.__len(list) or #list)+1
local nargs, pos, value = select('#', ...), ...
if nargs == 1 then
pos, value = e, pos
elseif nargs == 2 then
pos = checkinteger(pos, "2", "table.insert")
argcheck(1 <= pos and pos <= e, "2", "table.insert",
"position out of bounds" )
else
error("wrong number of arguments to 'insert'", 0)
end
for i = e-1, pos, -1 do
list[i+1] = list[i]
end
list[pos] = value
else
return table_insert(list, ...)
end
end
function M.table.move(a1, f, e, t, a2)
a2 = a2 or a1
f = checkinteger(f, "2", "table.move")
argcheck(f > 0, "2", "table.move",
"initial position must be positive")
e = checkinteger(e, "3", "table.move")
t = checkinteger(t, "4", "table.move")
if e >= f then
local m, n, d = 0, e-f, 1
if t > f then m, n, d = n, m, -1 end
for i = m, n, d do
a2[t+i] = a1[f+i]
end
end
return a2
end
function M.table.remove(list, pos)
local mt = gmt(list)
local has_mt = type(mt) == "table"
local has_len = has_mt and type(mt.__len) == "function"
if has_mt and (has_len or mt.__index or mt.__newindex) then
local e = (has_len and mt.__len(list) or #list)
pos = pos ~= nil and checkinteger(pos, "2", "table.remove") or e
if pos ~= e then
argcheck(1 <= pos and pos <= e+1, "2", "table.remove",
"position out of bounds" )
end
local result = list[pos]
while pos < e do
list[pos] = list[pos+1]
pos = pos + 1
end
list[pos] = nil
return result
else
return table_remove(list, pos)
end
end
do
local function pivot(list, cmp, a, b)
local m = b - a
if m > 2 then
local c = a + (m-m%2)/2
local x, y, z = list[a], list[b], list[c]
if not cmp(x, y) then
x, y, a, b = y, x, b, a
end
if not cmp(y, z) then
y, b = z, c
end
if not cmp(x, y) then
y, b = x, a
end
return b, y
else
return b, list[b]
end
end
local function lt_cmp(a, b)
return a < b
end
local function qsort(list, cmp, b, e)
if b < e then
local i, j, k, val = b, e, pivot(list, cmp, b, e)
while i < j do
while i < j and cmp(list[i], val) do
i = i + 1
end
while i < j and not cmp(list[j], val) do
j = j - 1
end
if i < j then
list[i], list[j] = list[j], list[i]
if i == k then k = j end -- update pivot position
i, j = i+1, j-1
end
end
if i ~= k and not cmp(list[i], val) then
list[i], list[k] = val, list[i]
k = i -- update pivot position
end
qsort(list, cmp, b, i == k and i-1 or i)
return qsort(list, cmp, i+1, e)
end
end
function M.table.sort(list, cmp)
local mt = gmt(list)
local has_mt = type(mt) == "table"
local has_len = has_mt and type(mt.__len) == "function"
if has_len then
cmp = cmp or lt_cmp
local len = mt.__len(list)
return qsort(list, cmp, 1, len)
else
return table_sort(list, cmp)
end
end
end
local function unpack_helper(list, i, j, ...)
if j < i then
return ...
else
return unpack_helper(list, i, j-1, list[j], ...)
end
end
function M.table.unpack(list, i, j)
local mt = gmt(list)
local has_mt = type(mt) == "table"
local has_len = has_mt and type(mt.__len) == "function"
if has_mt and (has_len or mt.__index) then
i, j = i or 1, j or (has_len and mt.__len(list)) or #list
return unpack_helper(list, i, j)
else
return unpack(list, i, j)
end
end
end -- update table library
-- bring Lua 5.1 (and LuaJIT) up to speed with Lua 5.2
if lua_version == "5.1" then
-- 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
-- cache globals in upvalues
local load, loadfile, loadstring, setfenv, xpcall =
load, loadfile, loadstring, setfenv, xpcall
local coroutine, os = coroutine, os
local coroutine_create = coroutine.create
local coroutine_resume = coroutine.resume
local coroutine_running = coroutine.running
local coroutine_status = coroutine.status
local coroutine_yield = coroutine.yield
local io_input = io.input
local io_open = io.open
local io_output = io.output
local io_write = io.write
local math_log = math.log
local os_execute = os.execute
local string_find = string.find
local string_format = string.format
local string_gmatch = string.gmatch
local string_gsub = string.gsub
local string_match = string.match
local string_rep = string.rep
local table_concat = table.concat
-- create subtables
M.coroutine = setmetatable({}, { __index = coroutine })
M.os = setmetatable({}, { __index = os })
M.package = setmetatable({}, { __index = package })
-- handle debug functions
if type(debug) == "table" then
local debug_setfenv = debug.setfenv
local debug_getfenv = debug.getfenv
local debug_setmetatable = debug.setmetatable
M.debug = setmetatable({}, { __index = debug })
if not is_luajit52 then
function M.debug.setuservalue(obj, value)
if type(obj) ~= "userdata" then
error("bad argument #1 to 'setuservalue' (userdata expected, got "..
type(obj)..")", 2)
end
if value == nil then value = _G end
if type(value) ~= "table" then
error("bad argument #2 to 'setuservalue' (table expected, got "..
type(value)..")", 2)
end
return debug_setfenv(obj, value)
end
function M.debug.getuservalue(obj)
if type(obj) ~= "userdata" then
return nil
else
local v = debug_getfenv(obj)
if v == _G or v == package then
return nil
end
return v
end
end
function M.debug.setmetatable(value, tab)
debug_setmetatable(value, tab)
return value
end
end -- not luajit with compat52 enabled
end -- debug table available
if not is_luajit52 then
function M.pairs(t)
local mt = gmt(t)
if type(mt) == "table" and type(mt.__pairs) == "function" then
return mt.__pairs(t)
else
return pairs(t)
end
end
end
if not is_luajit then
local function check_mode(mode, prefix)
local has = { text = false, binary = false }
for i = 1,#mode do
local c = mode:sub(i, i)
if c == "t" then has.text = true end
if c == "b" then has.binary = true end
end
local t = prefix:sub(1, 1) == "\27" and "binary" or "text"
if not has[t] then
return "attempt to load a "..t.." chunk (mode is '"..mode.."')"
end
end
function M.load(ld, source, mode, env)
mode = mode or "bt"
local chunk, msg
if type( ld ) == "string" then
if mode ~= "bt" then
local merr = check_mode(mode, ld)
if merr then return nil, merr end
end
chunk, msg = loadstring(ld, source)
else
local ld_type = type(ld)
if ld_type ~= "function" then
error("bad argument #1 to 'load' (function expected, got "..
ld_type..")", 2)
end
if mode ~= "bt" then
local checked, merr = false, nil
local function checked_ld()
if checked then
return ld()
else
checked = true
local v = ld()
merr = check_mode(mode, v or "")
if merr then return nil end
return v
end
end
chunk, msg = load(checked_ld, source)
if merr then return nil, merr end
else
chunk, msg = load(ld, source)
end
end
if not chunk then
return chunk, msg
end
if env ~= nil then
setfenv(chunk, env)
end
return chunk
end
M.loadstring = M.load
function M.loadfile(file, mode, env)
mode = mode or "bt"
if mode ~= "bt" then
local f = io_open(file, "rb")
if f then
local prefix = f:read(1)
f:close()
if prefix then
local merr = check_mode(mode, prefix)
if merr then return nil, merr end
end
end
end
local chunk, msg = loadfile(file)
if not chunk then
return chunk, msg
end
if env ~= nil then
setfenv(chunk, env)
end
return chunk
end
end -- not luajit
if not is_luajit52 then
function M.rawlen(v)
local t = type(v)
if t ~= "string" and t ~= "table" then
error("bad argument #1 to 'rawlen' (table or string expected)", 2)
end
return #v
end
end
if not is_luajit then
function M.xpcall(f, msgh, ...)
local args, n = { ... }, select('#', ...)
return xpcall(function() return f(unpack(args, 1, n)) end, msgh)
end
end
if not is_luajit52 then
function M.os.execute(cmd)
local code = os_execute(cmd)
-- Lua 5.1 does not report exit by signal.
if code == 0 then
return true, "exit", code
else
if package.config:sub(1, 1) == '/' then
code = code/256 -- only correct on Linux!
end
return nil, "exit", code
end
end
end
if not table_ok and not is_luajit52 then
M.table.pack = function(...)
return { n = select('#', ...), ... }
end
end
local main_coroutine = coroutine_create(function() end)
function M.coroutine.create(func)
local success, result = pcall(coroutine_create, func)
if not success then
if type(func) ~= "function" then
error("bad argument #1 (function expected)", 0)
end
result = coroutine_create(function(...) return func(...) end)
end
return result
end
if not is_luajit52 then
function M.coroutine.running()
local co = coroutine_running()
if co then
return co, false
else
return main_coroutine, true
end
end
end
function M.coroutine.yield(...)
local co, flag = coroutine_running()
if co and not flag then
return coroutine_yield(...)
else
error("attempt to yield from outside a coroutine", 0)
end
end
if not is_luajit then
function M.coroutine.resume(co, ...)
if co == main_coroutine then
return false, "cannot resume non-suspended coroutine"
else
return coroutine_resume(co, ...)
end
end
function M.coroutine.status(co)
local notmain = coroutine_running()
if co == main_coroutine then
return notmain and "normal" or "running"
else
return coroutine_status(co)
end
end
end -- not luajit
if not is_luajit then
M.math.log = function(x, base)
if base ~= nil then
return math_log(x)/math_log(base)
else
return math_log(x)
end
end
end
if not is_luajit then
function M.package.searchpath(name, path, sep, rep)
sep = (sep or "."):gsub("(%p)", "%%%1")
rep = (rep or package.config:sub(1, 1)):gsub("(%%)", "%%%1")
local pname = name:gsub(sep, rep):gsub("(%%)", "%%%1")
local msg = {}
for subpath in path:gmatch("[^;]+") do
local fpath = subpath:gsub("%?", pname)
local f = io_open(fpath, "r")
if f then
f:close()
return fpath
end
msg[#msg+1] = "\n\tno file '" .. fpath .. "'"
end
return nil, table_concat(msg)
end
end
local function fix_pattern(pattern)
return (string_gsub(pattern, "%z", "%%z"))
end
function M.string.find(s, pattern, ...)
return string_find(s, fix_pattern(pattern), ...)
end
function M.string.gmatch(s, pattern)
return string_gmatch(s, fix_pattern(pattern))
end
function M.string.gsub(s, pattern, ...)
return string_gsub(s, fix_pattern(pattern), ...)
end
function M.string.match(s, pattern, ...)
return string_match(s, fix_pattern(pattern), ...)
end
if not is_luajit then
function M.string.rep(s, n, sep)
if sep ~= nil and sep ~= "" and n >= 2 then
return s .. string_rep(sep..s, n-1)
else
return string_rep(s, n)
end
end
end
if not is_luajit then
do
local addqt = {
["\n"] = "\\\n",
["\\"] = "\\\\",
["\""] = "\\\""
}
local function addquoted(c, d)
return (addqt[c] or string_format(d~="" and "\\%03d" or "\\%d", c:byte()))..d
end
function M.string.format(fmt, ...)
local args, n = { ... }, select('#', ...)
local i = 0
local function adjust_fmt(lead, mods, kind)
if #lead % 2 == 0 then
i = i + 1
if kind == "s" then
args[i] = _G.tostring(args[i])
elseif kind == "q" then
args[i] = '"'..string_gsub(args[i], "([%z%c\\\"\n])(%d?)", addquoted)..'"'
return lead.."%"..mods.."s"
end
end
end
fmt = string_gsub(fmt, "(%%*)%%([%d%.%-%+%# ]*)(%a)", adjust_fmt)
return string_format(fmt, unpack(args, 1, n))
end
end
end
function M.io.write(...)
local res, msg, errno = io_write(...)
if res then
return io_output()
else
return nil, msg, errno
end
end
if not is_luajit then
local function helper(st, var_1, ...)
if var_1 == nil then
if st.doclose then st.f:close() end
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
function M.io.lines(fname, ...)
local doclose, file, msg
if fname ~= nil then
doclose, file, msg = true, io_open(fname, "r")
if not file then error(msg, 2) end
else
doclose, file = false, io_input()
end
local st = { f=file, doclose=doclose, 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 -- lua 5.1
-- further write should be forwarded to _G
M_meta.__newindex = _G
end -- lua < 5.3
-- return module table
return M
-- vi: set expandtab softtabstop=3 shiftwidth=3 :