Files
libdogtag-3-0/Helpers.lua
2021-03-23 13:46:51 -05:00

527 lines
13 KiB
Lua

local MAJOR_VERSION = "LibDogTag-3.0"
local MINOR_VERSION = tonumber(("@project-date-integer@"):match("%d+")) or 33333333333333
if MINOR_VERSION > _G.DogTag_MINOR_VERSION then
_G.DogTag_MINOR_VERSION = MINOR_VERSION
end
local _G, pairs, ipairs, type, table, tostring, rawget, assert, next, select, error, setmetatable, unpack, next =
_G, pairs, ipairs, type, table, tostring, rawget, assert, next, select, error, setmetatable, unpack, next
-- #AUTODOC_NAMESPACE DogTag
DogTag_funcs[#DogTag_funcs+1] = function(DogTag)
local poolNum = 0
local newList, newDict, newSet, del, deepDel, deepCopy
do
local pool = setmetatable({}, {__mode='k'})
function newList(...)
poolNum = poolNum + 1
local t = next(pool)
if t then
pool[t] = nil
for i = 1, select('#', ...) do
t[i] = select(i, ...)
end
else
t = { ... }
end
-- if TABLE_DEBUG and pool == normalPool then
-- TABLE_DEBUG[#TABLE_DEBUG+1] = { '***', "newList", poolNum, tostring(t), debugstack() }
-- end
return t
end
function newDict(...)
poolNum = poolNum + 1
local t = next(pool)
if t then
pool[t] = nil
else
t = {}
end
for i = 1, select('#', ...), 2 do
t[select(i, ...)] = select(i+1, ...)
end
-- if TABLE_DEBUG and pool == normalPool then
-- TABLE_DEBUG[#TABLE_DEBUG+1] = { '***', "newDict", poolNum, tostring(t), debugstack() }
-- end
return t
end
function newSet(...)
poolNum = poolNum + 1
local t = next(pool)
if t then
pool[t] = nil
else
t = {}
end
for i = 1, select('#', ...) do
t[select(i, ...)] = true
end
-- if TABLE_DEBUG and pool == normalPool then
-- TABLE_DEBUG[#TABLE_DEBUG+1] = { '***', "newSet", poolNum, tostring(t), debugstack() }
-- end
return t
end
function del(t)
if type(t) ~= "table" then
error(("Bad argument #1 to `del'. Expected %q, got %q (%s)."):format("table", type(t), tostring(t)), 2)
end
if pool[t] then
error("Double-free syndrome.", 2)
end
pool[t] = true
poolNum = poolNum - 1
for k in pairs(t) do
t[k] = nil
end
setmetatable(t, nil)
t[''] = true
t[''] = nil
-- if TABLE_DEBUG then
-- local tostring_t = tostring(t)
-- TABLE_DEBUG[#TABLE_DEBUG+1] = { '***', "del", poolNum, tostring_t, debugstack() }
-- for _, line in ipairs(TABLE_DEBUG) do
-- if line[4] == tostring_t then
-- line[1] = ''
-- end
-- end
-- pool[t] = nil
-- end
return nil
end
local deepDel_data
function deepDel(t)
local made_deepDel_data = not deepDel_data
if made_deepDel_data then
deepDel_data = newList()
end
if type(t) == "table" and not deepDel_data[t] then
deepDel_data[t] = true
for k,v in pairs(t) do
deepDel(v)
deepDel(k)
end
del(t)
end
if made_deepDel_data then
deepDel_data = del(deepDel_data)
end
return nil
end
function deepCopy(t)
if type(t) ~= "table" then
return t
else
local u = newList()
for k, v in pairs(t) do
u[deepCopy(k)] = deepCopy(v)
end
return u
end
end
end
DogTag.newList, DogTag.newDict, DogTag.newSet, DogTag.del, DogTag.deepDel, DogTag.deepCopy = newList, newDict, newSet, del, deepDel, deepCopy
local DEBUG = _G.DogTag_DEBUG -- set in test.lua
if DEBUG then
DogTag.getPoolNum = function()
return poolNum
end
DogTag.setPoolNum = function(value)
poolNum = value
end
end
local function sortStringList(s)
if not s then
return nil
end
local set = newSet((";"):split(s))
local list = newList()
for k in pairs(set) do
list[#list+1] = k
end
set = del(set)
table.sort(list)
local q = table.concat(list, ';')
list = del(list)
return q
end
DogTag.sortStringList = sortStringList
local function select2(min, max, ...)
if min <= max then
return select(min, ...), select2(min+1, max, ...)
end
end
DogTag.select2 = select2
local function joinSet(set, connector)
local t = newList()
for k in pairs(set) do
t[#t+1] = k
end
table.sort(t)
local s = table.concat(t, connector)
t = del(t)
return s
end
DogTag.joinSet = joinSet
local fixNamespaceList = setmetatable({}, {__index = function(self, nsList)
if not nsList then
return self[""]
end
local t = newSet((";"):split(nsList))
t[""] = nil
t["Base"] = true
local s = joinSet(t, ';')
t = del(t)
self[nsList] = s
return s
end})
DogTag.fixNamespaceList = fixNamespaceList
local unpackNamespaceList = setmetatable({}, {__index = function(self, key)
local t = {(";"):split(key)}
self[key] = t
return t
end, __call = function(self, key)
return unpack(self[key])
end})
DogTag.unpackNamespaceList = unpackNamespaceList
local function getASTType(ast)
if not ast then
return "nil"
end
local type_ast = type(ast)
if type_ast ~= "table" then
return type_ast
end
return ast[1]
end
DogTag.getASTType = getASTType
local memoizeTable
do
local function key_sort(alpha, bravo)
local type_alpha, type_bravo = type(alpha), type(bravo)
if type_alpha ~= type_bravo then
return type_alpha < type_bravo
end
if type_alpha == "string" or type_alpha == "number" then
return alpha < bravo
elseif type_alpha == "boolean" then
return not alpha and bravo
elseif type_alpha == "table" then
local alpha_len, bravo_len = #alpha, #bravo
if alpha_len ~= bravo_len then
return alpha_len < bravo_len
end
for i, v in ipairs(alpha) do
local u = bravo[i]
local one = key_sort(v, u)
if not one then
local two = key_sort(u, v)
if two then
return false
end
else
return true
end
end
local alpha_klen, bravo_klen = 0, 0
for k in pairs(alpha) do
alpha_klen = alpha_klen + 1
end
for k in pairs(bravo) do
bravo_klen = bravo_klen + 1
end
if alpha_klen ~= bravo_klen then
return alpha_klen < bravo_klen
end
if alpha_klen ~= alpha_len then
local alpha_keys, bravo_keys = newList(), newList()
for k in pairs(alpha) do
alpha_keys[#alpha_keys+1] = k
end
table.sort(alpha_keys, key_sort)
for k in pairs(bravo) do
bravo_keys[#bravo_keys+1] = k
end
table.sort(bravo_keys, key_sort)
for i, k in ipairs(alpha_keys) do
local l = bravo_keys[i]
local one = key_sort(k, l)
if not one then
local two = key_sort(l, k)
if two then
alpha_keys, bravo_keys = del(alpha_keys), del(bravo_keys)
return false
end
else
alpha_keys, bravo_keys = del(alpha_keys), del(bravo_keys)
return true
end
local v, u = alpha[k], bravo[l]
local one = key_sort(v, u)
if not one then
local two = key_sort(u, v)
if two then
alpha_keys, bravo_keys = del(alpha_keys), del(bravo_keys)
return false
end
else
alpha_keys, bravo_keys = del(alpha_keys), del(bravo_keys)
return true
end
end
alpha_keys, bravo_keys = del(alpha_keys), del(bravo_keys)
end
return false
end
return false
end
local function tableToString(tab, t)
local type_tab = type(tab)
if type_tab ~= "table" then
if type_tab == "number" or type_tab == "string" then
t[#t+1] = tab
else
t[#t+1] = tostring(tab)
end
return
end
local keys = newList()
for k in pairs(tab) do
keys[#keys+1] = k
end
table.sort(keys, key_sort)
for _, k in ipairs(keys) do
local v = tab[k]
tableToString(k, t)
t[#t+1] = '='
tableToString(v, t)
t[#t+1] = ';'
end
keys = del(keys)
end
local pool = setmetatable({}, {__mode='v'})
function memoizeTable(tab)
if type(tab) ~= "table" then
return tab
end
local t = newList()
tableToString(tab, t)
local key = table.concat(t)
t = del(t)
local pool_key = pool[key]
if pool_key then
deepDel(tab)
return pool_key
else
pool[key] = tab
return tab
end
end
end
DogTag.memoizeTable = memoizeTable
local function deepCompare(t1,t2)
local ty1 = type(t1)
local ty2 = type(t2)
if ty1 ~= ty2 then return false end
-- non-table types can be directly compared
if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end
for k1,v1 in pairs(t1) do
local v2 = t2[k1]
if v2 == nil or not deepCompare(v1,v2) then return false end
end
for k2,v2 in pairs(t2) do
local v1 = t1[k2]
if v1 == nil or not deepCompare(v1,v2) then return false end
end
return true
end
DogTag.deepCompare = deepCompare
local kwargsToKwargTypes, kwargsToKwargTypesWithTableCache
do
kwargsToKwargTypes = setmetatable({}, { __index = function(self, kwargs)
if not kwargs then
return self[""]
elseif kwargs == "" then
local t = {}
self[false] = t
self[""] = t
return t
end
local kwargTypes = newList()
local keys = newList()
for k in pairs(kwargs) do
keys[#keys+1] = k
end
table.sort(keys)
local t = newList()
for i,k in ipairs(keys) do
if i > 1 then
t[#t+1] = ";"
end
local v = kwargs[k]
t[#t+1] = k
t[#t+1] = "="
local type_v = type(v)
t[#t+1] = type_v
kwargTypes[k] = type_v
end
keys = del(keys)
local s = table.concat(t)
t = del(t)
local self_s = rawget(self, s)
if self_s then
kwargTypes = del(kwargTypes)
return self_s
end
local t = {}
for k, v in pairs(kwargTypes) do
t[k] = v
end
kwargTypes = del(kwargTypes)
kwargTypes = t
self[s] = kwargTypes
return kwargTypes
end, __mode='kv' })
-- This is separate from kwargsToKwargTypes because in some cases,
-- a reused kwargs (reused by the addon providing it to DogTag)
-- will cause a possibly incorrect kwargTypes table to be returned.
-- This cache should only be used in places where we are certain that the kwargs
-- table is not being reused - this is usually true if:
-- We memoizeTable(deepCopy(kwargs))
-- Or if kwargs is obtained from fsToKwargs[fs] or callbackToKwargs[uid]
-- (because all kwargs in these tables have had memoizeTable(deepCopy(kwargs)) performed)
kwargsToKwargTypesWithTableCache = setmetatable({}, { __index = function(self, kwargs)
local kwargTypes = kwargsToKwargTypes[kwargs]
if not kwargs then
return kwargTypes
end
self[kwargs] = kwargTypes
return kwargTypes
end, __mode='kv' })
end
DogTag.kwargsToKwargTypes = kwargsToKwargTypes
DogTag.kwargsToKwargTypesWithTableCache = kwargsToKwargTypesWithTableCache
local codeToFunction, codeToEventList, callbackToNSList, callbackToCode, callbackToKwargTypes, eventData
local fsToNSList, fsToKwargs, fsToCode, fsNeedQuickUpdate
DogTag_funcs[#DogTag_funcs+1] = function()
codeToFunction = DogTag.codeToFunction
codeToEventList = DogTag.codeToEventList
callbackToNSList = DogTag.callbackToNSList
callbackToCode = DogTag.callbackToCode
callbackToKwargTypes = DogTag.callbackToKwargTypes
eventData = DogTag.eventData
fsToNSList = DogTag.fsToNSList
fsToKwargs = DogTag.fsToKwargs
fsToCode = DogTag.fsToCode
fsNeedQuickUpdate = DogTag.fsNeedQuickUpdate
end
local codesToClear = {}
local function _clearCodes()
if not next(codesToClear) then
return
end
for nsList, codeToFunction_nsList in pairs(codeToFunction) do
for _, ns in ipairs(unpackNamespaceList[nsList]) do
if codesToClear[ns] then
for kwargTypes, d in pairs(codeToFunction_nsList) do
if type(kwargTypes) ~= "number" then
codeToFunction_nsList[kwargTypes] = del(d)
end
end
codeToFunction[nsList] = del(codeToFunction_nsList)
break
end
end
end
for nsList, codeToEventList_nsList in pairs(codeToEventList) do
for _, ns in ipairs(unpackNamespaceList[nsList]) do
if codesToClear[ns] then
for kwargTypes, d in pairs(codeToEventList_nsList) do
if type(kwargTypes) ~= "number" then
codeToEventList_nsList[kwargTypes] = del(d)
end
end
codeToEventList[nsList] = del(codeToEventList_nsList)
break
end
end
end
for event, eventData_event in pairs(eventData) do
eventData[event] = del(eventData_event)
end
for uid, nsList in pairs(callbackToNSList) do
for _, ns in ipairs(unpackNamespaceList[nsList]) do
if codesToClear[ns] then
local kwargTypes = callbackToKwargTypes[uid]
local code = callbackToCode[uid]
local eventList = codeToEventList[nsList][kwargTypes][code]
if eventList == nil then
local _ = codeToFunction[nsList][kwargTypes][code]
eventList = codeToEventList[nsList][kwargTypes][code]
assert(eventList ~= nil)
end
break
end
end
end
for fs, nsList in pairs(fsToNSList) do
local kwargs = fsToKwargs[fs]
local code = fsToCode[fs]
local kwargTypes = kwargsToKwargTypesWithTableCache[kwargs]
local codeToEventList_nsList_kwargTypes_code = codeToEventList[nsList][kwargTypes][code]
if codeToEventList_nsList_kwargTypes_code == nil then
local _ = codeToFunction[nsList][kwargTypes][code]
codeToEventList_nsList_kwargTypes_code = codeToEventList[nsList][kwargTypes][code]
assert(codeToEventList_nsList_kwargTypes_code ~= nil)
end
if codeToEventList_nsList_kwargTypes_code then
for event, arg in pairs(codeToEventList_nsList_kwargTypes_code) do
eventData[event][fs] = arg
DogTag.eventUsed(event)
end
end
fsNeedQuickUpdate[fs] = true
end
for k in pairs(codesToClear) do
codesToClear[k] = nil
end
end
DogTag._clearCodes = _clearCodes
local function clearCodes(namespace)
codesToClear[namespace or ''] = true
end
DogTag.clearCodes = clearCodes
function DogTag.tagError(code, nsList, err)
local _, minor = LibStub(MAJOR_VERSION)
local message = ("%s.%d: Error with code %q (%s). %s"):format(MAJOR_VERSION, minor/1000000, code, nsList, err)
geterrorhandler()(message)
return message, code, nsList, err
end
end