mirror of
https://github.com/parnic/LibDogTag-3.0.git
synced 2025-06-16 12:10:13 -05:00
2311 lines
61 KiB
Lua
2311 lines
61 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 type, error, rawget, next, pairs, ipairs, setmetatable, loadstring, pcall, table, tonumber, tostring, math, assert, unpack =
|
|
type, error, rawget, next, pairs, ipairs, setmetatable, loadstring, pcall, table, tonumber, tostring, math, assert, unpack
|
|
-- #AUTODOC_NAMESPACE DogTag
|
|
|
|
DogTag_funcs[#DogTag_funcs+1] = function(DogTag)
|
|
|
|
local L = DogTag.L
|
|
|
|
local Tags = DogTag.Tags
|
|
local newList, newDict, newSet, del, deepCopy, deepDel = DogTag.newList, DogTag.newDict, DogTag.newSet, DogTag.del, DogTag.deepCopy, DogTag.deepDel
|
|
|
|
local fixNamespaceList = DogTag.fixNamespaceList
|
|
local select2 = DogTag.select2
|
|
local joinSet = DogTag.joinSet
|
|
local unpackNamespaceList = DogTag.unpackNamespaceList
|
|
local getASTType = DogTag.getASTType
|
|
local kwargsToKwargTypes = DogTag.kwargsToKwargTypes
|
|
local kwargsToKwargTypesWithTableCache = DogTag.kwargsToKwargTypesWithTableCache
|
|
local memoizeTable = DogTag.memoizeTable
|
|
local unparse, parse, standardize, codeToEventList, clearCodes
|
|
DogTag_funcs[#DogTag_funcs+1] = function()
|
|
unparse = DogTag.unparse
|
|
parse = DogTag.parse
|
|
standardize = DogTag.standardize
|
|
codeToEventList = DogTag.codeToEventList
|
|
clearCodes = DogTag.clearCodes
|
|
end
|
|
|
|
local compilationSteps
|
|
do
|
|
local mt = {__index = function(self, ns)
|
|
self[ns] = newList()
|
|
return self[ns]
|
|
end}
|
|
if DogTag.oldLib and DogTag.oldLib.compilationSteps then
|
|
compilationSteps = DogTag.oldLib.compilationSteps
|
|
setmetatable(compilationSteps.pre, mt)
|
|
compilationSteps.pre.Base = {}
|
|
setmetatable(compilationSteps.start, mt)
|
|
compilationSteps.start.Base = {}
|
|
setmetatable(compilationSteps.tag, mt)
|
|
compilationSteps.tag.Base = {}
|
|
setmetatable(compilationSteps.tagevents, mt)
|
|
compilationSteps.tagevents.Base = {}
|
|
setmetatable(compilationSteps.finish, mt)
|
|
compilationSteps.finish.Base = {}
|
|
else
|
|
compilationSteps = {}
|
|
compilationSteps.pre = setmetatable({ ['Base'] = {} }, mt)
|
|
compilationSteps.start = setmetatable({ ['Base'] = {} }, mt)
|
|
compilationSteps.tag = setmetatable({ ['Base'] = {} }, mt)
|
|
compilationSteps.tagevents = setmetatable({ ['Base'] = {} }, mt)
|
|
compilationSteps.finish = setmetatable({ ['Base'] = {} }, mt)
|
|
end
|
|
end
|
|
DogTag.compilationSteps = compilationSteps
|
|
|
|
local correctTagCasing = setmetatable({}, {__index = function(self, tag)
|
|
for ns, data in pairs(Tags) do
|
|
if data[tag] then
|
|
self[tag] = tag
|
|
return tag
|
|
end
|
|
end
|
|
|
|
local tag_lower = tag:lower()
|
|
for ns, data in pairs(Tags) do
|
|
for t in pairs(data) do
|
|
if tag_lower == t:lower() then
|
|
self[tag] = t
|
|
return t
|
|
end
|
|
end
|
|
end
|
|
self[tag] = tag
|
|
return tag
|
|
end})
|
|
|
|
local function correctASTCasing(ast)
|
|
if type(ast) ~= "table" then
|
|
return
|
|
end
|
|
local astType = ast[1]
|
|
if astType == "tag" or astType == "mod" then
|
|
ast[2] = correctTagCasing[ast[2]]
|
|
if ast.kwarg then
|
|
for k,v in pairs(ast.kwarg) do
|
|
correctASTCasing(v)
|
|
end
|
|
end
|
|
end
|
|
for i = 1, #ast do
|
|
correctASTCasing(ast[i])
|
|
end
|
|
end
|
|
DogTag.correctASTCasing = correctASTCasing
|
|
|
|
local codeToFunction
|
|
do
|
|
local codeToFunction_mt_mt = {__index = function(self, code)
|
|
if not code then
|
|
return self[""]
|
|
end
|
|
local nsList = self[1]
|
|
local kwargTypes = self[2]
|
|
|
|
local s, functions = DogTag:CreateFunctionFromCode(code, nsList, kwargTypes, true)
|
|
local func, err = loadstring(s)
|
|
local val
|
|
if not func then
|
|
local _, minor = LibStub(MAJOR_VERSION)
|
|
geterrorhandler()(("%s.%d: Error (%s) loading code %q. Please inform ckknight."):format(MAJOR_VERSION, minor, err, code))
|
|
val = self[""]
|
|
else
|
|
DogTag.__functions = functions
|
|
local status, result = pcall(func)
|
|
DogTag.__functions = nil
|
|
if not status then
|
|
local _, minor = LibStub(MAJOR_VERSION)
|
|
geterrorhandler()(("%s.%d: Error (%s) loading code %q. Please inform ckknight."):format(MAJOR_VERSION, minor, result, code))
|
|
val = self[""]
|
|
else
|
|
val = result
|
|
end
|
|
end
|
|
if functions then
|
|
functions = del(functions)
|
|
end
|
|
self[code] = val
|
|
return val
|
|
end}
|
|
local codeToFunction_mt = {__index = function(self, kwargTypes)
|
|
local t = setmetatable(newList(self[1], kwargTypes), codeToFunction_mt_mt)
|
|
self[kwargTypes] = t
|
|
return t
|
|
end}
|
|
codeToFunction = setmetatable({}, {__index = function(self, nsList)
|
|
local t = setmetatable(newList(nsList), codeToFunction_mt)
|
|
self[nsList] = t
|
|
return t
|
|
end})
|
|
end
|
|
DogTag.codeToFunction = codeToFunction
|
|
|
|
local operators = {
|
|
["+"] = 'plus',
|
|
["-"] = 'minus',
|
|
["*"] = 'times',
|
|
["/"] = 'divide',
|
|
["%"] = 'modulus',
|
|
["^"] = 'raise',
|
|
["<"] = 'less',
|
|
[">"] = 'greater',
|
|
["<="] = 'lessequal',
|
|
[">="] = 'greaterequal',
|
|
["="] = 'equal',
|
|
["~="] = 'inequal',
|
|
["unm"] = 'unm',
|
|
}
|
|
|
|
local figureCachedTags
|
|
do
|
|
function figureCachedTags(ast)
|
|
local cachedTags = newList()
|
|
if type(ast) ~= "table" then
|
|
return cachedTags
|
|
end
|
|
local astType = ast[1]
|
|
if astType == 'tag' or operators[astType] then
|
|
local tagName = astType == 'tag' and ast[2] or astType
|
|
if not cachedTags[tagName] then
|
|
cachedTags[tagName] = 0
|
|
end
|
|
if not ast.kwarg then
|
|
cachedTags[tagName] = cachedTags[tagName] + 1
|
|
else
|
|
for key, value in pairs(ast.kwarg) do
|
|
local data = figureCachedTags(value)
|
|
for k,v in pairs(data) do
|
|
cachedTags[k] = (cachedTags[k] or 0) + v
|
|
end
|
|
data = del(data)
|
|
end
|
|
end
|
|
end
|
|
for i = 2, #ast do
|
|
local data = figureCachedTags(ast[i])
|
|
for k, v in pairs(data) do
|
|
cachedTags[k] = (cachedTags[k] or 0) + v
|
|
end
|
|
data = del(data)
|
|
end
|
|
return cachedTags
|
|
end
|
|
end
|
|
|
|
local function enumLines(text)
|
|
text = text:gsub("\r\n", "\n"):gsub("\t", " ")
|
|
local lines = newList(("\n"):split(text))
|
|
local t = newList()
|
|
local indent = 0
|
|
for i, v in ipairs(lines) do
|
|
if v:match("end;?$") or v:match("else$") or v:match("^ *elseif") then
|
|
indent = indent - 1
|
|
end
|
|
for j = 1, indent do
|
|
t[#t+1] = " "
|
|
end
|
|
t[#t+1] = v:gsub(";\s*$", "")
|
|
t[#t+1] = " -- "
|
|
t[#t+1] = i
|
|
t[#t+1] = "\n"
|
|
if v:match("then$") or v:match("do$") or v:match("else$") or v:match("function%(.-%)") then
|
|
indent = indent + 1
|
|
end
|
|
end
|
|
lines = del(lines)
|
|
local s = table.concat(t)
|
|
t = del(t)
|
|
return s
|
|
end
|
|
|
|
local newUniqueVar, clearUniqueVars, getNumUniqueVars
|
|
do
|
|
local num = 0
|
|
local pool = {}
|
|
function newUniqueVar()
|
|
num = num + 1
|
|
return 'arg' .. num
|
|
end
|
|
function clearUniqueVars()
|
|
num = 0
|
|
end
|
|
function getNumUniqueVars()
|
|
return num
|
|
end
|
|
end
|
|
|
|
|
|
local function getTagData(tag, nsList)
|
|
for _, ns in ipairs(unpackNamespaceList[nsList]) do
|
|
local Tags_ns = Tags[ns]
|
|
if Tags_ns then
|
|
local Tags_ns_tag = Tags_ns[tag]
|
|
if Tags_ns_tag then
|
|
return Tags_ns_tag, ns
|
|
end
|
|
end
|
|
end
|
|
end
|
|
DogTag.getTagData = getTagData
|
|
|
|
local function getKwargsForAST(ast, nsList, extraKwargs)
|
|
local tag
|
|
if ast[1] == "tag" then
|
|
tag = ast[2]
|
|
else
|
|
tag = ast[1]
|
|
end
|
|
|
|
local tagData = getTagData(tag, nsList)
|
|
|
|
local arg = tagData.arg
|
|
if not arg then
|
|
return newList() -- no issue, but no point
|
|
end
|
|
|
|
local kwargs = newList()
|
|
if extraKwargs then
|
|
-- extra kwargs specified on fontstring registration, e.g. { unit = "player" }
|
|
for k,v in pairs(extraKwargs) do
|
|
kwargs[k] = extraKwargs
|
|
end
|
|
end
|
|
|
|
if ast.kwarg then
|
|
for k,v in pairs(ast.kwarg) do
|
|
kwargs[k] = v
|
|
end
|
|
end
|
|
|
|
return kwargs
|
|
end
|
|
|
|
local function mytonumber(value)
|
|
local type_value = type(value)
|
|
if type_value == "number" then
|
|
return value
|
|
elseif type_value ~= "string" then
|
|
return nil
|
|
end
|
|
if value:match("^0x") then
|
|
return nil
|
|
elseif value:match("%.$") or value:match("%.%d*0$") then
|
|
return nil
|
|
elseif value:match("^0%d+") then
|
|
return nil
|
|
elseif value:match("^%+") then
|
|
return nil
|
|
end
|
|
return tonumber(value)
|
|
end
|
|
DogTag.__mytonumber = mytonumber
|
|
|
|
local allOperators = {
|
|
["concat"] = true,
|
|
["and"] = true,
|
|
["or"] = true,
|
|
["if"] = true,
|
|
["not"] = true,
|
|
}
|
|
for k in pairs(operators) do
|
|
allOperators[k] = true
|
|
end
|
|
|
|
local function numberToString(num)
|
|
if type(num) ~= "number" then
|
|
return tostring(num)
|
|
elseif num == math.huge then
|
|
return "NaN"
|
|
elseif num == -math.huge then
|
|
return "-NaN"
|
|
elseif math.floor(num) == num then
|
|
return tostring(num)
|
|
else
|
|
return ("%.22f"):format(num):gsub("0+$", "")
|
|
end
|
|
end
|
|
|
|
local function forceTypes(storeKey, types, staticValue, forceToTypes, t)
|
|
types = newSet((";"):split(types))
|
|
forceToTypes = newSet((";"):split(forceToTypes))
|
|
if forceToTypes["undef"] then
|
|
forceToTypes["undef"] = nil
|
|
forceToTypes["nil"] = true
|
|
end
|
|
|
|
if types["boolean"] and staticValue == nil then
|
|
assert(type(storeKey) == "string" and (storeKey:match("^arg%d+$") or storeKey == "result"))
|
|
if not types["string"] then
|
|
if not types["number"] then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = not not ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
else
|
|
t[#t+1] = [=[if type(]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[) ~= "number" then]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = not not ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = "end;\n"
|
|
end
|
|
else
|
|
t[#t+1] = [=[if ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ == true then]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = ([=[%q]=]):format(L["True"])
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = "end;\n"
|
|
types["boolean"] = nil
|
|
types["nil"] = true
|
|
end
|
|
end
|
|
|
|
local unfulfilledTypes = newList()
|
|
local finalTypes = newList()
|
|
for k in pairs(types) do
|
|
if not forceToTypes[k] then
|
|
unfulfilledTypes[k] = true
|
|
else
|
|
finalTypes[k] = true
|
|
end
|
|
end
|
|
types = del(types)
|
|
if not next(unfulfilledTypes) then
|
|
unfulfilledTypes = del(unfulfilledTypes)
|
|
local types = joinSet(finalTypes, ';')
|
|
finalTypes = del(finalTypes)
|
|
forceToTypes = del(forceToTypes)
|
|
if type(storeKey) ~= "string" or (not storeKey:match("^arg%d+$") and storeKey ~= "result" and not storeKey:match("^%(.*%)$")) then
|
|
storeKey = "(" .. storeKey .. ")"
|
|
end
|
|
return storeKey, types, staticValue
|
|
end
|
|
if unfulfilledTypes['nil'] then
|
|
-- we have a possible unrequested nil
|
|
if forceToTypes['boolean'] then
|
|
if type(storeKey) == "string" and storeKey:match("^arg%d+$") then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
if unfulfilledTypes['number'] or unfulfilledTypes['string'] then
|
|
-- and a possible unrequested number or string
|
|
t[#t+1] = [=[not not ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
staticValue = nil
|
|
else
|
|
t[#t+1] = [=[false;]=]
|
|
t[#t+1] = "\n"
|
|
staticValue = false
|
|
end
|
|
else
|
|
assert(storeKey == "nil" or storeKey == "(nil)")
|
|
storeKey = "false"
|
|
staticValue = false
|
|
end
|
|
finalTypes['boolean'] = true
|
|
elseif forceToTypes['string'] then
|
|
if type(storeKey) == "string" and storeKey:match("^arg%d+$") then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
if unfulfilledTypes['number'] then
|
|
-- and a possible unrequested number
|
|
t[#t+1] = [=[tostring(]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ or '');]=]
|
|
t[#t+1] = "\n"
|
|
staticValue = nil
|
|
elseif forceToTypes['number'] then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ or '';]=]
|
|
t[#t+1] = "\n"
|
|
finalTypes['number'] = true
|
|
staticValue = nil
|
|
else
|
|
t[#t+1] = [=['';]=]
|
|
t[#t+1] = "\n"
|
|
staticValue = ''
|
|
end
|
|
else
|
|
if storeKey == "nil" then
|
|
storeKey = "''"
|
|
staticValue = ''
|
|
else
|
|
storeKey = tostring(storeKey or "''")
|
|
staticValue = nil
|
|
end
|
|
end
|
|
finalTypes['string'] = true
|
|
elseif forceToTypes['number'] then
|
|
if type(storeKey) == "string" and storeKey:match("^arg%d+$") then
|
|
if unfulfilledTypes["string"] then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = tonumber(]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[) or 0;]=]
|
|
t[#t+1] = "\n"
|
|
else
|
|
t[#t+1] = [=[if not ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ then]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = [=[0;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = "end;\n"
|
|
end
|
|
staticValue = nil
|
|
else
|
|
if storeKey == "nil" then
|
|
storeKey = "0"
|
|
staticValue = 0
|
|
else
|
|
staticValue = tonumber(storeKey) or 0
|
|
storeKey = numberToString(staticValue)
|
|
end
|
|
end
|
|
finalTypes['number'] = true
|
|
end
|
|
elseif unfulfilledTypes['number'] then
|
|
-- we have a possible unrequested number
|
|
if forceToTypes['boolean'] then
|
|
if type(storeKey) == "string" and storeKey:match("^arg%d+$") then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = true;]=]
|
|
t[#t+1] = "\n"
|
|
else
|
|
storeKey = "true"
|
|
end
|
|
finalTypes['boolean'] = true
|
|
staticValue = true
|
|
elseif forceToTypes['string'] then
|
|
if type(storeKey) == "string" and storeKey:match("^arg%d+$") then
|
|
if forceToTypes['nil'] then
|
|
t[#t+1] = [=[if ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ then]=]
|
|
t[#t+1] = "\n"
|
|
end
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = tostring(]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[);]=]
|
|
t[#t+1] = "\n"
|
|
if forceToTypes['nil'] then
|
|
t[#t+1] = "end;\n"
|
|
end
|
|
staticValue = nil
|
|
else
|
|
if not forceToTypes['nil'] and storeKey ~= 'nil' and storeKey ~= '(nil)' then
|
|
if storeKey:match("^%(.*%)$") then
|
|
storeKey = storeKey:sub(2, -2)+0
|
|
else
|
|
storeKey = storeKey+0
|
|
end
|
|
staticValue = tostring(storeKey)
|
|
storeKey = ("%q"):format(tostring(storeKey))
|
|
end
|
|
end
|
|
finalTypes['string'] = true
|
|
elseif forceToTypes['nil'] then
|
|
if type(storeKey) == "string" and storeKey:match("^arg%d+$") then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
t[#t+1] = "\n"
|
|
else
|
|
storeKey = "nil"
|
|
end
|
|
staticValue = "@nil"
|
|
finalTypes['nil'] = true
|
|
end
|
|
elseif unfulfilledTypes['string'] then
|
|
-- we have a possible unrequested string
|
|
if forceToTypes['boolean'] then
|
|
if type(storeKey) == "string" and storeKey:match("^arg%d+$") then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = true;]=]
|
|
t[#t+1] = "\n"
|
|
else
|
|
storeKey = "true"
|
|
end
|
|
staticValue = true
|
|
finalTypes['boolean'] = true
|
|
elseif forceToTypes['number'] then
|
|
if type(storeKey) == "string" and storeKey:match("^arg%d+$") then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = tonumber(]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[)]=]
|
|
if not forceToTypes['nil'] then
|
|
t[#t+1] = [=[ or 0]=]
|
|
else
|
|
finalTypes['nil'] = true
|
|
end
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
staticValue = nil
|
|
else
|
|
staticValue = tonumber(staticValue)
|
|
if not forceToTypes['nil'] and not staticValue then
|
|
staticValue = 0
|
|
end
|
|
storeKey = numberToString(staticValue)
|
|
end
|
|
finalTypes['number'] = true
|
|
elseif forceToTypes['nil'] then
|
|
if type(storeKey) == "string" and storeKey:match("^arg%d+$") then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
t[#t+1] = "\n"
|
|
else
|
|
storeKey = "nil"
|
|
end
|
|
finalTypes['nil'] = true
|
|
staticValue = "@nil"
|
|
end
|
|
elseif unfulfilledTypes["boolean"] then
|
|
if forceToTypes["string"] then
|
|
if staticValue == nil then
|
|
t[#t+1] = [=[if ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ then]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = ([=[%q]=]):format(L["True"])
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[else]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
if forceToTypes["nil"] then
|
|
t[#t+1] = [=[nil]=]
|
|
finalTypes['nil'] = true
|
|
else
|
|
t[#t+1] = [=['']=]
|
|
end
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = "end;\n"
|
|
finalTypes['string'] = true
|
|
else
|
|
if staticValue then
|
|
staticValue = L["True"]
|
|
storeKey = ("%q"):format(staticValue)
|
|
finalTypes['string'] = true
|
|
else
|
|
if forceToTypes['nil'] then
|
|
staticValue = "@nil"
|
|
storeKey = 'nil'
|
|
finalTypes['nil'] = true
|
|
else
|
|
staticValue = ""
|
|
storeKey = "''"
|
|
finalTypes['string'] = true
|
|
end
|
|
end
|
|
end
|
|
elseif forceToTypes["number"] then
|
|
if staticValue == nil then
|
|
t[#t+1] = [=[if ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ then]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = 1;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[else]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
if forceToTypes["nil"] then
|
|
t[#t+1] = [=[nil]=]
|
|
finalTypes['nil'] = true
|
|
else
|
|
t[#t+1] = [=[0]=]
|
|
end
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = "end;\n"
|
|
finalTypes['number'] = true
|
|
else
|
|
if staticValue then
|
|
staticValue = 1
|
|
storeKey = "1"
|
|
finalTypes['number'] = true
|
|
else
|
|
if forceToTypes['nil'] then
|
|
staticValue = "@nil"
|
|
storeKey = 'nil'
|
|
finalTypes['nil'] = true
|
|
else
|
|
staticValue = ""
|
|
storeKey = "0"
|
|
finalTypes['number'] = true
|
|
end
|
|
end
|
|
end
|
|
elseif forceToTypes["nil"] then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
t[#t+1] = "\n"
|
|
finalTypes['nil'] = true
|
|
staticValue = "@nil"
|
|
end
|
|
end
|
|
unfulfilledTypes = del(unfulfilledTypes)
|
|
forceToTypes = del(forceToTypes)
|
|
local types = joinSet(finalTypes, ';')
|
|
finalTypes = del(finalTypes)
|
|
if type(storeKey) ~= "string" or (not storeKey:match("^arg%d+$") and storeKey ~= "result" and not storeKey:match("^%(.*%)$")) then
|
|
storeKey = "(" .. storeKey .. ")"
|
|
end
|
|
return storeKey, types, staticValue
|
|
end
|
|
|
|
local function compile(ast, nsList, t, cachedTags, events, functions, extraKwargs, forceToTypes, storeKey, saveFirstArg)
|
|
if #t ~= 0 then
|
|
error(("Assertion failed: %s == %s"):format(#t, 0))
|
|
end
|
|
local astType = getASTType(ast)
|
|
if astType == 'nil' or ast == "@undef" then
|
|
if storeKey then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
t[#t+1] = "\n"
|
|
return forceTypes(storeKey, "nil", "@nil", forceToTypes, t)
|
|
else
|
|
return forceTypes("nil", "nil", "@nil", forceToTypes, t)
|
|
end
|
|
elseif astType == 'kwarg' then
|
|
local kwarg = extraKwargs[ast[2]]
|
|
local arg, types = kwarg[1], kwarg[2]
|
|
if storeKey then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = arg
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
return forceTypes(storeKey, types, nil, forceToTypes, t)
|
|
else
|
|
return forceTypes(arg, types, nil, forceToTypes, t)
|
|
end
|
|
elseif astType == 'string' then
|
|
if ast == '' then
|
|
return compile(nil, nsList, t, cachedTags, events, functions, extraKwargs, forceToTypes, storeKey)
|
|
else
|
|
if storeKey then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = ([=[%q]=]):format(ast)
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
return forceTypes(storeKey, "string", ast, forceToTypes, t)
|
|
else
|
|
return forceTypes(([=[%q]=]):format(ast), "string", ast, forceToTypes, t)
|
|
end
|
|
end
|
|
elseif astType == 'number' then
|
|
if storeKey then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = numberToString(ast)
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
return forceTypes(storeKey, "number", ast, forceToTypes, t)
|
|
else
|
|
return forceTypes(numberToString(ast), "number", ast, forceToTypes, t)
|
|
end
|
|
elseif astType == 'tag' or operators[astType] then
|
|
local tag = ast[astType == 'tag' and 2 or 1]
|
|
local tagData, tagNS = getTagData(tag, nsList)
|
|
if not storeKey then
|
|
storeKey = newUniqueVar()
|
|
end
|
|
local caching, cachingFirst
|
|
if astType == 'tag' and not ast.kwarg and cachedTags[tag] then
|
|
caching = true
|
|
cachingFirst = cachedTags[tag] == 1
|
|
cachedTags[tag] = 2
|
|
end
|
|
local static_t_num = #t
|
|
if caching and not cachingFirst then
|
|
t[#t+1] = [=[if cache_]=]
|
|
t[#t+1] = tag
|
|
t[#t+1] = [=[ ~= NIL then]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = cache_]=]
|
|
t[#t+1] = tag
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[else]=]
|
|
t[#t+1] = "\n"
|
|
else
|
|
t[#t+1] = [=[do]=]
|
|
t[#t+1] = "\n"
|
|
end
|
|
local kwargs = getKwargsForAST(ast, nsList, extraKwargs)
|
|
|
|
local arg = tagData.arg
|
|
|
|
local allArgsStatic = true
|
|
local compiledKwargs = newList()
|
|
local firstAndNonNil
|
|
local firstMaybeNumber = false
|
|
for k,v in pairs(kwargs) do
|
|
if v == extraKwargs then
|
|
compiledKwargs[k] = newList(unpack(extraKwargs[k]))
|
|
allArgsStatic = false
|
|
else
|
|
local argTypes = "nil;number;string;boolean"
|
|
local arg_num
|
|
local arg_default = false
|
|
if not k:match("^%.%.%.%d+$") then
|
|
for i = 1, #arg, 3 do
|
|
if arg[i] == k then
|
|
argTypes = arg[i+1]
|
|
arg_num = (i-1)/3 + 1
|
|
arg_default = arg[i+2]
|
|
break
|
|
end
|
|
end
|
|
else
|
|
for i = 1, #arg, 3 do
|
|
if arg[i] == "..." then
|
|
arg_num = (i-1)/3 + 1
|
|
if arg[i+1]:match("^tuple%-") then
|
|
argTypes = arg[i+1]:sub(7)
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if arg_num == 1 and (arg_default == "@req" or arg_default == "@undef") then
|
|
local a = newSet((";"):split(argTypes))
|
|
firstAndNonNil = not a["nil"] and not a["boolean"] and k
|
|
if firstAndNonNil then
|
|
a["undef"] = nil
|
|
a["nil"] = true
|
|
argTypes = joinSet(a, ";")
|
|
end
|
|
a = del(a)
|
|
end
|
|
local u = newList()
|
|
local arg, types, static
|
|
if arg_num == 1 then
|
|
local rawTypes
|
|
arg, rawTypes, static = compile(v, nsList, u, cachedTags, events, functions, extraKwargs, "boolean;nil;number;string")
|
|
arg, types, static = forceTypes(arg, rawTypes, static, argTypes, u)
|
|
local a = newSet((";"):split(rawTypes))
|
|
firstMaybeNumber = a['number'] and rawTypes
|
|
a = del(a)
|
|
else
|
|
arg, types, static = compile(v, nsList, u, cachedTags, events, functions, extraKwargs, argTypes)
|
|
end
|
|
for i,v in ipairs(u) do
|
|
t[#t+1] = v
|
|
end
|
|
u = del(u)
|
|
if static == nil then
|
|
allArgsStatic = false
|
|
end
|
|
if firstAndNonNil == k then
|
|
local returns = newSet((";"):split(types))
|
|
if v == "@undef" then
|
|
firstAndNonNil = nil
|
|
-- firstAndNonNil_t_num = nil -- unused
|
|
elseif not returns["nil"] then
|
|
firstAndNonNil = nil
|
|
-- firstAndNonNil_t_num = nil -- unused
|
|
elseif returns["string"] or returns["number"] then
|
|
-- firstAndNonNil_t_num = nil -- unused
|
|
end
|
|
returns = del(returns)
|
|
end
|
|
compiledKwargs[k] = newList(arg, types, static)
|
|
end
|
|
end
|
|
if firstAndNonNil then
|
|
local compiledKwargs_firstAndNonNil = compiledKwargs[firstAndNonNil]
|
|
t[#t+1] = [=[if ]=]
|
|
t[#t+1] = compiledKwargs_firstAndNonNil[1]
|
|
t[#t+1] = [=[ then]=]
|
|
t[#t+1] = "\n"
|
|
local args = newSet((';'):split(compiledKwargs_firstAndNonNil[2]))
|
|
args['nil'] = nil
|
|
compiledKwargs_firstAndNonNil[2] = joinSet(args, ';')
|
|
args = del(args)
|
|
end
|
|
|
|
local step_t_num = #t
|
|
for step in pairs(compilationSteps.tag[tagNS]) do
|
|
step(ast, t, tag, tagData, kwargs, extraKwargs, compiledKwargs)
|
|
end
|
|
if step_t_num ~= #t then
|
|
allArgsStatic = false
|
|
end
|
|
|
|
local passData = newList() -- data that will be passed into functions like ret, code, etc.
|
|
for k, v in pairs(kwargs) do
|
|
local passData_k = newList()
|
|
passData[k] = passData_k
|
|
if type(v) ~= "table" or v[1] == "nil" then
|
|
local value = type(v) ~= "table" and v or nil
|
|
passData_k.isLiteral = true
|
|
passData_k.value = value
|
|
passData_k.types = type(value)
|
|
else
|
|
passData_k.isLiteral = false
|
|
passData_k.value = v
|
|
passData_k.types = compiledKwargs[k][2]
|
|
end
|
|
end
|
|
|
|
local code = tagData.code
|
|
local ret = tagData.ret
|
|
local evs = tagData.events
|
|
local static = tagData.static
|
|
|
|
if type(ret) == "function" then
|
|
ret = ret(passData)
|
|
end
|
|
local funcName
|
|
if tagData.dynamicCode then
|
|
code = code(passData)
|
|
for k, v in pairs(functions) do
|
|
if v == code then
|
|
funcName = k
|
|
end
|
|
end
|
|
if not funcName then
|
|
local pre = (operators[tag] or tag) .. "_"
|
|
local num = 1
|
|
while functions[pre .. num] do
|
|
num = num + 1
|
|
end
|
|
funcName = pre .. num
|
|
functions[funcName] = code
|
|
end
|
|
else
|
|
functions[operators[tag] or tag] = tag
|
|
end
|
|
if type(evs) == "function" then
|
|
evs = evs(passData)
|
|
end
|
|
if type(static) == "function" then
|
|
static = static(passData)
|
|
end
|
|
for k, v in pairs(passData) do
|
|
passData[k] = del(v)
|
|
end
|
|
passData = del(passData)
|
|
|
|
evs = evs and newSet((";"):split(evs)) or newSet()
|
|
ret = ret and newSet((";"):split(ret)) or newSet("nil")
|
|
local u = newList()
|
|
for step in pairs(compilationSteps.tagevents[tagNS]) do
|
|
u[#u+1] = newList()
|
|
step(ast, t, u[#u], tag, tagData, kwargs, extraKwargs, compiledKwargs, evs, ret)
|
|
if not next(u[#u]) then
|
|
u[#u] = del(u[#u])
|
|
end
|
|
end
|
|
local r = joinSet(ret, ";")
|
|
ret = del(ret)
|
|
ret = r
|
|
local afterAdditions = newList()
|
|
for i = #u, 1, -1 do
|
|
for _, v in ipairs(u[i]) do
|
|
afterAdditions[#afterAdditions+1] = v
|
|
end
|
|
u[i] = del(u[i])
|
|
end
|
|
u = del(u)
|
|
|
|
for k in pairs(evs) do
|
|
local ev_params = newList(("#"):split(k))
|
|
local ev = ev_params[1]
|
|
local events_ev = events[ev]
|
|
if events_ev ~= true then
|
|
if #ev_params >= 2 then
|
|
for i = 2, #ev_params do
|
|
local param = ev_params[i]
|
|
if param:match("^%$") then
|
|
local real_param = param:sub(2)
|
|
local compiledKwargs_real_param = compiledKwargs[real_param]
|
|
if not compiledKwargs_real_param then
|
|
error(("Unknown event parameter %q for tag %s. Please inform ckknight."):format(real_param, tag))
|
|
end
|
|
local compiledKwargs_real_param_1 = compiledKwargs_real_param[1]
|
|
if not compiledKwargs_real_param_1:match("^kwargs_[a-z]+$") then
|
|
local kwargs_real_param = kwargs[real_param]
|
|
if type(kwargs_real_param) == "table" then
|
|
if kwargs_real_param[1] == "kwarg" then
|
|
param = "$" .. kwargs_real_param[2]
|
|
else
|
|
param = unparse(kwargs_real_param)
|
|
end
|
|
else
|
|
param = kwargs_real_param or true
|
|
end
|
|
ev_params[i] = param
|
|
end
|
|
end
|
|
end
|
|
local paramResult
|
|
if #ev_params == 2 then
|
|
paramResult = ev_params[2]
|
|
else
|
|
paramResult = table.concat(ev_params, '#', 2, #ev_params)
|
|
end
|
|
if type(events_ev) == "table" then
|
|
if paramResult == true then
|
|
del(events_ev)
|
|
events[ev] = true
|
|
else
|
|
events_ev[paramResult] = true
|
|
end
|
|
elseif events_ev and events_ev ~= paramResult then
|
|
if paramResult == true then
|
|
events[ev] = true
|
|
else
|
|
events[ev] = newSet(events_ev, paramResult)
|
|
end
|
|
else
|
|
events[ev] = paramResult
|
|
end
|
|
else
|
|
if type(events_ev) == "table" then
|
|
del(events_ev)
|
|
end
|
|
events[ev] = true
|
|
end
|
|
end
|
|
ev_params = del(ev_params)
|
|
end
|
|
evs = del(evs)
|
|
|
|
|
|
local savedArg, savedArgTypes, savedArgStatic
|
|
for k,v in pairs(compiledKwargs) do
|
|
if saveFirstArg and k == arg[1] then
|
|
savedArg = v[1]
|
|
savedArgTypes = v[2]
|
|
savedArgStatic = v[3]
|
|
end
|
|
end
|
|
|
|
if tagData.static and allArgsStatic then
|
|
local args = newList()
|
|
local argNum = 0
|
|
if tagData.arg then
|
|
local hasTuple = false
|
|
for i = 1, #tagData.arg, 3 do
|
|
local argName = tagData.arg[i]
|
|
if argName == "..." then
|
|
hasTuple = true
|
|
else
|
|
argNum = argNum + 1
|
|
local stat = compiledKwargs[argName][3]
|
|
if stat == "@nil" then
|
|
stat = nil
|
|
end
|
|
args[argNum] = stat
|
|
end
|
|
end
|
|
if hasTuple then
|
|
local j = 0
|
|
while true do
|
|
j = j + 1
|
|
local kwarg = compiledKwargs["..." .. j]
|
|
if not kwarg then
|
|
break
|
|
end
|
|
argNum = argNum + 1
|
|
local stat = kwarg[3]
|
|
if stat == "@nil" then
|
|
stat = nil
|
|
end
|
|
args[argNum] = stat
|
|
end
|
|
end
|
|
end
|
|
|
|
local result
|
|
if firstAndNonNil and not args[1] then
|
|
result = nil
|
|
else
|
|
result = code(unpack(args, 1, argNum))
|
|
end
|
|
args = del(args)
|
|
if firstMaybeNumber then
|
|
if mytonumber(result) then
|
|
result = result+0
|
|
end
|
|
end
|
|
local key
|
|
local type_result = type(result)
|
|
if type_result == "string" then
|
|
key = ("(%q)"):format(result)
|
|
else
|
|
key = ("(%s)"):format(numberToString(result))
|
|
end
|
|
for i = static_t_num+1, #t do
|
|
t[i] = nil
|
|
end
|
|
|
|
for k,v in pairs(compiledKwargs) do
|
|
compiledKwargs[k] = del(v)
|
|
end
|
|
compiledKwargs = del(compiledKwargs)
|
|
kwargs = del(kwargs)
|
|
|
|
local type_result = type(result)
|
|
if result == nil then
|
|
result = "@nil"
|
|
end
|
|
local a, b, c = forceTypes(key, type_result, result, forceToTypes, t)
|
|
return a, b, c, savedArg, savedArgTypes, savedArgStatic
|
|
end
|
|
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = " = tag_"
|
|
if funcName then
|
|
t[#t+1] = funcName
|
|
else
|
|
if operators[tag] then
|
|
t[#t+1] = operators[tag]
|
|
else
|
|
t[#t+1] = tag
|
|
end
|
|
end
|
|
t[#t+1] = "("
|
|
if tagData.arg then
|
|
local hasTuple = false
|
|
local first = true
|
|
for i = 1, #tagData.arg, 3 do
|
|
local argName = tagData.arg[i]
|
|
if argName == "..." then
|
|
hasTuple = true
|
|
else
|
|
if not first then
|
|
t[#t+1] = ", "
|
|
else
|
|
first = false
|
|
end
|
|
t[#t+1] = compiledKwargs[argName][1]
|
|
end
|
|
end
|
|
if hasTuple then
|
|
local j = 0
|
|
while true do
|
|
j = j + 1
|
|
local kwarg = compiledKwargs["..." .. j]
|
|
if not kwarg then
|
|
break
|
|
end
|
|
if not first then
|
|
t[#t+1] = ", "
|
|
else
|
|
first = false
|
|
end
|
|
t[#t+1] = kwarg[1]
|
|
end
|
|
end
|
|
end
|
|
t[#t+1] = [=[);]=]
|
|
t[#t+1] = "\n"
|
|
|
|
for i,v in ipairs(afterAdditions) do
|
|
t[#t+1] = v
|
|
end
|
|
afterAdditions = del(afterAdditions)
|
|
|
|
for k,v in pairs(compiledKwargs) do
|
|
compiledKwargs[k] = del(v)
|
|
end
|
|
compiledKwargs = del(compiledKwargs)
|
|
|
|
if firstAndNonNil then
|
|
t[#t+1] = "end;\n"
|
|
local returns = newSet((";"):split(ret))
|
|
returns["nil"] = true
|
|
ret = joinSet(returns, ";")
|
|
returns = del(returns)
|
|
end
|
|
|
|
if caching then
|
|
t[#t+1] = [=[cache_]=]
|
|
t[#t+1] = tag
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
end
|
|
t[#t+1] = "end;\n"
|
|
|
|
kwargs = del(kwargs)
|
|
if firstMaybeNumber then
|
|
local types = newSet((";"):split(forceToTypes))
|
|
if types['number'] then
|
|
local retData = newSet((";"):split(ret))
|
|
if retData['string'] and not retData['number'] then
|
|
t[#t+1] = [=[if mytonumber(]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[) then]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[+0;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = "end;\n"
|
|
retData['number'] = true
|
|
ret = joinSet(retData, ';')
|
|
end
|
|
retData = del(retData)
|
|
end
|
|
types = del(types)
|
|
end
|
|
local a, b, c = forceTypes(storeKey, ret, nil, forceToTypes, t)
|
|
return a, b, c, savedArg, savedArgTypes, savedArgStatic
|
|
elseif astType == "concat" then
|
|
local t_num = #t
|
|
local args = newList()
|
|
local argTypes = newList()
|
|
for i = 2, #ast do
|
|
local u = newList()
|
|
local arg, err = compile(ast[i], nsList, u, cachedTags, events, functions, extraKwargs, "nil;number;string")
|
|
if #u > 0 then
|
|
t[#t+1] = "do\n"
|
|
for i,v in ipairs(u) do
|
|
t[#t+1] = v
|
|
end
|
|
t[#t+1] = "end;\n"
|
|
end
|
|
u = del(u)
|
|
args[#args+1] = arg
|
|
argTypes[#argTypes+1] = err
|
|
end
|
|
if not storeKey then
|
|
storeKey = newUniqueVar()
|
|
end
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
local finalTypes = newList()
|
|
local lastCouldBeNil = false
|
|
for i,v in ipairs(args) do
|
|
if i > 1 then
|
|
t[#t+1] = [=[ .. ]=]
|
|
end
|
|
local types = argTypes[i]
|
|
types = newSet((';'):split(types))
|
|
if types['nil'] and (types['string'] or types['number']) then
|
|
t[#t+1] = "("
|
|
t[#t+1] = v
|
|
t[#t+1] = " or '')"
|
|
lastCouldBeNil = v
|
|
elseif types['nil'] then
|
|
-- just nil
|
|
t[#t+1] = "''"
|
|
lastCouldBeNil = true
|
|
else
|
|
-- non-nil
|
|
if lastCouldBeNil and v:match("^%(\"%s") then
|
|
t[#t+1] = "("
|
|
if lastCouldBeNil ~= true then
|
|
t[#t+1] = '(('
|
|
t[#t+1] = lastCouldBeNil
|
|
t[#t+1] = " or '') == ''"
|
|
t[#t+1] = ') and '
|
|
end
|
|
t[#t+1] = v:gsub("^%(\"%s", "(\"")
|
|
if lastCouldBeNil ~= true then
|
|
t[#t+1] = ' or '
|
|
t[#t+1] = v
|
|
end
|
|
t[#t+1] = ")"
|
|
else
|
|
t[#t+1] = v
|
|
end
|
|
lastCouldBeNil = nil
|
|
end
|
|
if types['nil'] then
|
|
if not next(finalTypes) then
|
|
finalTypes['nil'] = true
|
|
end
|
|
else
|
|
finalTypes['nil'] = nil
|
|
end
|
|
if types['number'] and not finalTypes['string'] then
|
|
if finalTypes['number'] then
|
|
finalTypes['string'] = true
|
|
end
|
|
finalTypes['number'] = true
|
|
end
|
|
if types['string'] then
|
|
if not types['number'] then
|
|
finalTypes['number'] = nil
|
|
end
|
|
finalTypes['string'] = true
|
|
end
|
|
types = del(types)
|
|
end
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
if lastCouldBeNil then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = (]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[):gsub("%s$", "");]=]
|
|
t[#t+1] = "\n"
|
|
end
|
|
if finalTypes['number'] then
|
|
t[#t+1] = [=[if mytonumber(]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[) then]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[+0;]=]
|
|
t[#t+1] = "\n"
|
|
end
|
|
if finalTypes['nil'] then
|
|
if finalTypes['number'] then
|
|
t[#t+1] = [=[elseif ]=]
|
|
else
|
|
t[#t+1] = [=[if ]=]
|
|
end
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ == '' then]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = "end;\n"
|
|
else
|
|
if finalTypes['number'] then
|
|
t[#t+1] = "end;\n"
|
|
end
|
|
end
|
|
args = del(args)
|
|
argTypes = del(argTypes)
|
|
local s = joinSet(finalTypes, ';')
|
|
finalTypes = del(finalTypes)
|
|
return forceTypes(storeKey, s, nil, forceToTypes, t) -- TODO: maybe static
|
|
elseif astType == 'and' or astType == 'or' then
|
|
if not storeKey then
|
|
storeKey = newUniqueVar()
|
|
end
|
|
local t_num = #t
|
|
local u = newList()
|
|
local arg, firstResults, staticValue = compile(ast[2], nsList, u, cachedTags, events, functions, extraKwargs, astType == 'and' and "boolean;nil;number;string" or "nil;number;string", storeKey)
|
|
firstResults = newSet((";"):split(firstResults))
|
|
local totalResults = newList()
|
|
if #u > 0 then
|
|
t[#t+1] = "do\n"
|
|
for i, v in ipairs(u) do
|
|
t[#t+1] = v
|
|
end
|
|
t[#t+1] = "end;\n"
|
|
end
|
|
u = del(u)
|
|
if firstResults["nil"] and not firstResults['boolean'] and not firstResults['string'] and not firstResults['number'] then
|
|
for i = t_num, #t do
|
|
t[i] = nil
|
|
end
|
|
if astType == 'or' then
|
|
local arg, secondResults
|
|
local u = newList()
|
|
arg, secondResults, staticValue = compile(ast[3], nsList, u, cachedTags, events, functions, extraKwargs, "nil;number;string", storeKey)
|
|
for i, v in ipairs(u) do
|
|
t[#t+1] = v
|
|
end
|
|
u = del(u)
|
|
secondResults = newSet((";"):split(secondResults))
|
|
for k in pairs(totalResults) do
|
|
totalResults[k] = nil
|
|
end
|
|
for k in pairs(secondResults) do
|
|
totalResults[k] = true
|
|
end
|
|
secondResults = del(secondResults)
|
|
else
|
|
staticValue = "@nil"
|
|
end
|
|
elseif firstResults["nil"] or firstResults['boolean'] then
|
|
t[#t+1] = [=[if ]=]
|
|
if astType == 'or' then
|
|
t[#t+1] = [=[not ]=]
|
|
end
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ then]=]
|
|
t[#t+1] = "\n"
|
|
if astType == 'and' then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
t[#t+1] = "\n"
|
|
end
|
|
local u = newList()
|
|
local arg, secondResults, static = compile(ast[3], nsList, u, cachedTags, events, functions, extraKwargs, "nil;number;string", storeKey)
|
|
for i, v in ipairs(u) do
|
|
t[#t+1] = v
|
|
end
|
|
u = del(u)
|
|
secondResults = newSet((";"):split(secondResults))
|
|
if static then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = arg
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
end
|
|
t[#t+1] = "end;\n"
|
|
if astType == 'and' then
|
|
for k in pairs(firstResults) do
|
|
if k == "nil" or k == "boolean" then
|
|
totalResults[k] = true
|
|
end
|
|
end
|
|
else
|
|
for k in pairs(firstResults) do
|
|
if k ~= "nil" and k ~= "boolean" then
|
|
totalResults[k] = true
|
|
end
|
|
end
|
|
end
|
|
for k in pairs(secondResults) do
|
|
totalResults[k] = true
|
|
end
|
|
secondResults = del(secondResults)
|
|
staticValue = nil
|
|
else
|
|
if astType == 'and' then
|
|
for i = t_num, #t do
|
|
t[i] = nil
|
|
end
|
|
local arg, secondResults
|
|
arg, secondResults, staticValue = compile(ast[3], nsList, t, cachedTags, events, functions, extraKwargs, "nil;number;string", storeKey)
|
|
secondResults = newSet((";"):split(secondResults))
|
|
for k in pairs(totalResults) do
|
|
totalResults[k] = nil
|
|
end
|
|
for k in pairs(secondResults) do
|
|
totalResults[k] = true
|
|
end
|
|
secondResults = del(secondResults)
|
|
else
|
|
for k in pairs(firstResults) do
|
|
totalResults[k] = true
|
|
end
|
|
end
|
|
end
|
|
firstResults = del(firstResults)
|
|
local s = joinSet(totalResults, ';')
|
|
totalResults = del(totalResults)
|
|
return forceTypes(storeKey, s, staticValue, forceToTypes, t)
|
|
elseif astType == 'if' then
|
|
if not storeKey then
|
|
storeKey = newUniqueVar()
|
|
end
|
|
local hasElse = not not ast[4]
|
|
local t_num = #t
|
|
local u = newList()
|
|
local storeKey, condResults, staticValue = compile(ast[2], nsList, u, cachedTags, events, functions, extraKwargs, "boolean;nil;number;string", storeKey)
|
|
condResults = newSet((';'):split(condResults))
|
|
if #u > 0 then
|
|
t[#t+1] = "do\n"
|
|
for i, v in ipairs(u) do
|
|
t[#t+1] = v
|
|
end
|
|
t[#t+1] = "end;\n"
|
|
end
|
|
u = del(u)
|
|
if condResults["boolean"] or (condResults["nil"] and (condResults["string"] or condResults["number"])) then
|
|
condResults = del(condResults)
|
|
t[#t+1] = [=[if ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ then]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
t[#t+1] = "\n"
|
|
local u = newList(u)
|
|
local arg, firstResults, static = compile(ast[3], nsList, u, cachedTags, events, functions, extraKwargs, forceToTypes, storeKey)
|
|
for i, v in ipairs(u) do
|
|
t[#t+1] = v
|
|
end
|
|
u = del(u)
|
|
if static then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = arg
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
end
|
|
local totalResults = newSet((";"):split(firstResults))
|
|
t[#t+1] = [=[else]=]
|
|
t[#t+1] = "\n"
|
|
local secondResults
|
|
if hasElse then
|
|
local u = newList()
|
|
storeKey, secondResults, static = compile(ast[4], nsList, u, cachedTags, events, functions, extraKwargs, forceToTypes, storeKey)
|
|
for i, v in ipairs(u) do
|
|
t[#t+1] = v
|
|
end
|
|
u = del(u)
|
|
if static then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = arg
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
end
|
|
else
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
t[#t+1] = "\n"
|
|
storeKey, secondResults = forceTypes(storeKey, "nil", "@nil", forceToTypes, t)
|
|
end
|
|
secondResults = newSet((";"):split(secondResults))
|
|
for k in pairs(secondResults) do
|
|
totalResults[k] = true
|
|
end
|
|
secondResults = del(secondResults)
|
|
t[#t+1] = "end;\n"
|
|
|
|
local s = joinSet(totalResults, ';')
|
|
totalResults = del(totalResults)
|
|
return forceTypes(storeKey, s, nil, forceToTypes, t)
|
|
elseif condResults["nil"] then
|
|
-- just nil
|
|
condResults = del(condResults)
|
|
for i = t_num, #t do
|
|
t[i] = nil
|
|
end
|
|
return compile(ast[4], nsList, t, cachedTags, events, functions, extraKwargs, forceToTypes, storeKey)
|
|
else
|
|
-- non-nil
|
|
condResults = del(condResults)
|
|
for i = t_num, #t do
|
|
t[i] = nil
|
|
end
|
|
return compile(ast[3], nsList, t, cachedTags, events, functions, extraKwargs, forceToTypes, storeKey)
|
|
end
|
|
elseif astType == 'not' then
|
|
local t_num = #t
|
|
local s, results, staticValue, savedArg, savedArgTypes, savedArgStatic = compile(ast[2], nsList, t, cachedTags, events, functions, extraKwargs, "boolean;nil;number;string", storeKey, true)
|
|
|
|
results = newSet((";"):split(results))
|
|
if results["boolean"] or (results["nil"] and (results["string"] or results["number"])) then
|
|
results = del(results)
|
|
storeKey = s
|
|
|
|
local types = newList()
|
|
|
|
if savedArg then
|
|
types["nil"] = true
|
|
t[#t+1] = [=[if ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ then]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[else]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = savedArg
|
|
savedArgTypes = newSet((";"):split(savedArgTypes))
|
|
if savedArgTypes["nil"] then
|
|
t[#t+1] = [=[ or ]=]
|
|
t[#t+1] = ("%q"):format(L["True"])
|
|
savedArgTypes["string"] = true
|
|
savedArgTypes["nil"] = nil
|
|
elseif savedArgTypes["boolean"] then
|
|
t[#t] = ("%q"):format(L["True"])
|
|
savedArgTypes["string"] = true
|
|
savedArgTypes["boolean"] = nil
|
|
end
|
|
for k in pairs(savedArgTypes) do
|
|
types[k] = true
|
|
end
|
|
savedArgTypes = del(savedArgTypes)
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = "end;\n"
|
|
else
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = not ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
types["boolean"] = true
|
|
end
|
|
local s = joinSet(types, ";")
|
|
types = del(types)
|
|
return forceTypes(storeKey, s, nil, forceToTypes, t)
|
|
elseif results["nil"] then
|
|
-- just nil
|
|
results = del(results)
|
|
|
|
if savedArg then
|
|
storeKey = s
|
|
|
|
local types = newList()
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = savedArg
|
|
savedArgTypes = newSet((";"):split(savedArgTypes))
|
|
local hasNil = savedArgTypes["nil"]
|
|
if hasNil then
|
|
t[#t+1] = [=[ or ]=]
|
|
t[#t+1] = ("%q"):format(L["True"])
|
|
savedArgTypes["string"] = true
|
|
savedArgTypes["nil"] = nil
|
|
end
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
for k in pairs(savedArgTypes) do
|
|
types[k] = true
|
|
end
|
|
savedArgTypes = del(savedArgTypes)
|
|
local s = joinSet(types, ';')
|
|
types = del(types)
|
|
return forceTypes(storeKey, s, not hasNil and savedArgStatic or nil, forceToTypes, t)
|
|
else
|
|
for i = t_num, #t do
|
|
t[i] = nil
|
|
end
|
|
if storeKey then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = ("%q"):format(L["True"])
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
return forceTypes(storeKey, "string", L["True"], forceToTypes, t)
|
|
else
|
|
return forceTypes(("%q"):format(L["True"]), "string", L["True"], forceToTypes, t)
|
|
end
|
|
end
|
|
else
|
|
-- non-nil
|
|
results = del(results)
|
|
|
|
for i = t_num, #t do
|
|
t[i] = nil
|
|
end
|
|
if storeKey then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
t[#t+1] = "\n"
|
|
return forceTypes(storeKey, "nil", "@nil", forceToTypes, t)
|
|
else
|
|
return forceTypes("nil", "nil", "@nil", forceToTypes, t)
|
|
end
|
|
end
|
|
elseif astType == '...' then
|
|
t[#t+1] = [=[do return "... used inappropriately" end;]=]
|
|
return "nil", "nil", nil
|
|
end
|
|
error(("Unknown astType: %q"):format(tostring(astType or '')))
|
|
end
|
|
|
|
local unalias
|
|
do
|
|
local function replaceArg(ast, argName, value)
|
|
local astType = getASTType(ast)
|
|
if astType ~= "tag" and not allOperators[astType] then
|
|
return
|
|
end
|
|
local argStart = astType == "tag" and 3 or 2
|
|
for i = argStart, #ast do
|
|
local v = ast[i]
|
|
local astType = getASTType(v)
|
|
if astType == "tag" and v[2] == argName then
|
|
deepDel(v)
|
|
ast[i] = deepCopy(value)
|
|
else
|
|
replaceArg(v, argName, value)
|
|
end
|
|
end
|
|
if ast.kwarg then
|
|
for k, v in pairs(ast.kwarg) do
|
|
local astType = getASTType(v)
|
|
if astType == "tag" and v[2] == argName then
|
|
deepDel(v)
|
|
ast.kwarg[k] = deepCopy(value)
|
|
else
|
|
replaceArg(v, argName, value)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function replaceTupleArg(ast, tupleArgs)
|
|
local astType = getASTType(ast)
|
|
if astType ~= "tag" and not allOperators[astType] then
|
|
return
|
|
end
|
|
local argStart = astType == "tag" and 3 or 2
|
|
for i = argStart, #ast do
|
|
local v = ast[i]
|
|
local astType = getASTType(v)
|
|
if astType == "..." then
|
|
deepDel(v)
|
|
ast[i] = nil
|
|
for j, u in ipairs(tupleArgs) do
|
|
ast[i+j-1] = u
|
|
end
|
|
break
|
|
else
|
|
replaceTupleArg(v, tupleArgs)
|
|
end
|
|
end
|
|
end
|
|
|
|
function unalias(ast, nsList, kwargTypes)
|
|
if type(ast) ~= "table" then
|
|
return ast
|
|
end
|
|
local astType = getASTType(ast)
|
|
for i = 2, #ast do
|
|
local err
|
|
ast[i], err = unalias(ast[i], nsList, kwargTypes)
|
|
if not ast[i] then
|
|
ast = deepDel(ast)
|
|
return nil, err
|
|
end
|
|
end
|
|
if astType ~= "tag" and not operators[astType] then
|
|
return ast
|
|
end
|
|
local isTag = astType == "tag"
|
|
local startArg = isTag and 3 or 2
|
|
local tag = isTag and ast[2] or astType
|
|
local tagData = getTagData(tag, nsList)
|
|
|
|
if not tagData or tagData.code then
|
|
for i = 2, #ast do
|
|
local err
|
|
ast[i], err = unalias(ast[i], nsList, kwargTypes)
|
|
if not ast[i] then
|
|
ast = deepDel(ast)
|
|
return nil, err
|
|
end
|
|
end
|
|
return ast
|
|
end
|
|
|
|
local alias = "[" .. tagData.alias .. "]"
|
|
local args = newList()
|
|
local tupleArgs = newList()
|
|
local extraKwargs = newList()
|
|
local arg = tagData.arg
|
|
if arg then
|
|
for i = 1, #arg, 3 do
|
|
local argName = arg[i]
|
|
if argName == "..." then
|
|
local num = 0
|
|
while true do
|
|
num = num + 1
|
|
local val = ast[(i-1)/3 + startArg - 1 + num]
|
|
if not val then
|
|
break
|
|
end
|
|
tupleArgs[num] = deepCopy(val)
|
|
end
|
|
break
|
|
else
|
|
local val = ast[(i-1)/3 + startArg] or ast.kwarg and ast.kwarg[argName]
|
|
if not val and kwargTypes[argName] then
|
|
val = newList("kwarg", argName)
|
|
extraKwargs[val] = true
|
|
end
|
|
if not val and arg[i+2] == "@req" then
|
|
tupleArgs = del(tupleArgs)
|
|
args = del(args)
|
|
ast = deepDel(ast)
|
|
extraKwargs = deepDel(extraKwargs)
|
|
return nil, ("Arg #%d (%s) req'd for %s"):format((i-1)/3+1, argName, tag)
|
|
end
|
|
if not val then
|
|
val = arg[i+2]
|
|
end
|
|
args[argName] = deepCopy(val)
|
|
end
|
|
end
|
|
end
|
|
local parsedAlias = parse(alias)
|
|
if not parsedAlias then
|
|
tupleArgs = deepDel(tupleArgs)
|
|
extraKwargs = deepDel(extraKwargs)
|
|
args = deepDel(args)
|
|
ast = deepDel(ast)
|
|
return nil, ("Syntax error with alias %s"):format(tag)
|
|
end
|
|
local parsedAlias = standardize(parsedAlias)
|
|
for k,v in pairs(args) do
|
|
replaceArg(parsedAlias, k, v)
|
|
end
|
|
replaceTupleArg(parsedAlias, tupleArgs)
|
|
deepDel(ast)
|
|
tupleArgs = del(tupleArgs)
|
|
args = del(args)
|
|
extraKwargs = deepDel(extraKwargs)
|
|
|
|
ast = parsedAlias
|
|
ast = standardize(ast)
|
|
correctASTCasing(ast)
|
|
return unalias(ast, nsList, kwargTypes)
|
|
end
|
|
end
|
|
|
|
local function readjustKwargs(ast, nsList, kwargTypes)
|
|
if type(ast) ~= "table" then
|
|
return ast
|
|
end
|
|
local astType = ast[1]
|
|
for i = 2, #ast do
|
|
local err
|
|
ast[i], err = readjustKwargs(ast[i], nsList, kwargTypes)
|
|
if ast[i] == nil then
|
|
ast = deepDel(ast)
|
|
return nil, err
|
|
end
|
|
end
|
|
if ast.kwarg then
|
|
for k,v in pairs(ast.kwarg) do
|
|
local err
|
|
ast.kwarg[k], err = readjustKwargs(v, nsList, kwargTypes)
|
|
if ast.kwarg[k] == nil then
|
|
ast = deepDel(ast)
|
|
return nil, err
|
|
end
|
|
end
|
|
end
|
|
if astType == "tag" or operators[astType] then
|
|
local start = astType == "tag" and 3 or 2
|
|
local tag = astType == "tag" and ast[2] or astType
|
|
local tagData = getTagData(tag, nsList)
|
|
if not tagData then
|
|
if kwargTypes[tag] then
|
|
ast[1] = "kwarg"
|
|
return ast
|
|
end
|
|
ast = deepDel(ast)
|
|
return nil, ("Unknown tag %s"):format(tostring(tag))
|
|
end
|
|
local arg = tagData.arg
|
|
if not ast.kwarg then
|
|
ast.kwarg = newList()
|
|
end
|
|
local arg_num = 0
|
|
local hitTuple = false
|
|
local ast_len = #ast
|
|
if arg then
|
|
arg_num = #arg
|
|
for i = 1, arg_num, 3 do
|
|
local argName = arg[i]
|
|
local default = arg[i+2]
|
|
if default == true then
|
|
default = L["True"]
|
|
end
|
|
if argName == "..." then
|
|
hitTuple = true
|
|
for j = start + ((i-1)/3), ast_len do
|
|
local num = j - start - ((i-1)/3) + 1
|
|
ast.kwarg["..." .. num] = ast[j]
|
|
ast[j] = nil
|
|
end
|
|
for j = i+3, arg_num, 3 do
|
|
argName = arg[j]
|
|
default = arg[j+2]
|
|
if not ast.kwarg[argName] and not kwargTypes[argName] then
|
|
if default == "@req" then
|
|
ast = deepDel(ast)
|
|
return nil, ("Keyword-Arg %s req'd for %s"):format(argName, tag)
|
|
end
|
|
ast.kwarg[argName] = default
|
|
end
|
|
end
|
|
break
|
|
else
|
|
local astVar = ast[start + ((i-1)/3)]
|
|
if not astVar then
|
|
if not ast.kwarg[argName] and not kwargTypes[argName] then
|
|
if default == "@req" then
|
|
ast = deepDel(ast)
|
|
return nil, ("Arg #%d (%s) req'd for %s"):format((i-1)/3 + 1, argName, tag)
|
|
end
|
|
ast.kwarg[argName] = default
|
|
end
|
|
else
|
|
ast.kwarg[argName] = astVar
|
|
ast[start + ((i-1)/3)] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if not hitTuple then
|
|
if arg_num/3 < (ast_len - start + 1) then
|
|
ast = deepDel(ast)
|
|
return nil, ("Too many args for %s"):format(tag)
|
|
end
|
|
end
|
|
if not next(ast.kwarg) then
|
|
ast.kwarg = del(ast.kwarg)
|
|
end
|
|
if #ast ~= start-1 then
|
|
error(("Assertion failed: %s == %s"):format(#ast, start-1))
|
|
end
|
|
end
|
|
return ast
|
|
end
|
|
|
|
--[[
|
|
Notes:
|
|
This is mostly used for debugging purposes
|
|
Arguments:
|
|
string - a tag sequence
|
|
[optional] string - a semicolon-separated list of namespaces. Base is implied
|
|
[optional] table - a dictionary of default kwargs for all tags in the code to receive
|
|
Returns:
|
|
string - a block of code which could have loadstring called on it.
|
|
Example:
|
|
local funcCode = LibStub("LibDogTag-3.0"):CreateFunctionFromCode("[Name]", "Unit", { unit = 'player' })
|
|
]]
|
|
function DogTag:CreateFunctionFromCode(code, nsList, kwargs, notDebug)
|
|
if type(code) ~= "string" then
|
|
error(("Bad argument #2 to `CreateFunctionFromCode'. Expected %q, got %q."):format("string", type(code)), 2)
|
|
elseif nsList and type(nsList) ~= "string" then
|
|
error(("Bad argument #3 to `CreateFunctionFromCode'. Expected %q, got %q."):format("string", type(nsList)), 2)
|
|
elseif kwargs and type(kwargs) ~= "table" then
|
|
error(("Bad argument #4 to `CreateFunctionFromCode'. Expected %q, got %q."):format("table", type(kwargs)), 2)
|
|
end
|
|
local kwargTypes
|
|
if notDebug then
|
|
kwargTypes = kwargs
|
|
else
|
|
kwargTypes = kwargsToKwargTypes[kwargs] -- NOT safe for kwargsToKwargTypesWithTableCache
|
|
kwargs = nil
|
|
nsList = fixNamespaceList[nsList]
|
|
end
|
|
codeToEventList[nsList][kwargTypes][code] = false
|
|
|
|
local ast = parse(code)
|
|
if not ast then
|
|
return ("return function() return %q, nil end"):format("Syntax error")
|
|
end
|
|
ast = standardize(ast)
|
|
correctASTCasing(ast)
|
|
local err
|
|
ast, err = unalias(ast, nsList, kwargTypes)
|
|
if not ast then
|
|
return ("return function() return %q, nil end"):format(tostring(err))
|
|
end
|
|
ast, err = readjustKwargs(ast, nsList, kwargTypes)
|
|
if not ast then
|
|
return ("return function() return %q, nil end"):format(tostring(err))
|
|
end
|
|
for _, ns in ipairs(unpackNamespaceList[nsList]) do
|
|
for step in pairs(compilationSteps.pre[ns]) do
|
|
ast, err = step(ast, kwargTypes)
|
|
if not ast then
|
|
return ("return function() return %q, nil end"):format(tostring(err))
|
|
end
|
|
end
|
|
end
|
|
|
|
local t = newList()
|
|
t[#t+1] = [=[local _G = _G;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = ([=[local DogTag = _G.LibStub(%q);]=]):format(MAJOR_VERSION)
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[local colors = DogTag.__colors;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[local NIL = DogTag.__NIL;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[local mytonumber = DogTag.__mytonumber;]=]
|
|
t[#t+1] = "\n"
|
|
local t_num = #t
|
|
t[#t+1] = [=[return function(kwargs)]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[local result;]=]
|
|
t[#t+1] = "\n"
|
|
local cachedTags = figureCachedTags(ast)
|
|
for k, v in pairs(cachedTags) do
|
|
if v >= 2 then
|
|
t[#t+1] = [=[local cache_]=]
|
|
t[#t+1] = k
|
|
t[#t+1] = [=[ = NIL;]=]
|
|
t[#t+1] = "\n"
|
|
cachedTags[k] = 1
|
|
else
|
|
cachedTags[k] = nil
|
|
end
|
|
end
|
|
|
|
local u = newList()
|
|
local extraKwargs = newList()
|
|
for k, v in pairs(kwargTypes) do
|
|
local arg = "kwargs_" .. k
|
|
u[#u+1] = [=[local ]=]
|
|
u[#u+1] = arg
|
|
u[#u+1] = [=[ = kwargs["]=]
|
|
u[#u+1] = k
|
|
u[#u+1] = [=["];]=]
|
|
u[#u+1] = "\n"
|
|
extraKwargs[k] = newList(arg, v)
|
|
end
|
|
|
|
for _, ns in ipairs(unpackNamespaceList[nsList]) do
|
|
for step in pairs(compilationSteps.start[ns]) do
|
|
step(u, ast, kwargTypes, extraKwargs)
|
|
end
|
|
end
|
|
|
|
local w = newList()
|
|
local events = newList()
|
|
local functions = newList()
|
|
|
|
local good, ret, types, static = pcall(compile, ast, nsList, w, cachedTags, events, functions, extraKwargs, 'nil;number;string', 'result')
|
|
if not good then
|
|
DogTag.tagError(code, nsList, ret)
|
|
end
|
|
|
|
for i, v in ipairs(w) do
|
|
u[#u+1] = v
|
|
end
|
|
w = del(w)
|
|
if not good then
|
|
u = del(u)
|
|
functions = del(functions)
|
|
events = del(events)
|
|
return ("return function() return %q end"):format(tostring(ret))
|
|
end
|
|
local w = newList()
|
|
for k, v in pairs(functions) do
|
|
w[#w+1] = [=[local tag_]=]
|
|
w[#w+1] = k
|
|
if type(v) == "string" then
|
|
w[#w+1] = [=[ = DogTag.Tags.]=]
|
|
local tagData, tagNS = getTagData(v, nsList)
|
|
w[#w+1] = tagNS
|
|
w[#w+1] = [=[[]=]
|
|
w[#w+1] = ("%q"):format(v)
|
|
w[#w+1] = [=[].code;]=]
|
|
w[#w+1] = "\n"
|
|
else
|
|
w[#w+1] = [=[ = DogTag.__functions.]=]
|
|
w[#w+1] = k
|
|
w[#w+1] = [=[;]=]
|
|
w[#w+1] = "\n"
|
|
end
|
|
end
|
|
for i, v in ipairs(w) do
|
|
table.insert(t, t_num+i, v)
|
|
end
|
|
w = del(w)
|
|
for k, v in pairs(extraKwargs) do
|
|
extraKwargs[k] = del(v)
|
|
end
|
|
if static then
|
|
if static == "@nil" then
|
|
static = nil
|
|
end
|
|
local literal
|
|
if type(static) == "string" then
|
|
if static == '' then
|
|
static = nil
|
|
elseif mytonumber(static) then
|
|
static = static+0
|
|
end
|
|
end
|
|
if type(static) == "string" then
|
|
literal = ("%q"):format(static)
|
|
elseif type(static) == "number" then
|
|
literal = numberToString(static)
|
|
else
|
|
literal = "nil"
|
|
end
|
|
|
|
events = del(events)
|
|
extraKwargs = del(extraKwargs)
|
|
ast = deepDel(ast)
|
|
cachedTags = del(cachedTags)
|
|
t = del(t)
|
|
u = del(u)
|
|
clearUniqueVars()
|
|
|
|
return ("return function() return %s end"):format(literal)
|
|
end
|
|
|
|
if not next(events) then
|
|
events = del(events)
|
|
else
|
|
events = memoizeTable(events)
|
|
codeToEventList[nsList][kwargTypes][code] = events
|
|
end
|
|
local num = getNumUniqueVars()
|
|
if num > 0 then
|
|
t[#t+1] = [=[local ]=]
|
|
for i = 1, getNumUniqueVars() do
|
|
if i > 1 then
|
|
t[#t+1] = [=[, ]=]
|
|
end
|
|
t[#t+1] = [=[arg]=]
|
|
t[#t+1] = i
|
|
end
|
|
t[#t+1] = [=[;]=]
|
|
t[#t+1] = "\n"
|
|
end
|
|
for _,v in ipairs(u) do
|
|
t[#t+1] = v
|
|
end
|
|
u = del(u)
|
|
clearUniqueVars()
|
|
|
|
types = newSet((";"):split(types))
|
|
if types["string"] then
|
|
t[#t+1] = "if type(result) == 'string' then\n"
|
|
-- t[#t+1] = "result = result:trim();\n"
|
|
-- t[#t+1] = "result = result:gsub(' +', ' ');\n"
|
|
t[#t+1] = "if result == '' then\n"
|
|
t[#t+1] = "result = nil;\n"
|
|
t[#t+1] = "elseif mytonumber(result) then\n"
|
|
t[#t+1] = "result = result+0;\n"
|
|
t[#t+1] = "end;\n"
|
|
t[#t+1] = "end;\n"
|
|
end
|
|
types = del(types)
|
|
|
|
for _, ns in ipairs(unpackNamespaceList[nsList]) do
|
|
for step in pairs(compilationSteps.finish[ns]) do
|
|
step(t, ast, kwargTypes, extraKwargs)
|
|
end
|
|
end
|
|
|
|
extraKwargs = del(extraKwargs)
|
|
ast = deepDel(ast)
|
|
|
|
t[#t+1] = [=[local opacity = DogTag.opacity;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[local outline = DogTag.outline;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[DogTag.opacity = nil;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[DogTag.outline = nil;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = [=[return result or nil, opacity, outline;]=]
|
|
t[#t+1] = "\n"
|
|
t[#t+1] = "end;\n"
|
|
|
|
cachedTags = del(cachedTags)
|
|
local s = table.concat(t)
|
|
t = del(t)
|
|
if not notDebug then
|
|
s = enumLines(s) -- avoid interning the new string if not debugging
|
|
functions = del(functions)
|
|
return s
|
|
else
|
|
return s, functions
|
|
end
|
|
end
|
|
|
|
local codeEvaluationTime_mt = {__index = function(self, kwargTypes)
|
|
local t = newList()
|
|
self[kwargTypes] = t
|
|
return t
|
|
end, __mode='k'}
|
|
local codeEvaluationTime = setmetatable({}, {__index = function(self, nsList)
|
|
local t = setmetatable(newList(), codeEvaluationTime_mt)
|
|
self[nsList] = t
|
|
return t
|
|
end})
|
|
DogTag.codeEvaluationTime = codeEvaluationTime
|
|
|
|
local function evaluate(code, nsList, kwargs, kwargTypes)
|
|
|
|
-- kwargTypes is passed in if calling from DogTag:Evaluate() because it cannot be cached,
|
|
-- but otherwise we should be able to safely get from the table cached version of kwargsToKwargTypes.
|
|
-- Just make sure that where ever evaluate() is called, a 'safe' kwargs table is passed in
|
|
-- (one that DogTag generated from memoizeTable, not one that was passed in externally from another addon)
|
|
kwargTypes = kwargTypes or kwargsToKwargTypesWithTableCache[kwargs]
|
|
|
|
DogTag.__isMouseOver = false
|
|
|
|
local func = codeToFunction[nsList][kwargTypes][code]
|
|
codeEvaluationTime[nsList][kwargTypes][code] = GetTime()
|
|
|
|
local madeKwargs = not kwargs
|
|
if madeKwargs then
|
|
kwargs = newList()
|
|
end
|
|
|
|
local success, text, opacity, outline = pcall(func, kwargs)
|
|
if not success then
|
|
DogTag.tagError(code, nsList, text)
|
|
return
|
|
end
|
|
|
|
if madeKwargs then
|
|
kwargs = del(kwargs)
|
|
end
|
|
if success then
|
|
return text, opacity, outline
|
|
end
|
|
end
|
|
DogTag.evaluate = evaluate
|
|
|
|
--[[
|
|
Arguments:
|
|
string - the tag sequence to compile and evaluate
|
|
[optional] string - a semicolon-separated list of namespaces. Base is implied
|
|
[optional] table - a dictionary of default kwargs for all tags in the code to receive
|
|
Returns:
|
|
string, number, or nil - the resultant generated text
|
|
number or nil - the expected opacity of the generated text, specified by the [Alpha(num)] tag. nil if not specified.
|
|
string - the outline style, can be "", "OUTLINE", or "OUTLINE, THICKOUTLINE"
|
|
Example:
|
|
local text = LibStub("LibDogTag-3.0"):Evaluate("[Name]", "Unit", { unit = 'player' })
|
|
]]
|
|
function DogTag:Evaluate(code, nsList, kwargs)
|
|
if type(code) ~= "string" then
|
|
error(("Bad argument #2 to `Evaluate'. Expected %q, got %q"):format("string", type(code)), 2)
|
|
elseif nsList and type(nsList) ~= "string" then
|
|
error(("Bad argument #3 to `Evaluate'. Expected %q, got %q"):format("string", type(nsList)), 2)
|
|
elseif kwargs and type(kwargs) ~= "table" then
|
|
error(("Bad argument #4 to `Evaluate'. Expected %q, got %q"):format("table", type(kwargs)), 2)
|
|
end
|
|
nsList = fixNamespaceList[nsList]
|
|
|
|
|
|
-- kwargTypes is passed into evaluate() instead of determined inside of evaluate() because
|
|
-- evaluate will obtain kwargTypes from kwargsToKwargTypesWithTableCache if it is not passed in.
|
|
-- This function's kwargs are not 'secure' and so there is no guarentee that the table isn't being
|
|
-- reused with different values and types. (see definition of kwargsToKwargTypesWithTableCache in Helpers.lua for an explanation)
|
|
|
|
local kwargTypes = kwargsToKwargTypes[kwargs]
|
|
|
|
return evaluate(code, nsList, kwargs, kwargTypes)
|
|
end
|
|
|
|
--[[
|
|
Note:
|
|
Add a step to the compilation process
|
|
This should only be done by sublibraries or addons that add tags
|
|
Arguments:
|
|
string - namespace to run the compilation step on
|
|
string - kind of compilation step, can be "pre", "start", "tag", "tagevents", or "finish"
|
|
function - the function to run
|
|
Example:
|
|
DogTag:AddCompilationStep("MyNamespace", "start", function(t, ast, kwargTypes, extraKwargs)
|
|
-- do something here
|
|
end)
|
|
]]
|
|
function DogTag:AddCompilationStep(namespace, kind, func)
|
|
if type(namespace) ~= "string" then
|
|
error(("Bad argument #2 to `AddCompilationStep'. Expected %q, got %q"):format("string", type(namespace)), 2)
|
|
end
|
|
if type(kind) ~= "string" then
|
|
error(("Bad argument #3 to `AddCompilationStep'. Expected %q, got %q"):format("string", type(kind)), 2)
|
|
elseif kind ~= "pre" and kind ~= "start" and kind ~= "tag" and kind ~= "tagevents" and kind ~= "finish" then
|
|
error(("Bad argument #3 to `AddCompilationStep'. Expected %q, %q, %q, %q, or %q, got %q"):format("pre", "start", "tag", "tagevents", "finish", kind), 2)
|
|
end
|
|
if type(func) ~= "function" then
|
|
error(("Bad argument #4 to `AddCompilationStep'. Expected %q, got %q"):format("function", type(func)), 2)
|
|
end
|
|
compilationSteps[kind][namespace][func] = true
|
|
clearCodes(namespace)
|
|
end
|
|
|
|
--[[
|
|
Note:
|
|
Remove a step from the compilation process
|
|
This should only be done by sublibraries or addons that add tags
|
|
Arguments:
|
|
string - namespace to run the compilation step on
|
|
string - kind of compilation step, can be "pre", "start", "tag", "tagevents", or "finish"
|
|
function - the function to run
|
|
Example:
|
|
DogTag:RemoveCompilationStep("MyNamespace", "start", func)
|
|
]]
|
|
function DogTag:RemoveCompilationStep(namespace, kind, func)
|
|
if type(namespace) ~= "string" then
|
|
error(("Bad argument #2 to `AddCompilationStep'. Expected %q, got %q"):format("string", type(namespace)), 2)
|
|
end
|
|
if type(kind) ~= "string" then
|
|
error(("Bad argument #3 to `AddCompilationStep'. Expected %q, got %q"):format("string", type(kind)), 2)
|
|
elseif kind ~= "pre" and kind ~= "start" and kind ~= "tag" and kind ~= "tagevents" and kind ~= "finish" then
|
|
error(("Bad argument #3 to `AddCompilationStep'. Expected %q, %q, %q, %q, or %q, got %q"):format("pre", "start", "tag", "tagevents", "finish", kind), 2)
|
|
end
|
|
if type(func) ~= "function" then
|
|
error(("Bad argument #4 to `AddCompilationStep'. Expected %q, got %q"):format("function", type(func)), 2)
|
|
end
|
|
compilationSteps[kind][namespace][func] = nil
|
|
clearCodes(namespace)
|
|
end
|
|
|
|
--[[
|
|
Note:
|
|
Remove all steps from the compilation process
|
|
This should only be done by sublibraries or addons that add tags
|
|
Arguments:
|
|
string - namespace to run the compilation step on
|
|
[optional] string - kind of compilation step, can be "pre", "start", "tag", "tagevents", or "finish", if not specified, then all kinds
|
|
Example:
|
|
DogTag:RemoveAllCompilationSteps("MyNamespace", "start")
|
|
DogTag:RemoveAllCompilationSteps("MyNamespace")
|
|
]]
|
|
function DogTag:RemoveAllCompilationSteps(namespace, kind)
|
|
if type(namespace) ~= "string" then
|
|
error(("Bad argument #3 to `AddCompilationStep'. Expected %q, got %q"):format("string", type(namespace)), 2)
|
|
end
|
|
if kind then
|
|
if type(kind) ~= "string" then
|
|
error(("Bad argument #3 to `AddCompilationStep'. Expected %q, got %q"):format("string", type(kind)), 2)
|
|
elseif kind ~= "pre" and kind ~= "start" and kind ~= "tag" and kind ~= "tagevents" and kind ~= "finish" then
|
|
error(("Bad argument #3 to `AddCompilationStep'. Expected %q, %q, %q, %q, or %q, got %q"):format("pre", "start", "tag", "tagevents", "finish", kind), 2)
|
|
end
|
|
local compilationSteps_kind_namespace = rawget(compilationSteps[kind], namespace)
|
|
if compilationSteps_kind_namespace then
|
|
compilationSteps[kind][namespace] = del(compilationSteps_kind_namespace)
|
|
end
|
|
else
|
|
for kind, data in pairs(compilationSteps) do
|
|
local data_namespace = rawget(data, namespace)
|
|
if data_namespace then
|
|
data[namespace] = del(data_namespace)
|
|
end
|
|
end
|
|
end
|
|
clearCodes(namespace)
|
|
end
|
|
|
|
end |