412 lines
11 KiB
Lua
412 lines
11 KiB
Lua
-- elua core utilities used in other modules
|
|
|
|
local ffi = require("ffi")
|
|
local cast = ffi.cast
|
|
local new = ffi.new
|
|
local copy = ffi.copy
|
|
local str = ffi.string
|
|
local gc = ffi.gc
|
|
|
|
local C = ffi.C
|
|
|
|
local M = {}
|
|
|
|
local getmetatable, setmetatable = getmetatable, setmetatable
|
|
local dgetmt = debug.getmetatable
|
|
|
|
-- multiple inheritance index with depth-first search
|
|
local proto_lookup = function(protos, name)
|
|
if not protos then return nil end
|
|
for i = 1, #protos do
|
|
local proto = protos[i]
|
|
local v = proto[name]
|
|
if v ~= nil then
|
|
return v
|
|
end
|
|
end
|
|
end
|
|
|
|
local Object_MT = {
|
|
__index = function(self, name)
|
|
local v = proto_lookup(self.__mixins, name)
|
|
if v == nil then
|
|
return proto_lookup(self.__protos, name)
|
|
end
|
|
end,
|
|
|
|
__tostring = function(self)
|
|
local f = self["__tostring"]
|
|
if not f then
|
|
return ("Object: %s"):format(self.name or "unnamed")
|
|
end
|
|
return f(self)
|
|
end,
|
|
|
|
__call = function(self, ...)
|
|
return self["__call"](self, ...)
|
|
end
|
|
}
|
|
|
|
local obj_gc = function(px)
|
|
local obj = dgetmt(px).__obj
|
|
local dtor = obj and obj.__dtor or nil
|
|
if dtor then dtor(obj) end
|
|
end
|
|
|
|
M.Object = {
|
|
__enable_dtor = false,
|
|
|
|
__call = function(self, ...)
|
|
local r = self:clone()
|
|
if self.__enable_dtor then
|
|
local px = newproxy(true)
|
|
local pxmt = dgetmt(px)
|
|
r.__gcproxy = px
|
|
pxmt.__gc = obj_gc
|
|
pxmt.__obj = r
|
|
end
|
|
if self.__ctor then return r, self.__ctor(r, ...) end
|
|
return r
|
|
end,
|
|
|
|
clone = function(self, o)
|
|
o = o or {}
|
|
o.__protos, o.__mixins = { self }, {}
|
|
setmetatable(o, Object_MT)
|
|
return o
|
|
end,
|
|
|
|
is_a = function(self, base)
|
|
if self == base then return true end
|
|
local protos = self.__protos
|
|
for i = 1, #protos do
|
|
if protos[i]:is_a(base) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end,
|
|
|
|
add_parent = function(self, parent)
|
|
local protos = self.__protos
|
|
protos[#protos + 1] = parent
|
|
end,
|
|
|
|
add_mixin = function(self, mixin)
|
|
local mixins = self.__mixins
|
|
mixins[#mixins + 1] = mixin
|
|
end
|
|
}
|
|
|
|
local newproxy = newproxy
|
|
|
|
local robj_gc = function(px)
|
|
local dtor = px.__dtor
|
|
if dtor then dtor(px) end
|
|
end
|
|
|
|
M.Readonly_Object = M.Object:clone {}
|
|
M.Readonly_Object.__call = function(self, ...)
|
|
local r = newproxy(true)
|
|
local rmt = getmetatable(r)
|
|
rmt.__index = self
|
|
rmt.__tostring = Object_MT.__tostring
|
|
rmt.__metatable = false
|
|
if self.__enable_dtor then
|
|
rmt.__gc = robj_gc
|
|
end
|
|
if self.__ctor then return r, self.__ctor(r, rmt, ...) end
|
|
return r
|
|
end
|
|
|
|
local loaded_libs = {}
|
|
local loaded_libc = {}
|
|
|
|
local load_lib_win = function(libname, ev)
|
|
local succ, v
|
|
if not ev or ev == "" then
|
|
succ, v = pcall(ffi.load, libname)
|
|
if not succ then
|
|
succ, v = pcall(ffi.load, "lib" .. libname)
|
|
end
|
|
else
|
|
succ, v = pcall(ffi.load, ev .. "\\" .. libname .. ".dll")
|
|
if not succ then
|
|
succ, v = pcall(ffi.load, ev .. "\\lib" .. libname .. ".dll")
|
|
end
|
|
end
|
|
return succ, v
|
|
end
|
|
|
|
local load_lib = function(libname, ev)
|
|
local succ, v
|
|
if ffi.os == "Windows" then
|
|
succ, v = load_lib_win(libname, ev)
|
|
elseif not ev or ev == "" then
|
|
succ, v = pcall(ffi.load, libname)
|
|
else
|
|
local ext = (ffi.os == "OSX") and ".dylib" or ".so"
|
|
succ, v = pcall(ffi.load, ev .. "/lib" .. libname .. ext)
|
|
end
|
|
return succ, v
|
|
end
|
|
|
|
-- makes sure we only keep one handle for each lib
|
|
-- reference counted
|
|
M.lib_load = function(libname)
|
|
local lib = loaded_libs[libname]
|
|
if not lib then
|
|
local ev = os.getenv("ELUA_" .. libname:upper() .. "_LIBRARY_PATH")
|
|
local succ, v = load_lib(libname, ev)
|
|
if not succ then
|
|
error(v, 2)
|
|
end
|
|
lib = v
|
|
loaded_libs[libname] = lib
|
|
loaded_libc[libname] = 0
|
|
end
|
|
loaded_libc[libname] = loaded_libc[libname] + 1
|
|
return lib
|
|
end
|
|
|
|
M.lib_unload = function(libname)
|
|
local cnt = loaded_libc[libname]
|
|
if not cnt then return end
|
|
if cnt == 1 then
|
|
loaded_libs[libname], loaded_libc[libname] = nil, nil
|
|
else
|
|
loaded_libc[libname] = cnt - 1
|
|
end
|
|
end
|
|
|
|
-- string fmt
|
|
|
|
ffi.cdef [[
|
|
typedef struct _Str_Buf {
|
|
char *buf;
|
|
size_t len, cap;
|
|
} Str_Buf;
|
|
|
|
void *malloc(size_t);
|
|
void free(void*);
|
|
size_t strlen(const char *str);
|
|
|
|
int isalnum(int c);
|
|
int isdigit(int c);
|
|
]]
|
|
|
|
local fmt = string.format
|
|
local pcall = pcall
|
|
local error = error
|
|
local type = type
|
|
local tostr = tostring
|
|
|
|
local bytes = { ("cdeEfgGiopuxXsq"):byte(1, #("cdeEfgGiopuxXsq")) }
|
|
for i, v in ipairs(bytes) do bytes[v] = true end
|
|
|
|
local Str_Buf = ffi.metatype("Str_Buf", {
|
|
__new = function(self)
|
|
local r = new("Str_Buf")
|
|
r.buf = C.malloc(8)
|
|
r.len = 0
|
|
r.cap = 8
|
|
gc(r, self.free)
|
|
return r
|
|
end,
|
|
__tostring = function(self)
|
|
return str(self.buf, self.len)
|
|
end,
|
|
__index = {
|
|
free = function(self)
|
|
C.free(self.buf)
|
|
self.buf = nil
|
|
self.len = 0
|
|
self.cap = 0
|
|
end,
|
|
clear = function(self)
|
|
self.len = 0
|
|
end,
|
|
grow = function(self, newcap)
|
|
local oldcap = self.cap
|
|
if oldcap >= newcap then return end
|
|
local buf = C.malloc(newcap)
|
|
if self.len ~= 0 then copy(buf, self.buf, self.len) end
|
|
if self.buf ~= nil then C.free(self.buf) end
|
|
self.buf = buf
|
|
self.cap = newcap
|
|
end,
|
|
append_char = function(self, c)
|
|
local len = self.len
|
|
self:grow (len + 1)
|
|
self.buf [len] = c
|
|
self.len = len + 1
|
|
end,
|
|
append_str = function(self, str, strlen)
|
|
if type(str) == "string" then strlen = strlen or #str end
|
|
local strp = cast("const char*", str)
|
|
strlen = strlen or C.strlen(strp)
|
|
local len = self.len
|
|
self:grow(len + strlen)
|
|
for i = 0, strlen - 1 do
|
|
self.buf[len + i] = strp[i]
|
|
end
|
|
self.len = len + strlen
|
|
end
|
|
}
|
|
})
|
|
|
|
local fmterr = function(idx, msg, off)
|
|
local argerr = (type(idx) == "number")
|
|
and ("#" .. idx)
|
|
or ("'" .. idx .. "'")
|
|
error("bad argument " .. argerr .. " to '%' (" .. msg .. ")",
|
|
3 + (off or 0))
|
|
end
|
|
|
|
-- simulates lua's coercion
|
|
local checktype = function(c, idx, val)
|
|
if c == 115 or c == 112 then -- s, p
|
|
return val
|
|
end
|
|
local tv = type(val)
|
|
if c == 113 then -- q
|
|
if tv ~= "string" or tv ~= "number" then
|
|
fmterr(idx, "string expected, got " .. tv, 1)
|
|
end
|
|
return val
|
|
end
|
|
if tv == "number" then return val end
|
|
if tv == "string" then
|
|
local v = tonumber(val)
|
|
if v then return v end
|
|
end
|
|
fmterr(idx, "number expected, got " .. tv, 1)
|
|
end
|
|
|
|
getmetatable("").__mod = function(fmts, params)
|
|
if not fmts then return nil end
|
|
if type(params) ~= "table" then
|
|
params = { params }
|
|
end
|
|
|
|
local buf, nbuf = Str_Buf(), Str_Buf()
|
|
local s = cast("const char*", fmts)
|
|
local c, s = s[0], s + 1
|
|
local argn = 1
|
|
|
|
while c ~= 0 do
|
|
if c == 37 then -- %
|
|
c, s = s[0], s + 1
|
|
if c ~= 37 then
|
|
while c ~= 0 and C.isalnum(c) ~= 0 do
|
|
nbuf:append_char(c)
|
|
c, s = s[0], s + 1
|
|
end
|
|
if c == 36 then -- $
|
|
c, s = s[0], s + 1
|
|
local n = tostr(nbuf)
|
|
nbuf:clear()
|
|
while C.isdigit(c) ~= 0 or c == 45 or c == 46 do -- -, .
|
|
nbuf:append_char(c)
|
|
c, s = s[0], s + 1
|
|
end
|
|
if not bytes[c] then
|
|
buf:append_str(n)
|
|
buf:append_char(36) -- $
|
|
buf:append_char(c)
|
|
else
|
|
nbuf:append_char(c)
|
|
local idx = tonumber(n) or n
|
|
if type(idx) == "number" and idx > #params then
|
|
fmterr(idx, "no value")
|
|
end
|
|
local v = params[idx]
|
|
v = checktype(c, idx, v)
|
|
buf:append_str(("%" .. tostr(nbuf)):format(v))
|
|
nbuf:clear()
|
|
end
|
|
else
|
|
local fmtmark = (nbuf.len > 0) and nbuf.buf[0] or nil
|
|
if not fmtmark then
|
|
while c ~= 0 and (C.isdigit(c) ~= 0 or c == 45
|
|
or c == 46) do
|
|
nbuf:append_char(c)
|
|
c, s = s[0], s + 1
|
|
end
|
|
if bytes[c] then fmtmark = c end
|
|
end
|
|
if fmtmark then
|
|
if argn > #params then
|
|
fmterr(argn, "no value")
|
|
end
|
|
local v = params[argn]
|
|
v = checktype(fmtmark, argn, v)
|
|
buf:append_str(("%" .. tostr(nbuf)):format(v))
|
|
nbuf:clear()
|
|
argn = argn + 1
|
|
else
|
|
buf:append_str(nbuf.buf, nbuf.len)
|
|
nbuf:clear()
|
|
end
|
|
if c ~= 0 then buf:append_char(c) end
|
|
end
|
|
else
|
|
buf:append_char(c)
|
|
end
|
|
else
|
|
buf:append_char(c)
|
|
end
|
|
c, s = s[0], s + 1
|
|
end
|
|
nbuf:free()
|
|
local ret = tostr(buf)
|
|
buf:free()
|
|
return ret
|
|
end
|
|
|
|
-- file utils
|
|
|
|
M.find_file = function(fname, paths)
|
|
for i, path in ipairs(paths) do
|
|
local actual_path
|
|
if path:match(".*/") then
|
|
actual_path = path .. fname
|
|
else
|
|
actual_path = path .. "/" .. fname
|
|
end
|
|
local f = io.open(actual_path)
|
|
if f then
|
|
f:close()
|
|
return actual_path
|
|
end
|
|
end
|
|
end
|
|
|
|
-- table utils
|
|
|
|
table.uniq = function(tbl)
|
|
local ret = {}
|
|
local used = {}
|
|
for i, v in ipairs(tbl) do
|
|
if not used[v] then
|
|
ret[#ret + 1], used[v] = v, true
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
M.get_namespace = function(M, nspaces)
|
|
local last_m = M
|
|
for i, v in ipairs(nspaces) do
|
|
local nsp = M[v]
|
|
if not nsp then
|
|
nsp = {}
|
|
last_m[v] = nsp
|
|
end
|
|
last_m = nsp
|
|
end
|
|
return last_m
|
|
end
|
|
|
|
return M
|