From ebba2d02f55cc910e38020fb8f0d3379390967c2 Mon Sep 17 00:00:00 2001 From: "Johnny C. Lam" Date: Mon, 21 Apr 2014 05:03:21 +0000 Subject: [PATCH] Implement SimulationCraft profile translator module. This does a fairly straightforward translation from SimC action lists into the equivalent Ovale script language. It does an incomplete job -- it only generates functions from the equivalently-named action lists in a SimC profile, which need hand-editing and inclusion into Ovale icons. Lexer implemented using coroutines taken from Penlight Lua library. git-svn-id: svn://svn.curseforge.net/wow/ovale/mainline/trunk@1312 d5049fe3-3747-40f7-a4b5-f36d6801af5f --- Ovale.toc | 2 + OvaleLexer.lua | 362 +++++++++++++++ OvaleSimulationCraft.lua | 1121 ++++++++++++++++++++++++++++++++++++++++++++++ compiler.pl | 2 + 4 files changed, 1487 insertions(+) create mode 100644 OvaleLexer.lua create mode 100644 OvaleSimulationCraft.lua diff --git a/Ovale.toc b/Ovale.toc index 1f3e235..f84e154 100644 --- a/Ovale.toc +++ b/Ovale.toc @@ -19,6 +19,7 @@ locales\files.xml Ovale.lua # Utility modules. +OvaleLexer.lua OvalePool.lua OvalePoolGC.lua OvalePoolRefCount.lua @@ -43,6 +44,7 @@ OvalePaperDoll.lua OvalePower.lua OvaleScore.lua OvaleScripts.lua +OvaleSimulationCraft.lua OvaleSpellBook.lua OvaleStance.lua OvaleState.lua diff --git a/OvaleLexer.lua b/OvaleLexer.lua new file mode 100644 index 0000000..06d540a --- /dev/null +++ b/OvaleLexer.lua @@ -0,0 +1,362 @@ +--[[-------------------------------------------------------------------- + Copyright (C) 2009 Steve Donovan, David Manura. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--]]-------------------------------------------------------------------- +--[[-------------------------------------------------------------------- + Ovale Spell Priority + Copyright (C) 2014 Johnny C. Lam + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License in the LICENSE + file accompanying this program. +--]]-------------------------------------------------------------------- + +-- This file is modified from: +-- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/lexer.lua + +--- Lexical scanner for creating a sequence of tokens from text. +-- `lexer.scan(s)` returns an iterator over all tokens found in the +-- string `s`. This iterator returns two values, a token type string +-- (such as 'string' for quoted string, 'iden' for identifier) and the value of the +-- token. +-- +-- Versions specialized for Lua and C are available; these also handle block comments +-- and classify keywords as 'keyword' tokens. For example: +-- +-- > s = 'for i=1,n do' +-- > for t,v in lexer.lua(s) do print(t,v) end +-- keyword for +-- iden i +-- = = +-- number 1 +-- , , +-- iden n +-- keyword do +-- +-- See the Guide for further @{06-data.md.Lexical_Scanning|discussion} +-- @module pl.lexer + +local _, Ovale = ... +local OvaleLexer = {} +Ovale.OvaleLexer = OvaleLexer + +-- +local append = table.insert +local error = error +local strfind = string.find +local strsub = string.sub +local type = type +local yield, wrap = coroutine.yield, coroutine.wrap + +local plain_matches = nil + +local NUMBER1 = '^[%+%-]?%d+%.?%d*[eE][%+%-]?%d+' +local NUMBER2 = '^[%+%-]?%d+%.?%d*' +local NUMBER3 = '^0x[%da-fA-F]+' +local NUMBER4 = '^%d+%.?%d*[eE][%+%-]?%d+' +local NUMBER5 = '^%d+%.?%d*' +local IDEN = '^[%a_][%w_]*' +local WSPACE = '^%s+' +local STRING0 = [[^(['\"]).-\\%1]] +local STRING1 = [[^(['\"]).-[^\]%1]] +local STRING3 = "^((['\"])%2)" -- empty string +local PREPRO = '^#.-[^\\]\n' +-- + +-- +local function assert_arg(idx,val,tp) + if type(val) ~= tp then + error("argument "..idx.." must be "..tp, 2) + end +end + +local function tdump(tok) + return yield(tok,tok) +end + +local function ndump(tok,options) + if options and options.number then + tok = tonumber(tok) + end + return yield("number",tok) +end + +-- regular strings, single or double quotes; usually we want them +-- without the quotes +local function sdump(tok,options) + if options and options.string then + tok = strsub(tok,2,-2) + end + return yield("string",tok) +end + +local function chdump(tok,options) + if options and options.string then + tok = strsub(tok,2,-2) + end + return yield("char",tok) +end + +local function cdump(tok) + return yield('comment',tok) +end + +local function wsdump (tok) + return yield("space",tok) +end + +local function pdump (tok) + return yield('prepro',tok) +end + +local function plain_vdump(tok) + return yield("iden",tok) +end +-- + +-- +--- create a plain token iterator from a string or file-like object. +-- @param s the string +-- @param matches an optional match table (set of pattern-action pairs) +-- @param filter a table of token types to exclude, by default {space=true} +-- @param options a table of options; by default, {number=true,string=true}, +-- which means convert numbers and strip string quotes. +function OvaleLexer.scan (s,matches,filter,options) + --assert_arg(1,s,'string') + local file = type(s) ~= 'string' and s + filter = filter or {space=true} + options = options or {number=true,string=true} + if filter then + if filter.space then filter[wsdump] = true end + if filter.comments then + filter[cdump] = true + end + end + if not matches then + if not plain_matches then + plain_matches = { + {WSPACE,wsdump}, + {NUMBER3,ndump}, + {IDEN,plain_vdump}, + {NUMBER1,ndump}, + {NUMBER2,ndump}, + {STRING3,sdump}, + {STRING0,sdump}, + {STRING1,sdump}, + {'^.',tdump} + } + end + matches = plain_matches + end + local function lex () + local i1,i2,idx,res1,res2,tok,pat,fun,capt + local line = 1 + if file then s = file:read()..'\n' end + local sz = #s + local idx = 1 + --print('sz',sz) + while true do + for _,m in ipairs(matches) do + pat = m[1] + fun = m[2] + i1,i2 = strfind(s,pat,idx) + if i1 then + tok = strsub(s,i1,i2) + idx = i2 + 1 + if not (filter and filter[fun]) then + res1,res2 = fun(tok,options) + end + if res1 then + local tp = type(res1) + -- insert a token list + if tp=='table' then + yield('','') + for _,t in ipairs(res1) do + yield(t[1],t[2]) + end + elseif tp == 'string' then -- or search up to some special pattern + i1,i2 = strfind(s,res1,idx) + if i1 then + tok = strsub(s,i1,i2) + idx = i2 + 1 + yield('',tok) + else + yield('','') + idx = sz + 1 + end + --if idx > sz then return end + else + yield(line,idx) + end + end + if idx > sz then + if file then + --repeat -- next non-empty line + line = line + 1 + s = file:read() + if not s then return end + --until not s:match '^%s*$' + s = s .. '\n' + idx ,sz = 1,#s + break + else + return + end + else break end + end + end + end + end + return wrap(lex) +end + +local function isstring (s) + return type(s) == 'string' +end + +--- insert tokens into a stream. +-- @param tok a token stream +-- @param a1 a string is the type, a table is a token list and +-- a function is assumed to be a token-like iterator (returns type & value) +-- @param a2 a string is the value +function OvaleLexer.insert (tok,a1,a2) + if not a1 then return end + local ts + if isstring(a1) and isstring(a2) then + ts = {{a1,a2}} + elseif type(a1) == 'function' then + ts = {} + for t,v in a1() do + append(ts,{t,v}) + end + else + ts = a1 + end + tok(ts) +end + +--- get everything in a stream upto a newline. +-- @param tok a token stream +-- @return a string +function OvaleLexer.getline (tok) + local t,v = tok('.-\n') + return v +end + +--- get current line number.
+-- Only available if the input source is a file-like object. +-- @param tok a token stream +-- @return the line number and current column +function OvaleLexer.lineno (tok) + return tok(0) +end + +--- get the rest of the stream. +-- @param tok a token stream +-- @return a string +function OvaleLexer.getrest (tok) + local t,v = tok('.+') + return v +end + +--- get a list of parameters separated by a delimiter from a stream. +-- @param tok the token stream +-- @param endtoken end of list (default ')'). Can be '\n' +-- @param delim separator (default ',') +-- @return a list of token lists. +function OvaleLexer.get_separated_list(tok,endtoken,delim) + endtoken = endtoken or ')' + delim = delim or ',' + local parm_values = {} + local level = 1 -- used to count ( and ) + local tl = {} + local function tappend (tl,t,val) + val = val or t + append(tl,{t,val}) + end + local is_end + if endtoken == '\n' then + is_end = function(t,val) + return t == 'space' and val:find '\n' + end + else + is_end = function (t) + return t == endtoken + end + end + local token,value + while true do + token,value=tok() + if not token then return nil,'EOS' end -- end of stream is an error! + if is_end(token,value) and level == 1 then + append(parm_values,tl) + break + elseif token == '(' then + level = level + 1 + tappend(tl,'(') + elseif token == ')' then + level = level - 1 + if level == 0 then -- finished with parm list + append(parm_values,tl) + break + else + tappend(tl,')') + end + elseif token == delim and level == 1 then + append(parm_values,tl) -- a new parm + tl = {} + else + tappend(tl,token,value) + end + end + return parm_values,{token,value} +end + +--- get the next non-space token from the stream. +-- @param tok the token stream. +function OvaleLexer.skipws (tok) + local t,v = tok() + while t == 'space' do + t,v = tok() + end + return t,v +end + +local skipws = OvaleLexer.skipws + +--- get the next token, which must be of the expected type. +-- Throws an error if this type does not match! +-- @param tok the token stream +-- @param expected_type the token type +-- @param no_skip_ws whether we should skip whitespace +function OvaleLexer.expecting (tok,expected_type,no_skip_ws) + assert_arg(1,tok,'function') + assert_arg(2,expected_type,'string') + local t,v + if no_skip_ws then + t,v = tok() + else + t,v = skipws(tok) + end + if t ~= expected_type then error ("expecting "..expected_type,2) end + return v +end +--
diff --git a/OvaleSimulationCraft.lua b/OvaleSimulationCraft.lua new file mode 100644 index 0000000..751986b --- /dev/null +++ b/OvaleSimulationCraft.lua @@ -0,0 +1,1121 @@ +--[[-------------------------------------------------------------------- + Ovale Spell Priority + Copyright (C) 2014 Johnny C. Lam + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License in the LICENSE + file accompanying this program. +--]]-------------------------------------------------------------------- + +local _, Ovale = ... +local OvaleSimulationCraft = {} +Ovale.OvaleSimulationCraft = OvaleSimulationCraft + +-- +local OvaleLexer = Ovale.OvaleLexer + +local format = string.format +local gmatch = string.gmatch +local gsub = string.gsub +local setmetatable = setmetatable +local strfind = string.find +local strlower = string.lower +local strmatch = string.match +local strsplit = strsplit +local strupper = string.upper +local tconcat = table.concat +local tinsert = table.insert +local tonumber = tonumber +local tremove = table.remove +local tsort = table.sort +local type = type +local wipe = table.wipe +local yield = coroutine.yield +local RAID_CLASS_COLORS = RAID_CLASS_COLORS + +local INDENTATION = {} +do + INDENTATION[0] = "" + local metatable = { + __index = function(tbl, key) + key = tonumber(key) + if key > 0 then + local s = tbl[key - 1] .. " " + rawset(tbl, key, s) + return s + end + return INDENTATION[0] + end, + } + setmetatable(INDENTATION, metatable) +end + +local SIMC_CLASS = {} +do + for class in pairs(RAID_CLASS_COLORS) do + SIMC_CLASS[strlower(class)] = true + end +end +-- + +-- +OvaleSimulationCraft.simcString = nil +OvaleSimulationCraft.simcComments = nil +OvaleSimulationCraft.indent = 0 +OvaleSimulationCraft.profile = {} +OvaleSimulationCraft.symbols = {} +OvaleSimulationCraft.script = {} +OvaleSimulationCraft.__index = OvaleSimulationCraft +do + -- Class constructor + setmetatable(OvaleSimulationCraft, { __call = function(self, ...) return self:New(...) end }) +end +-- + +-- +local function NameValuePair(expr) + local name, value = strmatch(expr, "^([^=]*)=(.*)") + if strmatch(value, "^[%-]?%d+%.?%d*$") then + value = tonumber(value) + end + return name, value +end +-- + +-- +function OvaleSimulationCraft:New(simcString) + local obj = { + simcString = nil, + simcComments = nil, + indent = 0, + profile = {}, + symbols = {}, + script = {}, + } + setmetatable(obj, self) + if simcString then + obj:ParseProfile(simcString) + end + return obj +end + +function OvaleSimulationCraft:Indent() + self.indent = self.indent + 1 +end + +function OvaleSimulationCraft:UnIndent() + self.indent = (self.indent > 0) and (self.indent - 1) or 0 +end + +function OvaleSimulationCraft:Append(script, ...) + local s = format("%s%s", INDENTATION[self.indent], format(...)) + tinsert(script, s) +end + +function OvaleSimulationCraft:ParseProfile(simcString) + self.simcString = simcString + local profile = self.profile + for line in gmatch(simcString, "[^\r\n]+") do + line = strmatch(line, "^%s*(.-)%s*$") + if not (strmatch(line, "^#.*") or strmatch(line, "^$")) then + local key, value = strmatch(line, "([^%+=]+)%+?=(.*)") + if not profile[key] then + profile[key] = value + elseif type(profile[key]) == "table" then + tinsert(profile[key], value) + else + local oldValue = profile[key] + profile[key] = {} + tinsert(profile[key], oldValue) + tinsert(profile[key], value) + end + end + end + -- Concatenate variables defined over multiple lines using += + for k, v in pairs(profile) do + if type(v) == "table" then + local value = tconcat(v) + profile[k] = value + end + end + for k, v in pairs(profile) do + if strmatch(k, "^actions") then + local listName = strmatch(k, "^actions[.]([%w_]+)") or "default" + profile[k] = nil + profile.actionList = profile.actionList or {} + local tbl = { strsplit("/", v) } + for i, action in ipairs(tbl) do + local line = tbl[i] + tbl[i] = { strsplit(",", action) } + tbl[i].action = tremove(tbl[i], 1) + tbl[i].line = line + end + profile.actionList[listName] = tbl + elseif k == "glyphs" then + profile[k] = { strsplit("/", v) } + elseif k == "professions" then + local tbl = { strsplit("/", v) } + for i, profession in ipairs(tbl) do + local prof, level = NameValuePair(profession) + tbl[i] = nil + tbl[prof] = level + end + profile[k] = tbl + end + end +end + +do + local symbols = {} + + function OvaleSimulationCraft:GenerateScript(script) + script = script or {} + local profile = self.profile + for class in pairs(SIMC_CLASS) do + local simcName = profile[class] + if simcName then + profile.class = class + self:Append(script, "# Based on SimulationCraft profile %s.", simcName) + self:Append(script, "# class=%s", class) + end + end + if profile.spec then + self:Append(script, "# spec=%s", profile.spec) + end + if profile.talents then + self:Append(script, "# talents=%s", profile.talents) + end + if profile.glyphs then + self:Append(script, "# glyphs=%s", tconcat(profile.glyphs, "/")) + end + if profile.default_pet then + self:Append(script, "# pet=%s", profile.default_pet) + end + if profile.actionList then + for listName, actionList in pairs(profile.actionList) do + self:ParseActionList(script, listName, actionList) + end + end + self:Append(script, "") + + tsort(self.symbols) + wipe(symbols) + for _, v in ipairs(self.symbols) do + if not symbols[v] then + symbols[v] = true + tinsert(symbols, v) + end + end + self:Append(script, "### Pre-defined symbols") + for _, v in ipairs(symbols) do + self:Append(script, format("# %s", v)) + end + return script + end +end + +do + local function TitleCase(first, rest) + return strupper(first) .. strlower(rest) + end + + function OvaleSimulationCraft:FunctionName(listName) + if self.profile.spec then + listName = format("%s_%s_actions", self.profile.spec, listName) + else + listName = format("%s_actions", listName) + end + listName = gsub(listName, "_", " ") + listName = gsub(listName, "(%a)(%w*)", TitleCase) + listName = gsub(listName, "%s", "") + return listName + end +end + +function OvaleSimulationCraft:ParseActionList(script, listName, actionList) + self:Append(script, "") + self:Append(script, "AddFunction %s", self:FunctionName(listName)) + self:Append(script, "{") + self:Indent() + local indent = self.indent + for i, actionLine in ipairs(actionList) do + self:ParseActionLine(script, actionLine) + end + while self.indent > indent do + self:UnIndent() + self:Append(script, "}") + end + self:UnIndent() + self:Append(script, "}") +end + +do + local TO_NAME = { + druid = { + balance = { + ["dream_of_cenarius"] = "dream_of_cenarius_caster", + ["incarnation"] = "incarnation_caster", + ["wild_mushroom"] = "wild_mushroom_caster", + } + }, + } + + function OvaleSimulationCraft:Name(name) + local class, spec = self.profile.class, self.profile.spec + if TO_NAME[class] and TO_NAME[class][spec] and TO_NAME[class][spec][name] then + return TO_NAME[class][spec][name] + end + return name + end +end + +do + local SIMC_ACTION = { + ["_potion$"] = function(simc, action) return format("Item(%s)", action) end, + ["^$"] = false, + ["^auto_attack$"] = false, + ["^flask$"] = false, + ["^food$"] = false, + ["^snapshot_stats$"] = false, + -- Death Knight + ["^blood_presence$"] = "if not Stance(deathknight_blood_presence) Spell(blood_presence)", + ["^frost_presence$"] = "if not Stance(deathknight_frost_presence) Spell(frost_presence)", + ["^unholy_presence$"] = "if not Stance(deathknight_unholy_presence) Spell(unholy_presence)", + -- Hunter + ["^aspect_of_the_"] = function(simc, action) return format("if not Stance(hunter_%s) Spell(%s)", action, action) end, + -- Mage + ["^arcane_brilliance$"] = "if BuffExpires(critical_strike any=1) or BuffExpires(spell_power_multiplier any=1) Spell(arcane_brilliance)", + ["^cancel_buff$"] = false, + ["^frost_armor$"] = function(simc, action) + tinsert(simc.symbols, "frost_armor_buff") + return "if BuffExpires(frost_armor_buff) Spell(frost_armor)" + end, + ["^molten_armor$"] = function(simc, action) + tinsert(simc.symbols, "molten_armor_buff") + return "if BuffExpires(molten_armor_buff) Spell(molten_armor)" + end, + ["^rune_of_power$"] = false, -- XXX + -- Monk + ["^chi_sphere$"] = false, + -- Paladin + ["^rebuke$"] = "if target.IsInterruptible() Spell(rebuke)", + ["^seal_of_"] = function(simc, action) return format("if not Stance(paladin_%s) Spell(%s)", action, action) end, + -- Priest + ["^inner_fire$"] = function(simc, action) + tinsert(simc.symbols, "inner_fire_buff") + return "if BuffExpires(inner_fire_buff) Spell(inner_fire)" + end, + ["^shadowform$"] = "if not Stance(priest_shadowform) Spell(shadowform)", + -- Rogue + ["^apply_poison$"] = false, -- XXX + ["^kick$"] = "if target.IsInterruptible() Spell(kick)", + ["^stealth$"] = "if Stealthed(no) Spell(stealth)", + } + + local scriptLine = {} + + function OvaleSimulationCraft:ParseActionLine(script, actionLine) + wipe(scriptLine) + if self.simcComments then + self:Append(script, "#%s", actionLine.line) + end + + local action = self:Name(actionLine.action) + local matchedAction = false + for pattern, result in pairs(SIMC_ACTION) do + if strmatch(action, pattern) then + matchedAction = true + scriptLine.action = (type(result) == "function") and result(self, action) or result + break + end + end + if not matchedAction then + scriptLine.action = format("Spell(%s)", action) + end + + if scriptLine.action then + local addActionSymbol = false + if #actionLine == 0 then + addActionSymbol = true + else + for i, expr in ipairs(actionLine) do + local name, value = NameValuePair(expr) + if action == "use_item" then + if name == "slot" then + if value == "hands" then + scriptLine.action = "Item(HandsSlot usable=1)" + elseif value == "trinket" then + scriptLine.action = "{ Item(Trinket0Slot usable=1) Item(Trinket1Slot usable=1) }" + end + elseif name == "name" then + if strmatch(value, "gauntlets") or strmatch(value, "gloves") or strmatch(value, "grips") or strmatch(value, "handguards") then + scriptLine.action = "Item(HandsSlot usable=1)" + end + end + elseif action == "wait" then + if name == "sec" then + if type(value) == "number" then + scriptLine.action = nil + else + local spellName = strmatch(value, "^cooldown%.([%w_]+)%.remains$") + if spellName then + scriptLine.action = format("wait Spell(%s)", spellName) + end + end + end + elseif action == "swap_action_list" then + if name == "name" then + scriptLine.action = format("%s()", self:FunctionName(value)) + end + elseif action == "run_action_list" then + if name == "name" then + scriptLine.action = format("%s()", self:FunctionName(value)) + end + elseif action == "pool_resource" then + scriptLine.action = nil + if name == "for_next" and value == 1 then + script.for_next = true + end + elseif action == "stance" then + -- Don't add symbol for "stance". + else + addActionSymbol = true + end + if name == "if" then + scriptLine.if_expr = self:ParseExpression(action, value, addActionSymbol) + else + scriptLine[name] = value + end + end + end + if addActionSymbol then + tinsert(self.symbols, action) + end + + if scriptLine.if_expr + or scriptLine.moving == 1 + or scriptLine.sync + or action == "focus_fire" and scriptLine.five_stacks == 1 + or action == "stance" and scriptLine.choose + or scriptLine.weapon + then + local needAnd = false + if action == "pool_resource" and not script.for_next then + tinsert(scriptLine, "unless") + else + tinsert(scriptLine, "if") + end + if scriptLine.moving == 1 then + tinsert(scriptLine, "Speed() > 0") + needAnd = true + end + if scriptLine.sync then + if needAnd then + tinsert(scriptLine, "and") + end + tinsert(scriptLine, format("Spell(%s)", scriptLine.sync)) + needAnd = true + end + if scriptLine.weapon then + if needAnd then + tinsert(scriptLine, "and") + end + tinsert(scriptLine, format("WeaponEnchantExpires(%s)", scriptLine.weapon)) + needAnd = true + end + if action == "focus_fire" and scriptLine.five_stacks == 1 then + if needAnd then + tinsert(scriptLine, "and") + end + tinsert(scriptLine, "BuffStacks(pet_frenzy any=1) == 5") + needAnd = true + end + if action == "stance" and scriptLine.choose then + if needAnd then + tinsert(scriptLine, "and") + end + local class = self.profile.class + if class == "deathknight" then + local spellName = format("%s_presence", scriptLine.choose) + scriptLine.action = format("Spell(%s)", spellName) + tinsert(scriptLine, format("not Stance(%s_%s)", class, spellName)) + elseif class == "monk" then + local spellName = format("stance_of_the_%s", scriptLine.choose) + scriptLine.action = format("Spell(%s)", spellName) + tinsert(scriptLine, format("not Stance(%s_%s)", class, spellName)) + elseif class == "warrior" then + local spellName = format("%s_stance", scriptLine.choose) + scriptLine.action = format("Spell(%s)", spellName) + tinsert(scriptLine, format("not Stance(%s_%s)", class, spellName)) + else + tinsert(scriptLine, format("not Stance(%s_%s)", class, scriptLine.choose)) + end + needAnd = true + end + if scriptLine.if_expr then + if needAnd then + tinsert(scriptLine, "and") + end + if needAnd and strmatch(scriptLine.if_expr, " or ") then + tinsert(scriptLine, format("{ %s }", scriptLine.if_expr)) + else + tinsert(scriptLine, scriptLine.if_expr) + end + needAnd = true + end + end + + if action ~= "pool_resource" then + if script.for_next then + tinsert(scriptLine, "wait") + script.for_next = nil + end + tinsert(scriptLine, scriptLine.action) + end + + if scriptLine[1] then + self:Append(script, tconcat(scriptLine, " ")) + end + if action == "pool_resource" and not script.for_next then + self:Append(script, "{") + self:Indent() + end + end + end +end + +do + -- Table of matching tokens for SimC conditional expressions for OvaleLexer.scan(). + local MATCHES = {} + local function chdump(tok, options) + if options and options.string then + tok = strsub(tok, 2, -2) + end + return yield("char", tok) + end + + local function ndump(tok, options) + if options and options.number then + tok = tonumber(tok) + end + return yield("number", tok) + end + + local function tdump(tok) + return yield(tok, tok) + end + + local function vdump(tok) + return yield("iden", tok) + end + + local function wsdump(tok) + return yield("space", tok) + end + + -- whitespace + tinsert(MATCHES, { '^%s+', wsdump }) + -- numbers + tinsert(MATCHES, { '^[%-]?%d+%.?%d*', ndump }) + -- floor/ceil + tinsert(MATCHES, { '^floor', tdump }) + tinsert(MATCHES, { '^ceil', tdump }) + -- identifiers (foo.bar.baz.etc) + tinsert(MATCHES, { '^[%a_][%w_%.]*[%w_]', vdump }) + -- not-equal + tinsert(MATCHES, { '^!=', tdump }) + -- less-than-equal + tinsert(MATCHES, { '^<=', tdump }) + -- greater-than-equal + tinsert(MATCHES, { '^>=', tdump }) + -- exclusion + tinsert(MATCHES, { '^!~', tdump }) + -- catch-all + tinsert(MATCHES, { '^.', tdump }) + + --[[ + The "buff" list also contains special buffs such as: + "bleeding" (target is bleeding), + "casting" (character is casting), + "raid_movement" (character is moving because of a "movement" raid event), + "poisoned" (target is poisoned), + "self_movement" (character is moving because of a start_move action), + "stunned" (character is stunned), + "vulnerable" (target is vulnerable), + "stealthed" (character is stealthed). + --]] + local SPECIAL_PROPERTY = { + ["^debuff%.casting%.react$"] = "IsInterruptible()", + ["^debuff%.flying%.down$"] = "True(not flying_debuff)", + ["^buff%.raid_movement%.duration$"] = "0", + -- Pretend the target can never be invulnerable. + ["^debuff%.invulnerable%.react$"] = "InCombat(no)", + ["^buff%.bloodlust%.react$"] = "BuffPresent(burst_haste any=1)", + ["^buff%.bloodlust%.up$"] = "BuffPresent(burst_haste any=1)", + ["^buff%.bloodlust%.down$"] = "BuffExpires(burst_haste any=1)", + ["^buff%.stealthed%.down$"] = "Stealthed(no)", + ["^buff%.stealthed%.up$"] = "Stealthed()", + + -- Druid + ["^buff%.wild_mushroom%.max_stack$"] = function(simc, action) + local class, spec = simc.profile.class, simc.profile.spec + if class == "druid" and spec == "restoration" then + return "1" + end + return "3" + end, + ["^buff%.wild_mushroom%.stack$"] = "WildMushroomCount()", + -- Hunter + ["^debuff%.ranged_vulnerability%.up$"] = "target.DebuffPresent(ranged_vulnerability any=1)", + -- Mage + ["^buff%.arcane_charge%.stack$"] = function(simc, action) + tinsert(simc.symbols, "arcane_charge_debuff") + return "DebuffStacks(arcane_charge_debuff)" + end, + -- Monk + ["^dot%.zen_sphere%.ticking$"] = function(simc, action) + tinsert(simc.symbols, "zen_sphere_buff") + return "BuffPresent(zen_sphere_buff)" + end, + -- Rogue + ["^buff%.stealth%.down$"] = "Stealthed(no)", + ["^buff%.stealth%.up$"] = "Stealthed()", + -- Shaman + ["^active_flame_shock$"] = function(simc, action) + tinsert(simc.symbols, "flame_shock_debuff") + return "DebuffCount(flame_shock_debuff)" + end, + ["^buff%.lightning_shield%.max_stack$"] = "7", + ["^buff%.lightning_shield%.react$"] = function(simc, action) + tinsert(simc.symbols, "lightning_shield_buff") + return "BuffStacks(lightning_shield_buff)" + end, + ["^pet%.greater_fire_elemental%.active$"] = "TotemPresent(fire totem=fire_elemental_totem)", + ["^pet%.primal_fire_elemental%.active$"] = "TotemPresent(fire totem=fire_elemental_totem)", + } + + -- totem.. + local TOTEM_TYPE = { + ["capacitor_totem"] = "air", + ["earth_elemental_totem"] = "earth", + ["earthbind_totem"] = "earth", + ["earthgrab_totem"] = "earth", + ["fire_elemental_totem"] = "fire", + ["grounding_totem"] = "air", + ["healing_stream_totem"] = "water", + ["healing_tide_totem"] = "water", + ["magma_totem"] = "fire", + ["mana_tide_totem"] = "water", + ["searing_totem"] = "fire", + ["stone_bulwark_totem"] = "earth", + ["stormlash_totem"] = "air", + ["tremor_totem"] = "earth", + ["windwalk_totem"] = "air", + } + local TOTEM_PROPERTY_PATTERN = "^totem%.([%w_]+)%.([%w_]+)$" + local TOTEM_PROPERTY = { + ["^active$"] = function(simc, totemName) + if TOTEM_TYPE[totemName] then + return format("TotemPresent(%s totem=%s)", TOTEM_TYPE[totemName], totemName) + else + return format("TotemPresent(%s)", totemName) + end + end, + ["^remains$"] = function(simc, totemName) + if TOTEM_TYPE[totemName] then + return format("TotemRemains(%s totem=%s)", TOTEM_TYPE[totemName], totemName) + else + return format("TotemRemains(%s)", totemName) + end + end, + } + + local ACTION_PROPERTY_PATTERN = "^action%.([%w_]+)%.([%w_]+)$" + local ACTION_PROPERTY = { + -- TODO: Buff? + ["^active$"] = function(simc, actionName) + if strmatch(actionName, "_totem$") then + return TOTEM_PROPERTY["^active$"](simc, actionName) + else + local symbol = format("%s_debuff", actionName) + tinsert(simc.symbols, symbol) + return format("target.DebuffPresent(%s)", symbol) + end + end, + ["^add_ticks$"] = function(simc, actionName) + local symbol = format("%s_debuff", actionName) + tinsert(simc.symbols, symbol) + return format("TicksAdded(%s)", symbol) + end, + ["^ember_react$"] = "BurningEmbers() >= 1", + ["^cast_delay$"] = "True(cast_delay)", + ["^cast_time$"] = function(simc, actionName) return format("CastTime(%s)", actionName) end, + ["^charges$"] = function(simc, actionName) return format("Charges(%s)", actionName) end, + -- TODO: ItemCooldown? + ["^cooldown$"] = function(simc, actionName) return format("SpellCooldown(%s)", actionName) end, + -- TODO: Item? + ["^cooldown_react$"] = function(simc, actionName) return format("Spell(%s)", actionName) end, + ["^crit_damage$"] = function(simc, actionName) return format("CritDamage(%s)", actionName) end, + -- TODO: Melee/Ranged/Spell crit chance depending on type of attack, or at least class of player. + ["^crit_pct_current$"] = function(simc, actionName) return format("SpellCritChance(%s)", actionName) end, + ["^crit_tick_damage$"] = function(simc, actionName) return format("CritDamage(%s)", actionName) end, + ["^duration$"] = function(simc, actionName) + local symbol = format("%s_debuff", actionName) + tinsert(simc.symbols, symbol) + return format("SpellData(%s duration)", symbol) + end, + ["^enabled$"] = function(simc, actionName) + local symbol = format("%s_talent", actionName) + tinsert(simc.symbols, symbol) + return format("TalentPoints(%s)", symbol) + end, + ["^gcd$"] = function(simc, actionName) return format("GCD(%s)", actionName) end, + ["^hit_damage$"] = function(simc, actionName) return format("Damage(%s)", actionName) end, + ["^in_flight$"] = function(simc, actionName) return format("InFlightToTarget(%s)", actionName) end, + ["^in_flight_to_target$"] = function(simc, actionName) return format("InFlightToTarget(%s)", actionName) end, + ["^miss_react$"] = "True(miss_react)", + -- TODO: Buff? + ["^n_ticks$"] = function(simc, actionName) + local symbol = format("%s_debuff", actionName) + tinsert(simc.symbols, symbol) + return format("target.Ticks(%s)", symbol) + end, + ["^recharge_time$"] = function(simc, actionName) return format("SpellChargeCooldown(%s)", actionName) end, + -- TODO: Buff? + ["^remains$"] = function(simc, actionName) + if strmatch(actionName, "_totem$") then + return TOTEM_PROPERTY["^remains$"](simc, actionName) + else + local symbol = format("%s_debuff", actionName) + tinsert(simc.symbols, symbol) + return format("target.DebuffRemains(%s)", symbol) + end + end, + ["^shard_react$"] = "SoulShards() >= 1", + ["^spell_power$"] = function(simc, actionName) + local symbol = format("%s_debuff", actionName) + tinsert(simc.symbols, symbol) + return format("target.DebuffSpellpower(%s)", symbol) + end, + ["^tick_damage$"] = function(simc, actionName) return format("Damage(%s)", actionName) end, + ["^tick_multiplier$"] = function(simc, actionName) return format("target.DamageMultiplier(%s)", actionName) end, + -- TODO: Buff? + ["^tick_time$"] = function(simc, actionName) + local symbol = format("%s_debuff", actionName) + tinsert(simc.symbols, symbol) + return format("target.TickTime(%s)", symbol) + end, + -- TODO: Buff? + ["^ticking$"] = function(simc, actionName) + local symbol = format("%s_debuff", actionName) + tinsert(simc.symbols, symbol) + return format("target.DebuffPresent(%s)", symbol) + end, + -- TODO: Buff? + ["^ticks_remain$"] = function(simc, actionName) + local symbol = format("%s_debuff", actionName) + tinsert(simc.symbols, symbol) + return format("target.TicksRemain(%s)", symbol) + end, + ["^travel_time$"] = function(simc, actionName) return format("TravelTime(%s)", actionName) end, + } + + local CHARACTER_PROPERTY = { + ["^anticipation_charges"] = function(simc, pattern, token) + tinsert(simc.symbols, "anticipation_buff") + return "BuffStacks(anticipation_buff)" + end, + ["^burning_ember$"] = "BurningEmbers()", + ["^chi$"] = "Chi()", + ["^chi%.max$"] = "MaxChi()", + ["^combo_points$"] = "ComboPoints()", + ["^demonic_fury$"] = "DemonicFury()", + ["^eclipse$"] = "Eclipse()", + ["^eclipse_dir$"] = "EclipseDir()", + ["^energy$"] = "Energy()", + ["^energy%.regen$"] = "EnergyRegen()", + ["^energy%.time_to_max$"] = "TimeToMaxEnergy()", + ["^focus$"] = "Focus()", + ["^focus%.regen$"] = "FocusRegen()", + ["^focus%.time_to_max$"] = "TimeToMaxFocus()", + ["^health$"] = "Health()", + ["^health%.deficit$"] = "HealthMissing()", + ["^health%.max$"] = "MaxHealth()", + ["^health%.pct$"] = "HealthPercent()", + ["^holy_power$"] = "HolyPower()", + ["^incoming_damage_([%d]+)(m?s)$"] = function(simc, pattern, token) + local seconds, measure = strmatch(token, pattern) + seconds = tonumber(seconds) + if measure == "ms" then + seconds = seconds / 1000 + end + return format("IncomingDamage(%.3f)", seconds) + end, + ["^in_combat$"] = "InCombat()", + ["^level$"] = "Level()", + ["^mana$"] = "Mana()", + ["^mana%.deficit$"] = "ManaDeficit()", + ["^mana%.max$"] = "MaxMana()", + ["^mana%.max_nonproc$"] = "MaxMana()", + ["^mana%.pct$"] = "ManaPercent()", + ["^mana%.pct_nonproc$"] = "ManaPercent()", + ["^mana_gem_charges$"] = function(simc, pattern, token) + tinsert(simc.symbols, "mana_gem") + return "ItemCharges(mana_gem)" + end, + ["^mastery_value$"] = "MasteryEffect()", + ["^multiplier$"] = "DamageMultiplier()", + ["^position_front$"] = "False(position_front)", -- XXX + ["^rage$"] = "Rage()", + ["^rage%.max$"] = "MaxRage()", + ["^runic_power$"] = "RunicPower()", + ["^shadow_orb$"] = "ShadowOrbs()", + ["^soul_shards$"] = "SoulShards()", + ["^stat%.agility$"] = "Agility()", + ["^stat%.attack_power$"] = "AttackPower()", + ["^stat%.crit$"] = "CritRating()", + ["^stat%.crit_rating$"] = "CritRating()", + ["^stat%.energy$"] = "Energy()", + ["^stat%.focus$"] = "Focus()", + ["^stat%.haste_rating$"] = "HasteRating()", + ["^stat%.health$"] = "Health()", + ["^stat%.intellect$"] = "Intellect()", + ["^stat%.mana$"] = "Mana()", + ["^stat%.mastery_rating$"] = "MasteryRating()", + ["^stat%.maximum_energy$"] = "MaxEnergy()", + ["^stat%.maximum_focus$"] = "MaxFocus()", + ["^stat%.maximum_health$"] = "MaxHealth()", + ["^stat%.maximum_mana$"] = "MaxMana()", + ["^stat%.maximum_runic$"] = "MaxRunicPower()", + ["^stat%.rage$"] = "Rage()", + ["^stat%.runic$"] = "RunicPower()", + ["^stat%.spell_power$"] = "Spellpower()", + ["^stat%.spirit$"] = "Spirit()", + ["^stat%.stamina$"] = "Stamina()", + ["^stat%.strength$"] = "Strength()", + ["^time_to_die$"] = "TimeToDie()", + } + + -- aura.. + local AURA_PROPERTY_PATTERN = "^aura%.([%w_]+)%.([%w_]+)$" + local AURA_PROPERTY = { + ["^down$"] = function(simc, name) return format("BuffExpires(%s any=1)", name) end, + ["^stack$"] = function(simc, name) return format("BuffStacks(%s any=1)", name) end, + ["^react$"] = function(simc, name) return format("BuffPresent(%s any=1)", name) end, + ["^remains$"] = function(simc, name) return format("BuffRemains(%s any=1)", name) end, + ["^up$"] = function(simc, name) return format("BuffPresent(%s any=1)", name) end, + } + + -- (buff|debuff).. + local function BuffType(buffType) return (buffType == "debuff") and "Debuff" or "Buff" end + local BUFF_PROPERTY_PATTERN = "^(d?e?buff)%.([%w_]+)%.([%w_]+)$" + local BUFF_PROPERTY = { + -- XXX: Assume that the spell and the buff have the same name. + ["^cooldown_remains$"] = function(simc, buffType, name) return format("SpellCooldown(%s)", name) end, + ["^down$"] = function(simc, buffType, name) return format("%sExpires(%s_%s)", BuffType(buffType), name, buffType) end, + ["^duration$"] = function(simc, buffType, name) return format("SpellData(%s_%s duration)", name, buffType) end, + ["^stack$"] = function(simc, buffType, name) return format("%sStacks(%s_%s)", BuffType(buffType), name, buffType) end, + -- "react" is supposed to be a stack count, but it's used in almost every script as "up". + ["^react$"] = function(simc, buffType, name) return format("%sPresent(%s_%s)", BuffType(buffType), name, buffType) end, + ["^remains$"] = function(simc, buffType, name) return format("%sRemains(%s_%s)", BuffType(buffType), name, buffType) end, + ["^up$"] = function(simc, buffType, name) return format("%sPresent(%s_%s)", BuffType(buffType), name, buffType) end, + ["^value$"] = function(simc, buffType, name) return format("%sAmount(%s_%s)", BuffType(buffType), name, buffType) end, + } + + -- cooldown.. + local COOLDOWN_PROPERTY_PATTERN = "^cooldown%.([%w_]+)%.([%w_]+)$" + local COOLDOWN_PROPERTY = { + -- TODO: ItemCooldown? + ["^remains$"] = function(simc, spellName) return format("SpellCooldown(%s)", spellName) end, + } + + -- dot.. + local DOT_PROPERTY_PATTERN = "^dot%.([%w_]+)%.([%w_]+)$" + local DOT_HELPFUL = { + ["sacred_shield"] = true, + } + local function DotBuffName(dotName) return DOT_HELPFUL[dotName] and "Buff" or "Debuff" end + local function DotBuffSuffix(dotName) return DOT_HELPFUL[dotName] and "buff" or "debuff" end + local DOT_PROPERTY = { + ["^attack_power$"] = function(simc, dotName) return format("%sAttackPower(%s_%s)", DotBuffName(dotName), dotName, DotBuffSuffix(dotName)) end, + ["^crit_pct$"] = function(simc, dotName) return format("%sSpellCritChance(%s_%s)", DotBuffName(dotName), dotName, DotBuffSuffix(dotName)) end, + ["^duration$"] = function(simc, dotName) return format("%sDuration(%s_%s)", DotBuffName(dotName), dotName, DotBuffSuffix(dotName)) end, + ["^multiplier$"] = function(simc, dotName) return format("%sDamageMultiplier(%s_%s)", DotBuffName(dotName), dotName, DotBuffSuffix(dotName)) end, + ["^remains$"] = function(simc, dotName) return format("%sRemains(%s_%s)", DotBuffName(dotName), dotName, DotBuffSuffix(dotName)) end, + ["^spell_power$"] = function(simc, dotName) return format("%sSpellpower(%s_%s)", DotBuffName(dotName), dotName, DotBuffSuffix(dotName)) end, + -- TODO: Should really implement BuffDamage() that's akin to Damage/LastEstimatedDamage. + ["^tick_dmg$"] = function(simc, dotName) return format("LastEstimatedDamage(%s_%s)", dotName, DotBuffSuffix(dotName)) end, + ["^ticking$"] = function(simc, dotName) return format("%sPresent(%s_%s)", DotBuffName(dotName), dotName, DotBuffSuffix(dotName)) end, + ["^ticks$"] = function(simc, dotName) return format("Ticks(%s_%s)", dotName, DotBuffSuffix(dotName)) end, + ["^ticks_remain$"] = function(simc, dotName) return format("TicksRemain(%s_%s)", dotName, DotBuffSuffix(dotName)) end, + } + + -- talent.. + local TALENT_PROPERTY_PATTERN = "^talent%.([%w_]+)%.([%w_]+)$" + local TALENT_PROPERTY = { + ["^enabled$"] = function(simc, talentName) return format("TalentPoints(%s_talent)", talentName) end, + ["^disabled$"] = function(simc, talentName) return format("not TalentPoints(%s_talent)", talentName) end, + } + + -- glyph.. + local GLYPH_PROPERTY_PATTERN = "^glyph%.([%w_]+)%.([%w_]+)$" + local GLYPH_PROPERTY = { + ["^enabled$"] = function(simc, glyphName) return format("Glyph(glyph_of_%s)", glyphName) end, + ["^disabled$"] = function(simc, glyphName) return format("not Glyph(glyph_of_%s)", glyphName) end, + } + + -- trinket... + local TRINKET_PROPERTY_PATTERN = "^trinket%.([%w_]+)%.([%w_]+)%.([%w_]+)$" + local TRINKET_PROPERTY = { + ["^cooldown_remains"] = function(simc, procType, statName) return ("{ ItemCooldown(Trinket0Slot) + ItemCooldown(Trinket1Slot) }") end, + ["^down$"] = function(simc, procType, statName) return format("BuffPresent(trinket_%s_%s_buff)", procType, statName) end, + ["^react$"] = function(simc, procType, statName) + if strfind(procType, "stacking") then + return format("BuffStacks(trinket_%s_%s_buff)", procType, statName) + end + return format("BuffPresent(trinket_%s_%s_buff)", procType, statName) + end, + ["^remains$"] = function(simc, procType, statName) return format("BuffRemains(trinket_%s_%s_buff)", procType, statName) end, + ["^stack$"] = function(simc, procType, statName) return format("BuffStacks(trinket_%s_%s_buff)", procType, statName) end, + ["^up$"] = function(simc, procType, statName) return format("BuffPresent(trinket_%s_%s_buff)", procType, statName) end, + } + + local GENERAL_PROPERTY = { + ["^active_enemies$"] = "Enemies()", + ["^adds$"] = "Enemies()", + ["^ptr$"] = "PTR()", + ["^time$"] = "TimeInCombat()", + ["^time_to_bloodlust$"] = "TimeToBloodlust()", + } + + local TRANSLATED_TOKEN = { + ["^!$"] = "not", + ["^!=$"] = "!=", + ["^!~$"] = "!~", + ["^%%$"] = "/", + ["^%($"] = "{", + ["^%)$"] = "}", + ["^%*$"] = "*", + ["^%+$"] = "+", + ["^&$"] = "and", + ["^-$"] = "-", + ["^<$"] = "<", + ["^<=$"] = "<=", + ["^=$"] = "==", + ["^>$"] = ">", + ["^>=$"] = ">=", + ["^|$"] = "or", + ["^~$"] = "~", + } + + function OvaleSimulationCraft:ParseExpression(action, expr, addActionSymbol) + local translatedList = {} + local tokenIterator = OvaleLexer.scan(expr, MATCHES) + local tokenType, token = tokenIterator() + while tokenType do + local translated + if tokenType == "iden" then + -- Strip off "target." if present. + local isTargetFound = strmatch(token, "^target%.([%w_%.]+)") + if isTargetFound then + token = isTargetFound + end + if not translated then + -- Handle properties that have a special interpretation. + for pattern, result in pairs(SPECIAL_PROPERTY) do + if strmatch(token, pattern) then + translated = (type(result) == "function") and result(self, action) or result + break + end + end + end + if not translated then + -- Bare action properties. + if addActionSymbol then + tinsert(self.symbols, action) + end + for pattern, result in pairs(ACTION_PROPERTY) do + if strmatch(token, pattern) then + translated = (type(result) == "function") and result(self, action) or result + break + end + end + end + if not translated then + -- Action properties for actions other than the one for this action line. + local name, property = strmatch(token, ACTION_PROPERTY_PATTERN) + if name then + name = self:Name(name) + tinsert(self.symbols, name) + for pattern, result in pairs(ACTION_PROPERTY) do + if strmatch(property, pattern) then + translated = (type(result) == "function") and result(self, name) or result + break + end + end + end + end + if not translated then + for pattern, result in pairs(CHARACTER_PROPERTY) do + if strmatch(token, pattern) then + translated = (type(result) == "function") and result(self, pattern, token) or result + break + end + end + end + if not translated then + local name, property = strmatch(token, AURA_PROPERTY_PATTERN) + if name then + name = self:Name(name) + tinsert(self.symbols, name) + for pattern, result in pairs(AURA_PROPERTY) do + if strmatch(property, pattern) then + translated = (type(result) == "function") and result(self, name) or result + break + end + end + end + end + if not translated then + local buffType, name, property = strmatch(token, BUFF_PROPERTY_PATTERN) + if buffType then + if buffType == "debuff" then + -- Debuffs default to checking on the target. + isTargetFound = true + end + name = self:Name(name) + tinsert(self.symbols, format("%s_%s", name, buffType)) + for pattern, result in pairs(BUFF_PROPERTY) do + if strmatch(property, pattern) then + translated = (type(result) == "function") and result(self, buffType, name) or result + break + end + end + end + end + if not translated then + local name, property = strmatch(token, TOTEM_PROPERTY_PATTERN) + if name then + name = self:Name(name) + if TOTEM_TYPE[name] then + tinsert(self.symbols, name) + end + for pattern, result in pairs(TOTEM_PROPERTY) do + if strmatch(property, pattern) then + translated = (type(result) == "function") and result(self, name) or result + break + end + end + end + end + if not translated then + local name, property = strmatch(token, COOLDOWN_PROPERTY_PATTERN) + if name then + name = self:Name(name) + tinsert(self.symbols, name) + for pattern, result in pairs(COOLDOWN_PROPERTY) do + if strmatch(property, pattern) then + translated = (type(result) == "function") and result(self, name) or result + break + end + end + end + end + if not translated then + local name, property = strmatch(token, TALENT_PROPERTY_PATTERN) + if name then + tinsert(self.symbols, format("%s_talent", name)) + for pattern, result in pairs(TALENT_PROPERTY) do + if strmatch(property, pattern) then + translated = (type(result) == "function") and result(self, name) or result + break + end + end + end + end + if not translated then + local name, property = strmatch(token, GLYPH_PROPERTY_PATTERN) + if name then + tinsert(self.symbols, format("glyph_of_%s", name)) + for pattern, result in pairs(GLYPH_PROPERTY) do + if strmatch(property, pattern) then + translated = (type(result) == "function") and result(self, name) or result + break + end + end + end + end + if not translated then + local name, property = strmatch(token, DOT_PROPERTY_PATTERN) + if name then + -- DoTs default to checking on the target. + isTargetFound = true + name = self:Name(name) + tinsert(self.symbols, format("%s_%s", name, DotBuffSuffix(name))) + for pattern, result in pairs(DOT_PROPERTY) do + if strmatch(property, pattern) then + translated = (type(result) == "function") and result(self, name) or result + break + end + end + end + end + if not translated then + local procType, name, property = strmatch(token, TRINKET_PROPERTY_PATTERN) + if procType then + tinsert(self.symbols, format("trinket_%s_%s_buff", procType, name)) + for pattern, result in pairs(TRINKET_PROPERTY) do + if strmatch(property, pattern) then + translated = (type(result) == "function") and result(self, procType, name) or result + break + end + end + end + end + if not translated then + -- set_bonus._pc_ + local name, count, role = strmatch(token, "^set_bonus%.(%w+)_(%d+)pc_(%w+)$") + if name and count and role then + local tierLevel = strmatch(name, "^tier(%d+)") + if tierLevel then + name = format("T%s", tierLevel) + end + translated = format("ArmorSetBonus(%s_%s %s)", name, role, count) + end + end + if not translated then + for pattern, result in pairs(GENERAL_PROPERTY) do + if strmatch(token, pattern) then + translated = (type(result) == "function") and result(self, action) or result + break + end + end + end + if not translated then + translated = format("FIXME_%s", token) + end + if isTargetFound then + translated = "target." .. translated + end + elseif tokenType == "number" then + translated = token + end + if not translated then + for pattern, result in pairs(TRANSLATED_TOKEN) do + if strmatch(token, pattern) then + translated = (type(result) == "function") and result(self, token) or result + break + end + end + end + if not translated then + translated = format("FIXME_%s", token) + end + tinsert(translatedList, translated) + tokenType, token = tokenIterator() + end + local translation = tconcat(translatedList, " ") + return translation + end +end +-- diff --git a/compiler.pl b/compiler.pl index 39059c5..24727c9 100644 --- a/compiler.pl +++ b/compiler.pl @@ -102,10 +102,12 @@ $m{Skada}{get_player} = true; $p{Skada}{current} = true; $p{Skada}{total} = true; +$sp{Ovale}{OvaleLexer} = true; $sp{Ovale}{OvalePool} = true; $sp{Ovale}{OvalePoolGC} = true; $sp{Ovale}{OvalePoolRefCount} = true; $sp{Ovale}{OvaleQueue} = true; +$sp{Ovale}{OvaleSimulationCraft} = true; $sp{Ovale}{OvaleSkada} = true; $sp{Ovale}{OvaleTimeSpan} = true; -- 1.7.9.5