mirror of
https://github.com/parnic/LibDogTag-3.0.git
synced 2025-06-16 20:20:14 -05:00
- note: this is not to be used yet, it is not finished, it is mostly so I can have a proper code history as well as allowing others to view what I have thus far. - Differences from 2.0: Different syntax, uses a proper LALR parser instead of regexes. Vigorous testing, no longer unit-specific, that will be part of its own namespace. Feel free to check out test.lua, it's pretty much a full testsuite for LibDogTag-3.0 thus far, and it will stay that way.
1295 lines
32 KiB
Lua
1295 lines
32 KiB
Lua
local MAJOR_VERSION = "LibDogTag-3.0"
|
|
local MINOR_VERSION = tonumber(("$Revision$"):match("%d+")) or 0
|
|
|
|
if MINOR_VERSION > _G.DogTag_MINOR_VERSION then
|
|
_G.DogTag_MINOR_VERSION = MINOR_VERSION
|
|
end
|
|
|
|
DogTag_funcs[#DogTag_funcs+1] = function()
|
|
|
|
local DogTag = _G.DogTag
|
|
|
|
local L = DogTag__L
|
|
|
|
local FakeGlobals = DogTag.FakeGlobals
|
|
local Tags = DogTag.Tags
|
|
local newList, newDict, newSet, del = DogTag.newList, DogTag.newDict, DogTag.newSet, DogTag.del
|
|
|
|
local function getNamespaceList(...)
|
|
local n = select('#', ...)
|
|
if n == 0 then
|
|
return "Base"
|
|
end
|
|
local t = newList()
|
|
t["Base"] = true
|
|
for i = 1, n do
|
|
local v = select(i, ...)
|
|
t[v] = true
|
|
end
|
|
local u = newList()
|
|
for k in pairs(t) do
|
|
u[#u+1] = k
|
|
end
|
|
t = del(t)
|
|
table.sort(u)
|
|
local value = table.concat(u, ';')
|
|
u = del(u)
|
|
return value
|
|
end
|
|
|
|
local function select2(min, max, ...)
|
|
if min <= max then
|
|
return select(min, ...), select2(min+1, max, ...)
|
|
end
|
|
end
|
|
|
|
local function joinSet(set, connector)
|
|
local t = newList()
|
|
for k in pairs(set) do
|
|
t[#t+1] = k
|
|
end
|
|
table.sort(t)
|
|
local s = table.concat(t, connector)
|
|
t = del(t)
|
|
return s
|
|
end
|
|
|
|
local unpackNamespaceList = setmetatable({}, {__index = function(self, key)
|
|
local t = newList((";"):split(key))
|
|
self[key] = t
|
|
return t
|
|
end, __call = function(self, key)
|
|
return unpack(self[key])
|
|
end})
|
|
|
|
local function getASTType(ast)
|
|
local type_ast = type(ast)
|
|
if type_ast ~= "table" then
|
|
return type_ast
|
|
end
|
|
return ast[1]
|
|
end
|
|
|
|
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
|
|
|
|
local kwargsKeyPool = { [""] = {} }
|
|
local function kwargsToKey(kwargs)
|
|
if not kwargs then
|
|
return kwargsKeyPool[""]
|
|
end
|
|
local kwargsKey = newList()
|
|
local keys = newList()
|
|
for k in pairs(kwargs) do
|
|
keys[#keys+1] = k
|
|
end
|
|
table.sort(keys)
|
|
local t = newList()
|
|
for i,k in ipairs(keys) do
|
|
if i > 1 then
|
|
t[#t+1] = ";"
|
|
end
|
|
local v = kwargs[k]
|
|
t[#t+1] = k
|
|
t[#t+1] = "="
|
|
local type_v = type(v)
|
|
t[#t+1] = type_v
|
|
kwargsKey[k] = type_v
|
|
end
|
|
keys = del(keys)
|
|
local s = table.concat(t)
|
|
t = del(t)
|
|
local kwargsKeyPool_s = kwargsKeyPool[s]
|
|
if kwargsKeyPool_s then
|
|
kwargsKey = del(kwargsKey)
|
|
return kwargsKeyPool_s
|
|
end
|
|
kwargsKeyPool[s] = kwargsKey
|
|
return kwargsKey
|
|
end
|
|
DogTag.kwargsToKey = kwargsToKey
|
|
|
|
local codeToFunction
|
|
do
|
|
local codeToFunction_mt_mt = {__index = function(self, code)
|
|
if not code then
|
|
return self[""]
|
|
end
|
|
local nsList = self[1]
|
|
local kwargsKey = self[2]
|
|
|
|
local s = DogTag:CreateFunctionFromCode(code, true, kwargsKey, unpackNamespaceList(nsList))
|
|
for i, ns in ipairs(unpackNamespaceList[nsList]) do
|
|
local data = FakeGlobals[ns]
|
|
if data then
|
|
for k, v in pairs(data) do
|
|
DogTag[k] = v
|
|
end
|
|
end
|
|
end
|
|
local func, err = loadstring(s)
|
|
local val
|
|
if not func then
|
|
geterrorhandler()(("%s: Error (%s) loading code %q. Please inform ckknight."):format(MAJOR_VERSION, err, code))
|
|
val = self[""]
|
|
else
|
|
local status, result = pcall(func)
|
|
if not status then
|
|
geterrorhandler()(("%s: Error (%s) running code %q. Please inform ckknight."):format(MAJOR_VERSION, result, code))
|
|
val = self[""]
|
|
else
|
|
val = result
|
|
end
|
|
end
|
|
for i, ns in ipairs(unpackNamespaceList[nsList]) do
|
|
local data = FakeGlobals[ns]
|
|
if data then
|
|
for k in pairs(data) do
|
|
DogTag[k] = nil
|
|
end
|
|
end
|
|
end
|
|
self[code] = val
|
|
return val
|
|
end}
|
|
local codeToFunction_mt = {__index = function(self, kwargsKey)
|
|
local t = setmetatable(newList(self[1], kwargsKey), codeToFunction_mt_mt)
|
|
self[kwargsKey] = 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 figureCachedTags
|
|
do
|
|
local function _figureCachedTags(ast)
|
|
local cachedTags = newList()
|
|
if type(ast) ~= "table" then
|
|
return cachedTags
|
|
end
|
|
local astType = ast[1]
|
|
if astType == 'tag' then
|
|
if #ast == 2 and not ast.kwarg then
|
|
local tagName = ast[2]
|
|
cachedTags[tagName] = (cachedTags[tagName] or 0) + 1
|
|
else
|
|
if ast.kwarg then
|
|
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
|
|
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
|
|
function figureCachedTags(ast)
|
|
local cachedTags = newList()
|
|
local data = _figureCachedTags(ast)
|
|
for k,v in pairs(data) do
|
|
if v > 1 then
|
|
cachedTags[k] = 1
|
|
end
|
|
end
|
|
data = del(data)
|
|
return cachedTags
|
|
end
|
|
end
|
|
|
|
local figureGlobals
|
|
do
|
|
function figureGlobals(ast, nsList, kwargsKey)
|
|
local globals = newList()
|
|
if type(ast) ~= "table" then
|
|
return globals
|
|
end
|
|
local astType = ast[1]
|
|
if astType == 'tag' then
|
|
local tag = ast[2]
|
|
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
|
|
local Tags_ns_tag_globals = Tags_ns_tag.globals
|
|
if Tags_ns_tag_globals then
|
|
local g = newList((";"):split(Tags_ns_tag_globals))
|
|
for _,v in ipairs(g) do
|
|
globals[v] = true
|
|
end
|
|
g = del(g)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if ast.kwarg then
|
|
for k,v in pairs(ast.kwarg) do
|
|
local g = figureGlobals(v, nsList, kwargsKey)
|
|
for k in pairs(g) do
|
|
globals[k] = true
|
|
end
|
|
g = del(g)
|
|
end
|
|
end
|
|
end
|
|
for i = 2, #ast do
|
|
local g = figureGlobals(ast[i], nsList, kwargsKey)
|
|
for k in pairs(g) do
|
|
globals[k] = true
|
|
end
|
|
g = del(g)
|
|
end
|
|
return globals
|
|
end
|
|
end
|
|
|
|
local function enumLines(text)
|
|
text = text:gsub(";", ";\n"):gsub("\r\n", "\n"):gsub("\t", " "):gsub("%f[A-Za-z_]do%f[^A-Za-z_]", "do\n"):gsub("%f[A-Za-z_]then%f[^A-Za-z_]", "then\n"):gsub("%f[A-Za-z_]else%f[^A-Za-z_]", "else\n"):gsub("\n *", "\n"):gsub("function(%b()) ", "function%1\n")
|
|
local lines = newList(('\n'):split(text))
|
|
local t = newList()
|
|
local indent = 0
|
|
for i = #lines, 1, -1 do
|
|
local v = lines[i]
|
|
if v:match("^%s*$") then
|
|
table.remove(lines, i)
|
|
end
|
|
end
|
|
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, delUniqueVar, clearUniqueVars, getNumUniqueVars
|
|
do
|
|
local num = 0
|
|
local pool = {}
|
|
function newUniqueVar()
|
|
local t = next(pool)
|
|
if t then
|
|
pool[t] = nil
|
|
return t
|
|
end
|
|
num = num + 1
|
|
return 'arg' .. num
|
|
end
|
|
function delUniqueVar(t)
|
|
pool[t] = true
|
|
end
|
|
function clearUniqueVars()
|
|
for k in pairs(pool) do
|
|
pool[k] = nil
|
|
end
|
|
num = 0
|
|
end
|
|
function getNumUniqueVars()
|
|
return num
|
|
end
|
|
end
|
|
|
|
|
|
local compile
|
|
|
|
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
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function getKwargsForAST(ast, nsList, extraKwargs)
|
|
if type(ast) ~= "table" then
|
|
return nil, ("%s is not a tag"):format(tostring(ast))
|
|
end
|
|
local tag, startArgs
|
|
if ast[1] == "tag" then
|
|
tag = ast[2]
|
|
startArgs = 3
|
|
else
|
|
tag = ast[1]
|
|
startArgs = 2
|
|
end
|
|
|
|
local tagData = getTagData(tag, nsList)
|
|
if not tagData then
|
|
return nil, ("Unknown tag %s"):format(tag)
|
|
end
|
|
|
|
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
|
|
|
|
for i = startArgs, #ast do
|
|
local argName = arg[(i-startArgs)*3 + 1]
|
|
local argTypes = arg[(i-startArgs)*3 + 2]
|
|
if argTypes == "list-string" or argTypes == "list-number" then
|
|
for j = i, #ast do
|
|
kwargs[argName .. (j-i+1)] = ast[j]
|
|
end
|
|
break
|
|
end
|
|
if not argName then
|
|
kwargs = del(kwargs)
|
|
return nil, ("Too many args for %s"):format(tag)
|
|
end
|
|
kwargs[argName] = ast[i]
|
|
end
|
|
|
|
if ast.kwarg then
|
|
for k,v in pairs(ast.kwarg) do
|
|
kwargs[k] = v
|
|
end
|
|
end
|
|
|
|
-- validate all args are met
|
|
for i = 1, #arg, 3 do
|
|
local argName, argType, default = arg[i], arg[i+1], arg[i+2]
|
|
|
|
if not kwargs[argName] and argType ~= "list-string" and argType ~= "list-number"then
|
|
if default == "@req" then
|
|
kwargs = del(kwargs)
|
|
return nil, ("Arg #%d (%s) req'd for %s"):format((i-1)/3+1, argName, tag)
|
|
else
|
|
kwargs[argName] = default
|
|
end
|
|
end
|
|
end
|
|
|
|
return kwargs
|
|
end
|
|
|
|
local interpolationHandler__data
|
|
local interpolationHandler__error
|
|
local function interpolationHandler(str)
|
|
local data = interpolationHandler__data
|
|
local ast, tagData, nsList, t, isOperator, afterStack, cachedTags, alreadyCompiled, kwargs, extraKwargs = unpack(data)
|
|
local arg = tagData.arg
|
|
local result
|
|
|
|
if str == '#...' then
|
|
local num = 1
|
|
while kwargs["..." .. num] ~= nil do
|
|
num = num + 1
|
|
end
|
|
return num-1
|
|
end
|
|
|
|
for i = 1, #arg, 3 do
|
|
local argName, argTypes, default = arg[i], arg[i+1], arg[i+2]
|
|
if argName == str then
|
|
local result = alreadyCompiled[argName]
|
|
if not result then
|
|
if kwargs[argName] ~= nil then
|
|
local types
|
|
if kwargs[argName] == extraKwargs then
|
|
result = extraKwargs[argName][1]
|
|
types = extraKwargs[argName][2]
|
|
else
|
|
result, types = compile(kwargs[argName] or nil, nsList, t, cachedTags, nil, extraKwargs)
|
|
if not result then
|
|
interpolationHandler__error = types
|
|
return nil
|
|
end
|
|
end
|
|
|
|
argTypes = newSet((";"):split(argTypes))
|
|
types = newSet((";"):split(types))
|
|
local unfulfilledTypes = newList()
|
|
for k in pairs(types) do
|
|
if not argTypes[k] then
|
|
unfulfilledTypes[k] = true
|
|
end
|
|
end
|
|
types = del(types)
|
|
if next(unfulfilledTypes) then
|
|
if unfulfilledTypes['nil'] then
|
|
-- we have a possible unrequested nil
|
|
if argTypes['string'] then
|
|
t[#t+1] = result
|
|
t[#t+1] = [=[ = ]=]
|
|
if unfulfilledTypes['number'] then
|
|
-- and a possible unrequested number
|
|
t[#t+1] = [=[tostring(]=]
|
|
t[#t+1] = result
|
|
t[#t+1] = [=[ or '');]=]
|
|
else
|
|
t[#t+1] = [=['';]=]
|
|
end
|
|
elseif argTypes['number'] then
|
|
t[#t+1] = [=[if not ]=]
|
|
t[#t+1] = result
|
|
t[#t+1] = [=[ then ]=]
|
|
t[#t+1] = result
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = [=[0;]=]
|
|
t[#t+1] = [=[end;]=]
|
|
end
|
|
elseif unfulfilledTypes['number'] then
|
|
-- we have a possible unrequested number
|
|
if argTypes['string'] then
|
|
if type(result) == "string" and result:match("^arg%d+$") then
|
|
t[#t+1] = result
|
|
t[#t+1] = [=[ = tostring(]=]
|
|
t[#t+1] = result
|
|
t[#t+1] = [=[)]=]
|
|
else
|
|
result = ("%q"):format(tostring(result+0))
|
|
end
|
|
elseif argTypes['nil'] then
|
|
if type(result) == "string" and result:match("^arg%d+$") then
|
|
t[#t+1] = result
|
|
t[#t+1] = [=[ = nil;]=]
|
|
else
|
|
result = "nil"
|
|
end
|
|
end
|
|
elseif unfulfilledTypes['string'] then
|
|
-- we have a possible unrequested string
|
|
if argTypes['number'] then
|
|
if type(result) == "string" and result:match("^arg%d+$") then
|
|
t[#t+1] = result
|
|
t[#t+1] = [=[ = tonumber(]=]
|
|
t[#t+1] = result
|
|
t[#t+1] = [=[)]=]
|
|
if not argTypes['nil'] then
|
|
t[#t+1] = [=[ or 0]=]
|
|
end
|
|
t[#t+1] = [=[;]=]
|
|
else
|
|
result = tonumber(result)
|
|
if not argTypes['nil'] and not result then
|
|
result = 0
|
|
end
|
|
result = tostring(result)
|
|
end
|
|
elseif argTypes['nil'] then
|
|
if type(result) == "string" and result:match("^arg%d+$") then
|
|
t[#t+1] = result
|
|
t[#t+1] = [=[ = nil]=]
|
|
else
|
|
result = "nil"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
unfulfilledTypes = del(unfulfilledTypes)
|
|
argTypes = del(argTypes)
|
|
|
|
if type(result) == "string" and result:match("^arg%d+$") then
|
|
afterStack[#afterStack+1] = result
|
|
else
|
|
result = ("(%s)"):format(result)
|
|
end
|
|
alreadyCompiled[argName] = result
|
|
elseif argTypes == "list-number" or argTypes == "list-string" then
|
|
local num = 0
|
|
local argList = newList()
|
|
while true do
|
|
num = num + 1
|
|
local argName_num = argName .. num
|
|
if not kwargs[argName_num] then
|
|
break
|
|
end
|
|
|
|
local res, types = compile(kwargs[argName_num] or nil, nsList, t, cachedTags, nil, extraKwargs)
|
|
|
|
if not res then
|
|
argList = del(argList)
|
|
interpolationHandler__error = types
|
|
return nil
|
|
end
|
|
|
|
types = newSet((";"):split(types))
|
|
local unfulfilledTypes = newList()
|
|
for k in pairs(types) do
|
|
if (k ~= "number" or argTypes ~= "list-number") and (k ~= "string" or argTypes ~= "list-string") then
|
|
unfulfilledTypes[k] = true
|
|
end
|
|
end
|
|
types = del(types)
|
|
|
|
if next(unfulfilledTypes) then
|
|
if unfulfilledTypes['nil'] then
|
|
-- we have a possible unrequested nil
|
|
if argTypes == "list-string" then
|
|
if type(res) == "string" and res:match("^arg%d+$") then
|
|
t[#t+1] = res
|
|
t[#t+1] = [=[ = ]=]
|
|
if unfulfilledTypes['number'] then
|
|
-- and a possible unrequested number
|
|
t[#t+1] = [=[tostring(]=]
|
|
t[#t+1] = res
|
|
t[#t+1] = [=[ or '');]=]
|
|
else
|
|
t[#t+1] = [=['';]=]
|
|
end
|
|
else
|
|
res = ("%q"):format(tostring(tonumber(res) or ''))
|
|
end
|
|
else
|
|
t[#t+1] = res
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = [=[0;]=]
|
|
end
|
|
elseif unfulfilledTypes['number'] then
|
|
-- we have a possible unrequested number
|
|
if argTypes == "list-string" then
|
|
if type(res) == "string" and res:match("^arg%d+$") then
|
|
t[#t+1] = res
|
|
t[#t+1] = [=[ = tostring(]=]
|
|
t[#t+1] = res
|
|
t[#t+1] = [=[)]=]
|
|
else
|
|
res = ("%q"):format(tostring(res+0))
|
|
end
|
|
end
|
|
elseif unfulfilledTypes['string'] then
|
|
-- we have a possible unrequested string
|
|
if argTypes == "list-number" then
|
|
if type(res) == "string" and res:match("^arg%d+$") then
|
|
t[#t+1] = res
|
|
t[#t+1] = [=[ = tonumber(]=]
|
|
t[#t+1] = res
|
|
t[#t+1] = [=[)]=]
|
|
if not argTypes['nil'] then
|
|
t[#t+1] = [=[ or 0]=]
|
|
end
|
|
t[#t+1] = [=[;]=]
|
|
else
|
|
res = tonumber(res)
|
|
if not argTypes['nil'] then
|
|
res = res or 0
|
|
end
|
|
res = tostring(res)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
unfulfilledTypes = del(unfulfilledTypes)
|
|
if type(res) == "string" and res:match("^arg%d+$") then
|
|
afterStack[#afterStack+1] = res
|
|
argList[#argList + 1] = res
|
|
else
|
|
argList[#argList + 1] = ("(%s)"):format(res)
|
|
end
|
|
end
|
|
result = table.concat(argList, ", ")
|
|
argList = del(argList)
|
|
alreadyCompiled[argName] = result
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
end
|
|
|
|
for i = 0, #arg-1, 3 do
|
|
local argName, argTypes, default = arg[i+1], arg[i+2], arg[i+3]
|
|
if argName == str then
|
|
local result = alreadyCompiled[argName]
|
|
if not result then
|
|
local err
|
|
result, err = compile(ast.kwarg and ast.kwarg[str] or ast[i/3 + (isOperator and 2 or 3)], nsList, t, cachedTags, nil, extraKwargs)
|
|
if not result then
|
|
interpolationHandler__error = err
|
|
return nil
|
|
end
|
|
alreadyCompiled[argName] = result
|
|
if type(result) == "string" and result:match("^arg%d+$") then
|
|
afterStack[#afterStack+1] = result
|
|
end
|
|
|
|
end
|
|
return result
|
|
end
|
|
end
|
|
end
|
|
|
|
local function tuple_interpolationHandler()
|
|
local result = interpolationHandler('...')
|
|
if result and result ~= '' then
|
|
return ", " .. result
|
|
else
|
|
return ''
|
|
end
|
|
end
|
|
|
|
local operators = {
|
|
["+"] = true,
|
|
["-"] = true,
|
|
["*"] = true,
|
|
["/"] = true,
|
|
["%"] = true,
|
|
["^"] = true,
|
|
["<"] = true,
|
|
[">"] = true,
|
|
["<="] = true,
|
|
[">="] = true,
|
|
["="] = true,
|
|
["~="] = true,
|
|
["unm"] = true,
|
|
}
|
|
|
|
function compile(ast, nsList, t, cachedTags, storeKey, extraKwargs)
|
|
local astType = getASTType(ast)
|
|
if astType == 'string' then
|
|
if ast == '' then
|
|
return compile(nil, nsList, t, cachedTags, storeKey, extraKwargs)
|
|
else
|
|
if storeKey then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = ([=[%q]=]):format(ast)
|
|
t[#t+1] = [=[;]=]
|
|
return storeKey, "string"
|
|
else
|
|
return ([=[%q]=]):format(ast), "string"
|
|
end
|
|
end
|
|
elseif astType == 'number' then
|
|
if storeKey then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = ("%.22f"):format(ast)
|
|
t[#t+1] = [=[;]=]
|
|
return storeKey, "number"
|
|
else
|
|
return ("%.22f"):format(ast), "number"
|
|
end
|
|
elseif astType == 'nil' then
|
|
if storeKey then
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = "nil"
|
|
t[#t+1] = [=[;]=]
|
|
return storeKey, "nil"
|
|
else
|
|
return "nil", "nil"
|
|
end
|
|
elseif astType == 'tag' or operators[astType] then
|
|
local tag = ast[astType == 'tag' and 2 or 1]
|
|
local tagData = getTagData(tag, nsList)
|
|
if not storeKey then
|
|
storeKey = newUniqueVar()
|
|
end
|
|
if tagData then
|
|
local caching, cachingFirst
|
|
if astType == 'tag' and #ast == 2 and not ast.kwarg and cachedTags[tag] then
|
|
caching = true
|
|
cachingFirst = cachedTags[tag] == 1
|
|
cachedTags[tag] = 2
|
|
end
|
|
if caching and not cachingFirst then
|
|
t[#t+1] = [=[if cache_]=]
|
|
t[#t+1] = tag
|
|
t[#t+1] = [=[ ~= NIL then ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = cache_]=]
|
|
t[#t+1] = tag
|
|
t[#t+1] = [=[; else ]=]
|
|
end
|
|
local code = tagData.code
|
|
local arg = tagData.arg
|
|
-- TODO: Check for arguments
|
|
code = code:gsub("return ", storeKey .. " = ")
|
|
local afterStack = newList()
|
|
local alreadyCompiled = newList()
|
|
local kwargs, errMessage = getKwargsForAST(ast, nsList, extraKwargs)
|
|
if not kwargs then
|
|
afterStack = del(afterStack)
|
|
alreadyCompiled = del(alreadyCompiled)
|
|
return nil, errMessage
|
|
end
|
|
local data = newList(ast, tagData, nsList, t, astType ~= 'tag', afterStack, cachedTags, alreadyCompiled, kwargs, extraKwargs)
|
|
local prev_interpolationHandler__data = interpolationHandler__data
|
|
interpolationHandler__data = data
|
|
code = code:gsub(",%s*${%.%.%.}", tuple_interpolationHandler)
|
|
interpolationHandler__data = data
|
|
code = code:gsub("${(.-)}", interpolationHandler)
|
|
interpolationHandler__data = prev_interpolationHandler__data
|
|
data = del(data)
|
|
alreadyCompiled = del(alreadyCompiled)
|
|
kwargs = del(kwargs)
|
|
if interpolationHandler__error then
|
|
afterStack = del(afterStack)
|
|
local err = interpolationHandler__error
|
|
interpolationHandler__error = nil
|
|
return nil, err
|
|
end
|
|
t[#t+1] = code
|
|
t[#t+1] = [=[;]=]
|
|
for i,v in ipairs(afterStack) do
|
|
t[#t+1] = v
|
|
delUniqueVar(v)
|
|
t[#t+1] = [=[ = nil;]=]
|
|
end
|
|
afterStack = del(afterStack)
|
|
if caching then
|
|
t[#t+1] = [=[cache_]=]
|
|
t[#t+1] = tag
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[;]=]
|
|
if not cachingFirst then
|
|
t[#t+1] = [=[end;]=]
|
|
end
|
|
end
|
|
return storeKey, tagData.ret
|
|
else
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = ("%q"):format(("Unknown tag %s"):format(tag))
|
|
t[#t+1] = [=[;]=]
|
|
return storeKey, "string"
|
|
end
|
|
elseif astType == ' ' then
|
|
local t_num = #t
|
|
local args = newList()
|
|
local argTypes = newList()
|
|
for i = 2, #ast do
|
|
local t_num = #t
|
|
local arg, err = compile(ast[i], nsList, t, cachedTags, nil, extraKwargs)
|
|
if not arg then
|
|
args = del(args)
|
|
argTypes = del(argTypes)
|
|
return arg, err
|
|
end
|
|
args[#args+1] = arg
|
|
argTypes[#argTypes+1] = err
|
|
if #t ~= t_num then
|
|
table.insert(t, t_num+1, [=[do ]=])
|
|
t[#t+1] = [=[end;]=]
|
|
end
|
|
end
|
|
if not storeKey then
|
|
storeKey = newUniqueVar()
|
|
end
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
local finalTypes = newList()
|
|
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 '')"
|
|
elseif types['nil'] then
|
|
-- just nil
|
|
t[#t+1] = "''"
|
|
else
|
|
-- non-nil
|
|
t[#t+1] = v
|
|
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] = [=[;]=]
|
|
if finalTypes['number'] then
|
|
t[#t+1] = [=[if tonumber(]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[) then ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[+0;]=]
|
|
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] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
t[#t+1] = [=[end;]=]
|
|
else
|
|
if finalTypes['number'] then
|
|
t[#t+1] = [=[end;]=]
|
|
end
|
|
end
|
|
for i,v in ipairs(args) do
|
|
if type(v) == "string" and v:match("^arg%d+$") then
|
|
t[#t+1] = v
|
|
delUniqueVar(v)
|
|
t[#t+1] = [=[ = nil;]=]
|
|
end
|
|
end
|
|
args = del(args)
|
|
argTypes = del(argTypes)
|
|
local s = joinSet(finalTypes, ';')
|
|
finalTypes = del(finalTypes)
|
|
return storeKey, s
|
|
elseif astType == 'and' or astType == 'or' then
|
|
if not storeKey then
|
|
storeKey = newUniqueVar()
|
|
end
|
|
local t_num = #t
|
|
t[#t+1] = [=[do ]=]
|
|
local arg, firstResults = compile(ast[2], nsList, t, cachedTags, storeKey, extraKwargs)
|
|
if not arg then
|
|
return nil, firstResults
|
|
end
|
|
firstResults = newSet((";"):split(firstResults))
|
|
local totalResults = newList()
|
|
t[#t+1] = [=[end;]=]
|
|
if firstResults["nil"] then
|
|
t[#t+1] = [=[if ]=]
|
|
if astType == 'or' then
|
|
t[#t+1] = [=[not ]=]
|
|
end
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ then ]=]
|
|
local arg, secondResults = compile(ast[3], nsList, t, cachedTags, storeKey, extraKwargs)
|
|
if not arg then
|
|
firstResults = del(firstResults)
|
|
totalResults = del(totalResults)
|
|
return nil, secondResults
|
|
end
|
|
secondResults = newSet((";"):split(secondResults))
|
|
t[#t+1] = [=[end;]=]
|
|
for k in pairs(firstResults) do
|
|
if k ~= "nil" then
|
|
totalResults[k] = true
|
|
end
|
|
end
|
|
for k in pairs(secondResults) do
|
|
totalResults[k] = true
|
|
end
|
|
secondResults = del(secondResults)
|
|
elseif astType == 'and' then
|
|
for i = t_num, #t do
|
|
t[i] = nil
|
|
end
|
|
local arg, secondResults = compile(ast[3], nsList, t, cachedTags, storeKey, extraKwargs)
|
|
if not arg then
|
|
firstResults = del(firstResults)
|
|
totalResults = del(totalResults)
|
|
return nil, secondResults
|
|
end
|
|
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
|
|
firstResults = del(firstResults)
|
|
local s = joinSet(totalResults, ';')
|
|
totalResults = del(totalResults)
|
|
return storeKey, s
|
|
elseif astType == 'if' then
|
|
if not storeKey then
|
|
storeKey = newUniqueVar()
|
|
end
|
|
local t_num = #t
|
|
t[#t+1] = [=[do ]=]
|
|
local storeKey, condResults = compile(ast[2], nsList, t, cachedTags, storeKey, extraKwargs)
|
|
if not storeKey then
|
|
return nil, condResults
|
|
end
|
|
condResults = newSet((';'):split(condResults))
|
|
t[#t+1] = [=[end;]=]
|
|
if condResults["nil"] and (condResults["string"] or condResults["number"]) then
|
|
condResults = del(condResults)
|
|
t[#t+1] = [=[if ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ then ]=]
|
|
local arg, firstResults = compile(ast[3], nsList, t, cachedTags, storeKey, extraKwargs)
|
|
if not arg then
|
|
return nil, firstResults
|
|
end
|
|
local totalResults = newSet((";"):split(firstResults))
|
|
t[#t+1] = [=[ else ]=]
|
|
if ast[4] then
|
|
local arg, secondResults = compile(ast[4], nsList, t, cachedTags, storeKey, extraKwargs)
|
|
if not arg then
|
|
totalResults = del(totalResults)
|
|
return nil, secondResults
|
|
end
|
|
secondResults = newSet((";"):split(secondResults))
|
|
for k in pairs(secondResults) do
|
|
totalResults[k] = true
|
|
end
|
|
secondResults = del(secondResults)
|
|
else
|
|
totalResults["nil"] = true
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = nil;]=]
|
|
end
|
|
t[#t+1] = [=[end;]=]
|
|
|
|
local s = joinSet(totalResults, ';')
|
|
totalResults = del(totalResults)
|
|
return storeKey, s
|
|
elseif condResults["nil"] then
|
|
-- just nil
|
|
condResults = del(condResults)
|
|
for i = t_num, #t do
|
|
t[i] = nil
|
|
end
|
|
if type(cond) == "string" and cond:match("^arg%d+$") then
|
|
delUniqueVar(cond)
|
|
end
|
|
local arg, totalResults = compile(ast[4], nsList, t, cachedTags, storeKey, extraKwargs)
|
|
if not arg then
|
|
return nil, totalResults
|
|
end
|
|
return storeKey, totalResults
|
|
else
|
|
-- non-nil
|
|
condResults = del(condResults)
|
|
for i = t_num, #t do
|
|
t[i] = nil
|
|
end
|
|
if type(cond) == "string" and cond:match("^arg%d+$") then
|
|
delUniqueVar(cond)
|
|
end
|
|
local arg, totalResults = compile(ast[3], nsList, t, cachedTags, storeKey, extraKwargs)
|
|
if not arg then
|
|
return nil, totalResults
|
|
end
|
|
return storeKey, totalResults
|
|
end
|
|
elseif astType == 'not' then
|
|
local t_num = #t
|
|
local s, results = compile(ast[2], nsList, t, cachedTags, storeKey, extraKwargs)
|
|
if not s then
|
|
return nil, results
|
|
end
|
|
results = newSet((";"):split(results))
|
|
if results["nil"] and (results["string"] or results["number"]) then
|
|
results = del(results)
|
|
storeKey = s
|
|
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = storeKey
|
|
t[#t+1] = [=[ and ]=]
|
|
t[#t+1] = ("%q"):format(L["True"])
|
|
t[#t+1] = [=[ or nil;]=]
|
|
return storeKey, "nil;string"
|
|
elseif results["nil"] then
|
|
-- just nil
|
|
results = del(results)
|
|
|
|
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] = [=[;]=]
|
|
return storeKey, "string"
|
|
else
|
|
return ("%q"):format(L["True"]), "string"
|
|
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;]=]
|
|
return storeKey, "nil"
|
|
else
|
|
return "nil", "nil"
|
|
end
|
|
end
|
|
end
|
|
error(("Unknown astType: %q"):format(tostring(astType)))
|
|
end
|
|
|
|
function DogTag:CreateFunctionFromCode(code, ...)
|
|
if type(code) ~= "string" then
|
|
error(("Bad argument #2 to `CreateFunctionFromCode'. Expected %q, got %q."):format("string", type(code)), 2)
|
|
end
|
|
local notDebug = (...) == true
|
|
local kwargsKey = kwargsToKey()
|
|
local nsList
|
|
if notDebug then
|
|
kwargsKey = select(2, ...)
|
|
nsList = getNamespaceList(select(3, ...))
|
|
else
|
|
local n = select('#', ...)
|
|
local kwargs = select(n, ...)
|
|
if type(kwargs) == "table" then
|
|
kwargsKey = kwargsToKey(kwargs)
|
|
n = n - 1
|
|
end
|
|
for i = 1, n do
|
|
if type(select(i, ...)) ~= "string" then
|
|
error(("Bad argument #%d to `CreateFunctionFromCode'. Expected %q, got %q"):format(i+2, "string", type(select(i, ...))), 2)
|
|
end
|
|
end
|
|
nsList = getNamespaceList(select2(1, n, ...))
|
|
end
|
|
|
|
local ast = DogTag.parse(code)
|
|
ast = DogTag.standardize(ast)
|
|
correctASTCasing(ast)
|
|
|
|
local t = newList()
|
|
t[#t+1] = ([=[local DogTag = _G.LibStub(%q);]=]):format(MAJOR_VERSION)
|
|
t[#t+1] = [=[local colors = DogTag.__colors;]=]
|
|
t[#t+1] = [=[local NIL = DogTag.__NIL;]=]
|
|
t[#t+1] = [=[local cleanText = DogTag.__cleanText;]=]
|
|
|
|
local globals = figureGlobals(ast, nsList, nil)
|
|
globals['table.concat'] = true
|
|
globals['tonumber'] = true
|
|
globals['type'] = true
|
|
for global in pairs(globals) do
|
|
if global:find("^[A-Za-z0-9%-]+%-%d+%.%d+$") then
|
|
if Rock then
|
|
Rock(global, false, true) -- try to lod
|
|
end
|
|
if AceLibrary then
|
|
AceLibrary:HasInstance(global) -- try to load
|
|
end
|
|
if LibStub(global, true) then -- catches Rock and AceLibrary libs as well
|
|
t[#t+1] = [=[local ]=]
|
|
t[#t+1] = global:gsub("%-.-$", "")
|
|
if not global:find("^Lib") then
|
|
t[#t+1] = [=[Lib]=]
|
|
end
|
|
t[#t+1] = [=[ = LibStub("]=]
|
|
t[#t+1] = k
|
|
t[#t+1] = [=[");]=]
|
|
end
|
|
else
|
|
t[#t+1] = [=[local ]=]
|
|
t[#t+1] = global:gsub("%.", "_")
|
|
t[#t+1] = [=[ = ]=]
|
|
t[#t+1] = global
|
|
t[#t+1] = [=[;]=]
|
|
end
|
|
end
|
|
globals = del(globals)
|
|
t[#t+1] = [=[return function(kwargs) ]=]
|
|
t[#t+1] = [=[local value, opacity;]=]
|
|
|
|
local cachedTags = figureCachedTags(ast)
|
|
for k in pairs(cachedTags) do
|
|
t[#t+1] = [=[local cache_]=]
|
|
t[#t+1] = k
|
|
t[#t+1] = [=[ = NIL;]=]
|
|
end
|
|
|
|
local u = newList()
|
|
local extraKwargs = newList()
|
|
for k, v in pairs(kwargsKey) do
|
|
local arg = newUniqueVar()
|
|
u[#u+1] = arg
|
|
u[#u+1] = [=[ = kwargs["]=]
|
|
u[#u+1] = k
|
|
u[#u+1] = [=["];]=]
|
|
extraKwargs[k] = newList(arg, v)
|
|
end
|
|
local ret, types = compile(ast, nsList, u, cachedTags, 'value', extraKwargs)
|
|
for k, v in pairs(extraKwargs) do
|
|
extraKwargs[k] = del(v)
|
|
end
|
|
extraKwargs = del(extraKwargs)
|
|
ast = deepDel(ast)
|
|
if not ret then
|
|
for i = 1, #u do
|
|
u[i] = nil
|
|
end
|
|
u[#u+1] = [=[value = ]=]
|
|
u[#u+1] = ("%q"):format(types)
|
|
u[#u+1] = [=[;]=]
|
|
end
|
|
for i = 1, getNumUniqueVars() do
|
|
t[#t+1] = [=[local arg]=]
|
|
t[#t+1] = i
|
|
t[#t+1] = [=[;]=]
|
|
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 value == '' then value = nil; elseif tonumber(value) then value = value+0; end;]=]
|
|
end
|
|
types = del(types)
|
|
|
|
t[#t+1] = [=[return value, opacity;]=]
|
|
|
|
t[#t+1] = [=[end]=]
|
|
|
|
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
|
|
end
|
|
return s
|
|
end
|
|
|
|
function DogTag:Evaluate(code, ...)
|
|
if type(code) ~= "string" then
|
|
error(("Bad argument #2 to `Evaluate'. Expected %q, got %q"):format("string", type(code)), 2)
|
|
end
|
|
local n = select('#', ...)
|
|
local kwargs
|
|
if n > 0 then
|
|
kwargs = select(n, ...)
|
|
if type(kwargs) == "table" then
|
|
n = n - 1
|
|
else
|
|
kwargs = nil
|
|
end
|
|
end
|
|
for i = 1, n do
|
|
if type(select(i, ...)) ~= "string" then
|
|
error(("Bad argument #%d to `Evaluate'. Expected %q, got %q"):format(i+2, "string", type(select(i, ...))), 2)
|
|
end
|
|
end
|
|
local nsList = getNamespaceList(select2(1, n, ...))
|
|
local kwargsKey = kwargsToKey(kwargs)
|
|
|
|
DogTag.__isMouseOver = false
|
|
|
|
local func = codeToFunction[nsList][kwargsKey][code]
|
|
|
|
local madeKwargs = not kwargs
|
|
if madeKwargs then
|
|
kwargs = newList()
|
|
end
|
|
local success, text, opacity = pcall(func, kwargs)
|
|
if madeKwargs then
|
|
kwargs = del(kwargs)
|
|
end
|
|
if success then
|
|
if opacity then
|
|
if opacity > 1 then
|
|
opacity = 1
|
|
elseif opacity < 0 then
|
|
opacity = 0
|
|
end
|
|
end
|
|
return text, opacity
|
|
else
|
|
geterrorhandler()(("%s.%d: Error with code %q%s. %s"):format(MAJOR_VERSION, MINOR_VERSION, code, nsList == "Base" and "" or " (" .. nsList .. ")", text))
|
|
end
|
|
end
|
|
|
|
end |