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