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

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