Johnny C. Lam [04-21-14 - 05:03]
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
+
+--<private-static-properties>
+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'
+--</private-static-properties>
+
+--<private-static-methods>
+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
+--<private-static-methods>
+
+--<public-static-methods>
+--- 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. <br>
+-- 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
+--</public-static-methods>
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
+
+--<private-static-properties>
+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
+--<private-static-properties>
+
+--<public-static-properties>
+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
+--</public-static-properties>
+
+--<private-static-methods>
+local function NameValuePair(expr)
+ local name, value = strmatch(expr, "^([^=]*)=(.*)")
+ if strmatch(value, "^[%-]?%d+%.?%d*$") then
+ value = tonumber(value)
+ end
+ return name, value
+end
+--</private-static-methods>
+
+--<public-static-methods>
+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.<totem_name>.<totem_property>
+ 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.<aura_name>.<aura_property>
+ 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).<aura_name>.<aura_property>
+ 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.<spell_name>.<cooldown_property>
+ local COOLDOWN_PROPERTY_PATTERN = "^cooldown%.([%w_]+)%.([%w_]+)$"
+ local COOLDOWN_PROPERTY = {
+ -- TODO: ItemCooldown?
+ ["^remains$"] = function(simc, spellName) return format("SpellCooldown(%s)", spellName) end,
+ }
+
+ -- dot.<dot_name>.<dot_property>
+ 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.<talent_name>.<talent_property>
+ 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.<glyph_name>.<glyph_property>
+ 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.<proc_type>.<stat>.<trinket_property>
+ 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.<set_name>_<N>pc_<role>
+ 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
+--</public-static-methods>
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;