Quantcast
--[[--------------------------------------------------------------------
    Copyright (C) 2014 Johnny C. Lam.
    See the file LICENSE.txt for copying permission.
--]]--------------------------------------------------------------------

local OVALE, Ovale = ...
local OvaleSimulationCraft = Ovale:NewModule("OvaleSimulationCraft")
Ovale.OvaleSimulationCraft = OvaleSimulationCraft

--<private-static-properties>
local AceConfig = LibStub("AceConfig-3.0")
local AceConfigDialog = LibStub("AceConfigDialog-3.0")
local L = Ovale.L
local OvaleDebug = Ovale.OvaleDebug
local OvaleOptions = Ovale.OvaleOptions
local OvalePool = Ovale.OvalePool

-- Forward declarations for module dependencies.
local OvaleAST = nil
local OvaleCompile = nil
local OvaleData = nil
local OvaleHonorAmongThieves = nil
local OvaleLexer = nil
local OvalePower = nil

local format = string.format
local gmatch = string.gmatch
local gsub = string.gsub
local ipairs = ipairs
local pairs = pairs
local rawset = rawset
local strfind = string.find
local strlen = string.len
local strlower = string.lower
local strmatch = string.match
local strsub = string.sub
local strupper = string.upper
local tconcat = table.concat
local tinsert = table.insert
local tonumber = tonumber
local tostring = tostring
local tremove = table.remove
local tsort = table.sort
local type = type
local wipe = wipe
local yield = coroutine.yield
local RAID_CLASS_COLORS = RAID_CLASS_COLORS

-- Keywords for SimulationCraft action lists.
local KEYWORD = {}

local MODIFIER_KEYWORD = {
	["ammo_type"] = true,
	["chain"] = true,
	["choose"] = true,
	["cooldown"] = true,
	["cooldown_stddev"] = true,
	["cycle_targets"] = true,
	["damage"] = true,
	["early_chain_if"] = true,
	["extra_amount"] = true,
	["five_stacks"] = true,
	["for_next"] = true,
	["if"] = true,
	["interrupt"] = true,
	["interrupt_if"] = true,
	["lethal"] = true,
	["line_cd"] = true,
	["max_cycle_targets"] = true,
	["max_energy"] = true,
	["moving"] = true,
	["name"] = true,
	["sec"] = true,
	["slot"] = true,
	["sync"] = true,
	["sync_weapons"] = true,
	["target"] = true,
	["travel_speed"] = true,
	["type"] = true,
	["wait"] = true,
	["wait_on_ready"] = true,
	["weapon"] = true,
}

local FUNCTION_KEYWORD = {
	["ceil"] = true,
	["floor"] = true,
}

local SPECIAL_ACTION = {
	["apply_poison"] = true,
	["auto_attack"] = true,
	["call_action_list"] = true,
	["cancel_buff"] = true,
	["cancel_metamorphosis"] = true,
	["exotic_munitions"] = true,
	["flask"] = true,
	["food"] = true,
	["health_stone"] = true,
	["pool_resource"] = true,
	["potion"] = true,
	["run_action_list"] = true,
	["snapshot_stats"] = true,
	["stance"] = true,
	["start_moving"] = true,
	["stealth"] = true,
	["stop_moving"] = true,
	["swap_action_list"] = true,
	["use_item"] = true,
	["wait"] = true,
}

local RUNE_OPERAND = {
	["Blood"] = "blood",
	["Frost"] = "frost",
	["Unholy"] = "unholy",
	["blood"] = "blood",
	["death"] = "death",
	["frost"] = "frost",
	["unholy"] = "unholy",
	["rune.blood"] = "blood",
	["rune.death"] = "death",
	["rune.frost"] = "frost",
	["rune.unholy"] = "unholy",
}

do
	-- All expression keywords are keywords.
	for keyword, value in pairs(MODIFIER_KEYWORD) do
		KEYWORD[keyword] = value
	end
	-- All function keywords are keywords.
	for keyword, value in pairs(FUNCTION_KEYWORD) do
		KEYWORD[keyword] = value
	end
	-- All special actions are keywords.
	for keyword, value in pairs(SPECIAL_ACTION) do
		KEYWORD[keyword] = value
	end
end

-- Table of pattern/tokenizer pairs for SimulationCraft action lists.
local MATCHES = nil

-- Unary and binary operators with precedence.
local UNARY_OPERATOR = {
	["!"]  = { "logical", 15 },
	["-"]  = { "arithmetic", 50 },
}
local BINARY_OPERATOR = {
	-- logical
	["|"]  = { "logical", 5, "associative" },
	["^"]  = { "logical", 8, "associative" },
	["&"]  = { "logical", 10, "associative" },
	-- comparison
	["!="] = { "compare", 20 },
	["<"]  = { "compare", 20 },
	["<="] = { "compare", 20 },
	["="]  = { "compare", 20 },
	[">"]  = { "compare", 20 },
	[">="] = { "compare", 20 },
	["~"]  = { "compare", 20 },
	["!~"] = { "compare", 20 },
	-- addition, subtraction
	["+"]  = { "arithmetic", 30, "associative" },
	["-"]  = { "arithmetic", 30 },
	-- multiplication, division, modulus
	["%"]  = { "arithmetic", 40 },
	["*"]  = { "arithmetic", 40, "associative" },
}

-- INDENT[k] is a string of k concatenated tabs.
local INDENT = {}
do
	INDENT[0] = ""
	local metatable = {
		__index = function(tbl, key)
			key = tonumber(key)
			if key > 0 then
				local s = tbl[key - 1] .. "\t"
				rawset(tbl, key, s)
				return s
			end
			return INDENT[0]
		end,
	}
	setmetatable(INDENT, metatable)
end

local EMIT_DISAMBIGUATION = {}
local EMIT_EXTRA_PARAMETERS = {}
local OPERAND_TOKEN_PATTERN = "[^.]+"

local POTION_STAT = {
	["draenic_agility"]		= "agility",
	["draenic_armor"]		= "armor",
	["draenic_intellect"]	= "intellect",
	["draenic_strength"]	= "strength",
	["jade_serpent"]		= "intellect",
	["mogu_power"]			= "strength",
	["mountains"]			= "armor",
	["tolvir"]				= "agility",
	["virmens_bite"]		= "agility",
}

local self_outputPool = OvalePool("OvaleSimulationCraft_outputPool")
local self_childrenPool = OvalePool("OvaleSimulationCraft_childrenPool")
local self_pool = OvalePool("OvaleSimulationCraft_pool")
do
	self_pool.Clean = function(self, node)
		if node.child then
			self_childrenPool:Release(node.child)
			node.child = nil
		end
	end
end

-- Save the most recent profile entered into the SimulationCraft input window.
local self_lastSimC = nil
-- Save the most recent script translated from the profile in the SimulationCraft input window.
local self_lastScript = nil

do
	-- Add a slash command "/ovale simc" to access the GUI for this module.
	local actions = {
		simc  = {
			name = "SimulationCraft",
			type = "execute",
			func = function()
				local appName = OvaleSimulationCraft:GetName()
				AceConfigDialog:SetDefaultSize(appName, 700, 550)
				AceConfigDialog:Open(appName)
			end,
		},
	}
	-- Inject into OvaleOptions.
	for k, v in pairs(actions) do
		OvaleOptions.options.args.actions.args[k] = v
	end
	OvaleOptions:RegisterOptions(OvaleSimulationCraft)
end

-- XXX Temporary hard-coding of tags and tag priorities.
local OVALE_TAGS = { "main", "shortcd", "cd" }
local OVALE_TAG_PRIORITY = {}
do
	for i, tag in pairs(OVALE_TAGS) do
		OVALE_TAG_PRIORITY[tag] = i * 10
	end
end
--</private-static-properties>

--<private-static-methods>
-- Implementation of PHP-like print_r() taken from http://lua-users.org/wiki/TableSerialization.
-- This is used to print out a table, but has been modified to print out an AST.
local function print_r(node, indent, done, output)
	done = done or {}
	output = output or {}
	indent = indent or ''
	for key, value in pairs(node) do
		if type(value) == "table" then
			if done[value] then
				tinsert(output, indent .. "[" .. tostring(key) .. "] => (self_reference)")
			else
				-- Shortcut conditional allocation
				done[value] = true
				tinsert(output, indent .. "[" .. tostring(key) .. "] => {")
				print_r(value, indent .. "    ", done, output)
				tinsert(output, indent .. "}")
			end
		else
			tinsert(output, indent .. "[" .. tostring(key) .. "] => " .. tostring(value))
		end
	end
	return output
end

-- Get a new node from the pool and save it in the nodes array.
local function NewNode(nodeList, hasChild)
	local node = self_pool:Get()
	if nodeList then
		local nodeId = #nodeList + 1
		node.nodeId = nodeId
		nodeList[nodeId] = node
	end
	if hasChild then
		node.child = self_childrenPool:Get()
	end
	return node
end

--[[---------------------------------------------
	Lexer functions (for use with OvaleLexer)
--]]---------------------------------------------
local function TokenizeName(token)
	if KEYWORD[token] then
		return yield("keyword", token)
	else
		return yield("name", token)
	end
end

local function TokenizeNumber(token, options)
	if options and options.number then
		token = tonumber(token)
	end
	return yield("number", token)
end

local function Tokenize(token)
	return yield(token, token)
end

local function NoToken()
	return yield(nil)
end

do
	MATCHES = {
		{ "^%d+%.?%d*", TokenizeNumber },
		{ "^[%a_][%w_]*[.:]?[%w_.]*", TokenizeName },
		{ "^!=", Tokenize },
		{ "^<=", Tokenize },
		{ "^>=", Tokenize },
		{ "^!~", Tokenize },
		{ "^.", Tokenize },
		{ "^$", NoToken },
	}
end

local function GetTokenIterator(s)
	local exclude = { space = false, comments = false }
	return OvaleLexer.scan(s, MATCHES, exclude)
end

--[[------------------------
	"Unparser" functions
--]]------------------------

-- Return the precedence of an operator in the given node.
-- Returns nil if the node is not an expression node.
local function GetPrecedence(node)
	local precedence = node.precedence
	if not precedence then
		local operator = node.operator
		if operator then
			if node.expressionType == "unary" and UNARY_OPERATOR[operator] then
				precedence = UNARY_OPERATOR[operator][2]
			elseif node.expressionType == "binary" and BINARY_OPERATOR[operator] then
				precedence = BINARY_OPERATOR[operator][2]
			end
		end
	end
	return precedence
end

local UNPARSE_VISITOR = nil

local function Unparse(node)
	local visitor = UNPARSE_VISITOR[node.type]
	if not visitor then
		OvaleSimulationCraft:Error("Unable to unparse node of type '%s'.", node.type)
	else
		return visitor(node)
	end
end

local function UnparseAction(node)
	local output = self_outputPool:Get()
	output[#output + 1] = node.name
	for modifier, expressionNode in pairs(node.child) do
		output[#output + 1] = modifier .. "=" .. Unparse(expressionNode)
	end
	local s = tconcat(output, ",")
	self_outputPool:Release(output)
	return s
end

local function UnparseActionList(node)
	local output = self_outputPool:Get()
	local listName
	if node.name == "_default" then
		listName = "action"
	else
		listName = "action." .. node.name
	end
	output[#output + 1] = ""
	for i, actionNode in pairs(node.child) do
		local operator = (i == 1) and "=" or "+=/"
		output[#output + 1] = listName .. operator .. Unparse(actionNode)
	end
	local s = tconcat(output, "\n")
	self_outputPool:Release(output)
	return s
end

local function UnparseExpression(node)
	local expression
	local precedence = GetPrecedence(node)
	if node.expressionType == "unary" then
		local rhsExpression
		local rhsNode = node.child[1]
		local rhsPrecedence = GetPrecedence(rhsNode)
		if rhsPrecedence and precedence >= rhsPrecedence then
			rhsExpression = "(" .. Unparse(rhsNode) .. ")"
		else
			rhsExpression = Unparse(rhsNode)
		end
		expression = node.operator .. rhsExpression
	elseif node.expressionType == "binary" then
		local lhsExpression, rhsExpression
		local lhsNode = node.child[1]
		local lhsPrecedence = GetPrecedence(lhsNode)
		if lhsPrecedence and lhsPrecedence < precedence then
			lhsExpression = "(" .. Unparse(lhsNode) .. ")"
		else
			lhsExpression = Unparse(lhsNode)
		end
		local rhsNode = node.child[2]
		local rhsPrecedence = GetPrecedence(rhsNode)
		if rhsPrecedence and precedence > rhsPrecedence then
			rhsExpression = "(" .. Unparse(rhsNode) .. ")"
		elseif rhsPrecedence and precedence == rhsPrecedence then
			if BINARY_OPERATOR[node.operator][3] == "associative" then
				rhsExpression = Unparse(rhsNode)
			else
				rhsExpression = "(" .. Unparse(rhsNode) .. ")"
			end
		else
			rhsExpression = Unparse(rhsNode)
		end
		expression = lhsExpression .. node.operator .. rhsExpression
	end
	return expression
end

local function UnparseFunction(node)
	return node.name .. "(" .. Unparse(node.child[1]) .. ")"
end

local function UnparseNumber(node)
	return tostring(node.value)
end

local function UnparseOperand(node)
	return node.name
end

do
	UNPARSE_VISITOR = {
		["action"] = UnparseAction,
		["action_list"] = UnparseActionList,
		["arithmetic"] = UnparseExpression,
		["compare"] = UnparseExpression,
		["function"] = UnparseFunction,
		["logical"] = UnparseExpression,
		["number"] = UnparseNumber,
		["operand"] = UnparseOperand,
	}
end

--[[--------------------
	Parser functions
--]]--------------------

-- Prints the error message and the next 20 tokens from tokenStream.
local function SyntaxError(tokenStream, ...)
	OvaleSimulationCraft:Print(...)
	local context = { "Next tokens:" }
	for i = 1, 20 do
		local tokenType, token = tokenStream:Peek(i)
		if tokenType then
			context[#context + 1] = token
		else
			context[#context + 1] = "<EOS>"
			break
		end
	end
	OvaleSimulationCraft:Print(tconcat(context, " "))
end

-- Left-rotate tree to preserve precedence.
local function LeftRotateTree(node)
	local rhsNode = node.child[2]
	while node.type == rhsNode.type and node.operator == rhsNode.operator and BINARY_OPERATOR[node.operator][3] == "associative" and rhsNode.expressionType == "binary" do
		node.child[2] = rhsNode.child[1]
		rhsNode.child[1] = node
		node = rhsNode
		rhsNode = node.child[2]
	end
	return node
end

-- Forward declarations of parser functions needed to implement a recursive descent parser.
local ParseAction = nil
local ParseActionList = nil
local ParseExpression = nil
local ParseFunction = nil
local ParseModifier = nil
local ParseNumber = nil
local ParseOperand = nil
local ParseParentheses = nil
local ParseSimpleExpression = nil

local function TicksRemainTranslationHelper(p1, p2, p3, p4)
	if p4 then
		return p1 .. p2 .. "<" .. tostring(tonumber(p4) + 1)
	else
		return p1 .. "<" .. tostring(tonumber(p3) + 1)
	end
end

ParseAction = function(action, nodeList, annotation)
	local ok = true
	local stream = action
	do
		-- Fix "|" being silently replaced by "||" in WoW strings entered via an edit box.
		stream = gsub(stream, "||", "|")
	end
	do
		-- Fix bugs in SimulationCraft action lists.
		-- ",," into ","
		stream = gsub(stream, ",,", ",")
	end
	do
		-- Changes to SimulationCraft action lists for easier translation into Ovale timespan concept.
		-- "active_dot.dotName=0" into "!(active_dot.dotName>0)"
		stream = gsub(stream, "(active_dot%.[%w_]+)=0", "!(%1>0)")
		-- "cooldown_remains=0" into "!(cooldown_remains>0)"
		stream = gsub(stream, "([^_%.])(cooldown_remains)=0", "%1!(%2>0)")
		stream = gsub(stream, "([a-z_%.]+%.cooldown_remains)=0", "!(%1>0)")
		-- "remains=0" into "!(remains>0)"
		stream = gsub(stream, "([^_%.])(remains)=0", "%1!(%2>0)")
		stream = gsub(stream, "([a-z_%.]+%.remains)=0", "!(%1>0)")
		-- "ticks_remain=1" into "ticks_remain<2"
		-- "ticks_remain<=N" into "ticks_remain<N+1"
		stream = gsub(stream, "([^_%.])(ticks_remain)(<?=)([0-9]+)", TicksRemainTranslationHelper)
		stream = gsub(stream, "([a-z_%.]+%.ticks_remain)(<?=)([0-9]+)", TicksRemainTranslationHelper)
	end
	local tokenStream = OvaleLexer("SimulationCraft", GetTokenIterator(stream))
	-- Consume the action.
	local name
	do
		local tokenType, token = tokenStream:Consume()
		if (tokenType == "keyword" and SPECIAL_ACTION[token]) or tokenType == "name" then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing action line; name or special action expected.", token)
			ok = false
		end
	end
	local child = self_childrenPool:Get()
	if ok then
		local tokenType, token = tokenStream:Peek()
		while ok and tokenType do
			if tokenType == "," then
				-- Consume the ',' token.
				tokenStream:Consume()
				local modifier, expressionNode
				ok, modifier, expressionNode = ParseModifier(tokenStream, nodeList, annotation)
				if ok then
					child[modifier] = expressionNode
					tokenType, token = tokenStream:Peek()
				end
			else
				SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing action line; ',' expected.", token)
				ok = false
			end
		end
	end
	local node
	if ok then
		node = NewNode(nodeList)
		node.type = "action"
		node.action = action
		node.name = name
		node.child = child
	else
		self_childrenPool:Release(child)
	end
	return ok, node
end

ParseActionList = function(name, actionList, nodeList, annotation)
	local ok = true
	local child = self_childrenPool:Get()
	for action in gmatch(actionList, "[^/]+") do
		local actionNode
		ok, actionNode = ParseAction(action, nodeList, annotation)
		if ok then
			child[#child + 1] = actionNode
		else
			break
		end
	end
	local node
	if ok then
		node = NewNode(nodeList)
		node.type = "action_list"
		node.name = name
		node.child = child
	else
		self_childrenPool:Release(child)
	end
	return ok, node
end

--[[
	Operator-precedence parser for logical and arithmetic expressions.
	Implementation taken from Wikipedia:
		http://en.wikipedia.org/wiki/Operator-precedence_parser
--]]
ParseExpression = function(tokenStream, nodeList, annotation, minPrecedence)
	minPrecedence = minPrecedence or 0
	local ok = true
	local node

	-- Check for unary operator expressions first as they decorate the underlying expression.
	do
		local tokenType, token = tokenStream:Peek()
		if tokenType then
			local opInfo = UNARY_OPERATOR[token]
			if opInfo then
				local opType, precedence = opInfo[1], opInfo[2]
				local asType = (opType == "logical") and "boolean" or "value"
				tokenStream:Consume()
				local operator = token
				local rhsNode
				ok, rhsNode = ParseExpression(tokenStream, nodeList, annotation, precedence)
				if ok then
					if operator == "-" and rhsNode.type == "number" then
						-- Elide the unary negation operator into the number.
						rhsNode.value = -1 * rhsNode.value
						node = rhsNode
					else
						node = NewNode(nodeList, true)
						node.type = opType
						node.expressionType = "unary"
						node.operator = operator
						node.precedence = precedence
						node.child[1] = rhsNode
						rhsNode.asType = asType
					end
				end
			else
				ok, node = ParseSimpleExpression(tokenStream, nodeList, annotation)
				if ok and node then
					node.asType = "boolean"
				end
			end
		end
	end

	-- Peek at the next token to see if it is a binary operator.
	while ok do
		local keepScanning = false
		local tokenType, token = tokenStream:Peek()
		if tokenType then
			local opInfo = BINARY_OPERATOR[token]
			if opInfo then
				local opType, precedence = opInfo[1], opInfo[2]
				local asType = (opType == "logical") and "boolean" or "value"
				if precedence and precedence > minPrecedence then
					keepScanning = true
					tokenStream:Consume()
					local operator = token
					local lhsNode = node
					local rhsNode
					ok, rhsNode = ParseExpression(tokenStream, nodeList, annotation, precedence)
					if ok then
						node = NewNode(nodeList, true)
						node.type = opType
						node.expressionType = "binary"
						node.operator = operator
						node.precedence = precedence
						node.child[1] = lhsNode
						node.child[2] = rhsNode
						lhsNode.asType = asType
						rhsNode.asType = asType
						-- Left-rotate tree to preserve precedence.
						node = LeftRotateTree(node)
					end
				end
			end
		end
		if not keepScanning then
			break
		end
	end

	return ok, node
end

ParseFunction = function(tokenStream, nodeList, annotation)
	local ok = true
	local name
	-- Consume the name.
	do
		local tokenType, token = tokenStream:Consume()
		if tokenType == "keyword" and FUNCTION_KEYWORD[token] then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing FUNCTION; name expected.", token)
			ok = false
		end
	end
	-- Consume the left parenthesis.
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType ~= "(" then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing FUNCTION; '(' expected.", token)
			ok = false
		end
	end
	-- Consume the function argument.
	local argumentNode
	if ok then
		ok, argumentNode = ParseExpression(tokenStream, nodeList, annotation)
	end
	-- Consume the right parenthesis.
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType ~= ")" then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing FUNCTION; ')' expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = NewNode(nodeList, true)
		node.type = "function"
		node.name = name
		node.child[1] = argumentNode
	end
	return ok, node
end

ParseModifier = function(tokenStream, nodeList, annotation)
	local ok = true
	local name
	do
		local tokenType, token = tokenStream:Consume()
		if tokenType == "keyword" and MODIFIER_KEYWORD[token] then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing action line; expression keyword expected.", token)
			ok = false
		end
	end
	if ok then
		-- Consume the '=' token.
		local tokenType, token = tokenStream:Consume()
		if tokenType ~= "=" then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing action line; '=' expected.", token)
			ok = false
		end
	end
	local expressionNode
	if ok then
		ok, expressionNode = ParseExpression(tokenStream, nodeList, annotation)
		if ok and expressionNode and name == "sec" then
			expressionNode.asType = "value"
		end
	end
	return ok, name, expressionNode
end

ParseNumber = function(tokenStream, nodeList, annotation)
	local ok = true
	local value
	-- Consume the number.
	do
		local tokenType, token = tokenStream:Consume()
		if tokenType == "number" then
			value = tonumber(token)
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing NUMBER; number expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = NewNode(nodeList)
		node.type = "number"
		node.value = value
	end
	return ok, node
end

ParseOperand = function(tokenStream, nodeList, annotation)
	local ok = true
	local name
	-- Consume the operand.
	do
		local tokenType, token = tokenStream:Consume()
		if tokenType == "name" then
			name = token
		elseif tokenType == "keyword" and token == "target" then
			-- Allow a bare "target" to be used as an operand.
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing OPERAND; operand expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = NewNode(nodeList)
		node.type = "operand"
		node.name = name
		node.rune = RUNE_OPERAND[name]
		if node.rune then
			local firstCharacter = strsub(name, 1, 1)
			node.includeDeath = (firstCharacter == "B" or firstCharacter == "F" or firstCharacter == "U")
		end
		annotation.operand = annotation.operand or {}
		annotation.operand[#annotation.operand + 1] = node
	end
	return ok, node
end

ParseParentheses = function(tokenStream, nodeList, annotation)
	local ok = true
	local leftToken, rightToken
	-- Consume the left parenthesis.
	do
		local tokenType, token = tokenStream:Consume()
		if tokenType == "(" then
			leftToken, rightToken = "(", ")"
		elseif tokenType == "{" then
			leftToken, rightToken = "{", "}"
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing PARENTHESES; '(' or '{' expected.", token)
			ok = false
		end
	end
	-- Consume the inner expression.
	local node
	if ok then
		ok, node = ParseExpression(tokenStream, nodeList, annotation)
	end
	-- Consume the right parenthesis.
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType ~= rightToken then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing PARENTHESES; '%s' expected.", token, rightToken)
			ok = false
		end
	end
	-- Create the AST node.
	if ok then
		node.left = leftToken
		node.right = rightToken
	end
	return ok, node
end

ParseSimpleExpression = function(tokenStream, nodeList, annotation)
	local ok = true
	local node
	local tokenType, token = tokenStream:Peek()
	if tokenType == "number" then
		ok, node = ParseNumber(tokenStream, nodeList, annotation)
	elseif tokenType == "keyword" then
		if FUNCTION_KEYWORD[token] then
			ok, node = ParseFunction(tokenStream, nodeList, annotation)
		elseif token == "target" then
			ok, node = ParseOperand(tokenStream, nodeList, annotation)
		end
	elseif tokenType == "name" then
		ok, node = ParseOperand(tokenStream, nodeList, annotation)
	elseif tokenType == "(" then
		ok, node = ParseParentheses(tokenStream, nodeList, annotation)
	else
		SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing SIMPLE EXPRESSION", token)
		tokenStream:Consume()
		ok = false
	end
	return ok, node
end

--[[-----------------------------
	Code generation functions
--]]-----------------------------

local CamelCase = nil
do
	local function CamelCaseHelper(first, rest)
		return strupper(first) .. strlower(rest)
	end

	CamelCase = function(s)
		local tc = gsub(s, "(%a)(%w*)", CamelCaseHelper)
		return gsub(tc, "[%s_]", "")
	end
end

local function OvaleFunctionName(name, annotation)
	local output = self_outputPool:Get()
	local profileName, class, specialization = annotation.name, annotation.class, annotation.specialization
	if specialization then
		output[#output + 1] = specialization
	end
	if strmatch(profileName, "_1[hH]_") then
		if class == "DEATHKNIGHT" and specialization == "frost" then
			output[#output + 1] = "dual wield"
		elseif class == "WARRIOR" and specialization == "fury" then
			output[#output + 1] = "single minded fury"
		end
	elseif strmatch(profileName, "_2[hH]_") then
		if class == "DEATHKNIGHT" and specialization == "frost" then
			output[#output + 1] = "two hander"
		elseif class == "WARRIOR" and specialization == "fury" then
			output[#output + 1] = "titans grip"
		end
	elseif strmatch(profileName, "_[gG]ladiator_") then
		output[#output + 1] = "gladiator"
	end
	output[#output + 1] = name
	output[#output + 1] = "actions"
	local outputString = CamelCase(tconcat(output, " "))
	self_outputPool:Release(output)
	return outputString
end

local function AddSymbol(annotation, symbol)
	local symbolTable = annotation.symbolTable or {}
	-- Add the symbol to the table if it's not already present and it's not a globally-defined spell list name.
	if not symbolTable[symbol] and not OvaleData.buffSpellList[symbol] then
		symbolTable[symbol] = true
		symbolTable[#symbolTable + 1] = symbol
	end
	annotation.symbolTable = symbolTable
end

local function AddPerClassSpecialization(tbl, name, info, class, specialization)
	class = class or "ALL_CLASSES"
	specialization = specialization or "ALL_SPECIALIZATIONS"
	tbl[class] = tbl[class] or {}
	tbl[class][specialization] = tbl[class][specialization] or {}
	tbl[class][specialization][name] = info
end

local function GetPerClassSpecialization(tbl, name, class, specialization)
	local info
	while not info do
		while not info do
			if tbl[class] and tbl[class][specialization] and tbl[class][specialization][name] then
				info = tbl[class][specialization][name]
			end
			if specialization ~= "ALL_SPECIALIZATIONS" then
				specialization = "ALL_SPECIALIZATIONS"
			else
				break
			end
		end
		if class ~= "ALL_CLASSES" then
			class = "ALL_CLASSES"
		else
			break
		end
	end
	return info
end

local function AddDisambiguation(name, info, class, specialization)
	AddPerClassSpecialization(EMIT_DISAMBIGUATION, name, info, class, specialization)
end

local function Disambiguate(name, class, specialization)
	return GetPerClassSpecialization(EMIT_DISAMBIGUATION, name, class, specialization) or name
end

local function InitializeDisambiguation()
	AddDisambiguation("bloodlust_buff",			"burst_haste_buff")
	AddDisambiguation("trinket_proc_all_buff",	"trinket_proc_any_buff")
	-- Death Knight
	AddDisambiguation("arcane_torrent",			"arcane_torrent_runicpower",	"DEATHKNIGHT")
	AddDisambiguation("blood_fury",				"blood_fury_ap",				"DEATHKNIGHT")
	AddDisambiguation("breath_of_sindragosa_debuff",	"breath_of_sindragosa_buff",	"DEATHKNIGHT")
	AddDisambiguation("soul_reaper",			"soul_reaper_blood",			"DEATHKNIGHT",	"blood")
	AddDisambiguation("soul_reaper",			"soul_reaper_frost",			"DEATHKNIGHT",	"frost")
	AddDisambiguation("soul_reaper",			"soul_reaper_unholy",			"DEATHKNIGHT",	"unholy")
	-- Druid
	AddDisambiguation("arcane_torrent",			"arcane_torrent_energy",		"DRUID")
	AddDisambiguation("berserk",				"berserk_bear",					"DRUID",		"guardian")
	AddDisambiguation("berserk",				"berserk_cat",					"DRUID",		"feral")
	AddDisambiguation("blood_fury",				"blood_fury_apsp",				"DRUID")
	AddDisambiguation("dream_of_cenarius",		"dream_of_cenarius_caster",		"DRUID",		"balance")
	AddDisambiguation("dream_of_cenarius",		"dream_of_cenarius_melee",		"DRUID",		"feral")
	AddDisambiguation("dream_of_cenarius",		"dream_of_cenarius_tank",		"DRUID",		"guardian")
	AddDisambiguation("force_of_nature",		"force_of_nature_caster",		"DRUID",		"balance")
	AddDisambiguation("force_of_nature",		"force_of_nature_melee",		"DRUID",		"feral")
	AddDisambiguation("force_of_nature",		"force_of_nature_tank",			"DRUID",		"guardian")
	AddDisambiguation("heart_of_the_wild",		"heart_of_the_wild_tank",		"DRUID",		"guardian")
	AddDisambiguation("incarnation",			"incarnation_caster",			"DRUID",		"balance")
	AddDisambiguation("incarnation",			"incarnation_melee",			"DRUID",		"feral")
	AddDisambiguation("incarnation",			"incarnation_tank",				"DRUID",		"guardian")
	AddDisambiguation("moonfire",				"moonfire_cat",					"DRUID",		"feral")
	AddDisambiguation("omen_of_clarity",		"omen_of_clarity_melee",		"DRUID",		"feral")
	AddDisambiguation("rejuvenation_debuff",	"rejuvenation_buff",			"DRUID")
	-- Hunter
	AddDisambiguation("arcane_torrent",			"arcane_torrent_focus",			"HUNTER")
	AddDisambiguation("blood_fury",				"blood_fury_ap",				"HUNTER")
	AddDisambiguation("focusing_shot",			"focusing_shot_marksmanship",	"HUNTER",		"marksmanship")
	-- Mage
	AddDisambiguation("arcane_torrent",			"arcane_torrent_mana",			"MAGE")
	AddDisambiguation("arcane_charge_buff",		"arcane_charge_debuff",			"MAGE",			"arcane")
	AddDisambiguation("blood_fury",				"blood_fury_sp",				"MAGE")
	AddDisambiguation("water_jet",				"pet_water_jet",				"MAGE",			"frost")
	-- Monk
	AddDisambiguation("arcane_torrent",			"arcane_torrent_chi",			"MONK")
	AddDisambiguation("blood_fury",				"blood_fury_apsp",				"MONK")
	AddDisambiguation("chi_explosion",			"chi_explosion_heal",			"MONK",			"mistweaver")
	AddDisambiguation("chi_explosion",			"chi_explosion_melee",			"MONK",			"windwalker")
	AddDisambiguation("chi_explosion",			"chi_explosion_tank",			"MONK",			"brewmaster")
	AddDisambiguation("zen_sphere_debuff",		"zen_sphere_buff",				"MONK")
	-- Paladin
	AddDisambiguation("arcane_torrent",			"arcane_torrent_holy",			"PALADIN")
	AddDisambiguation("avenging_wrath",			"avenging_wrath_heal",			"PALADIN",		"holy")
	AddDisambiguation("avenging_wrath",			"avenging_wrath_melee",			"PALADIN",		"retribution")
	AddDisambiguation("blood_fury",				"blood_fury_apsp",				"PALADIN")
	AddDisambiguation("sacred_shield_debuff",	"sacred_shield_buff",			"PALADIN")
	-- Priest
	AddDisambiguation("arcane_torrent",			"arcane_torrent_mana",			"PRIEST")
	AddDisambiguation("blood_fury",				"blood_fury_sp",				"PRIEST")
	AddDisambiguation("cascade",				"cascade_caster",				"PRIEST",		"shadow")
	AddDisambiguation("cascade",				"cascade_heal",					"PRIEST",		"discipline")
	AddDisambiguation("cascade",				"cascade_heal",					"PRIEST",		"holy")
	AddDisambiguation("devouring_plague_tick",	"devouring_plague",				"PRIEST")
	AddDisambiguation("divine_star",			"divine_star_caster",			"PRIEST",		"shadow")
	AddDisambiguation("divine_star",			"divine_star_heal",				"PRIEST",		"discipline")
	AddDisambiguation("divine_star",			"divine_star_heal",				"PRIEST",		"holy")
	AddDisambiguation("halo",					"halo_caster",					"PRIEST",		"shadow")
	AddDisambiguation("halo",					"halo_heal",					"PRIEST",		"discipline")
	AddDisambiguation("halo",					"halo_heal",					"PRIEST",		"holy")
	AddDisambiguation("renew_debuff",			"renew_buff",					"PRIEST")
	-- Rogue
	AddDisambiguation("arcane_torrent",			"arcane_torrent_energy",		"ROGUE")
	AddDisambiguation("blood_fury",				"blood_fury_ap",				"ROGUE")
	AddDisambiguation("stealth_buff",			"stealthed_buff",				"ROGUE")
	-- Shaman
	AddDisambiguation("arcane_torrent",			"arcane_torrent_mana",			"SHAMAN")
	AddDisambiguation("ascendance",				"ascendance_caster",			"SHAMAN",		"elemental")
	AddDisambiguation("ascendance",				"ascendance_heal",				"SHAMAN",		"restoration")
	AddDisambiguation("ascendance",				"ascendance_melee",				"SHAMAN",		"enhancement")
	AddDisambiguation("blood_fury",				"blood_fury_apsp",				"SHAMAN")
	-- Warlock
	AddDisambiguation("arcane_torrent",			"arcane_torrent_mana",			"WARLOCK")
	AddDisambiguation("blood_fury",				"blood_fury_sp",				"WARLOCK")
	AddDisambiguation("dark_soul",				"dark_soul_instability",		"WARLOCK",		"destruction")
	AddDisambiguation("dark_soul",				"dark_soul_knowledge",			"WARLOCK",		"demonology")
	AddDisambiguation("dark_soul",				"dark_soul_misery",				"WARLOCK",		"affliction")
	AddDisambiguation("glyph_of_dark_soul_instability",	"glyph_of_dark_soul",	"WARLOCK",		"destruction")
	AddDisambiguation("glyph_of_dark_soul_knowledge",	"glyph_of_dark_soul",	"WARLOCK",		"demonology")
	AddDisambiguation("glyph_of_dark_soul_misery",		"glyph_of_dark_soul",	"WARLOCK",		"affliction")
	-- Warrior
	AddDisambiguation("arcane_torrent",			"arcane_torrent_rage",			"WARRIOR")
	AddDisambiguation("blood_fury",				"blood_fury_ap",				"WARRIOR")
	AddDisambiguation("execute",				"execute_arms",					"WARRIOR",		"arms")
	AddDisambiguation("shield_barrier",			"shield_barrier_melee",			"WARRIOR",		"arms")
	AddDisambiguation("shield_barrier",			"shield_barrier_melee",			"WARRIOR",		"fury")
	AddDisambiguation("shield_barrier",			"shield_barrier_tank",			"WARRIOR",		"protection")
end

local function IsTotem(name)
	if strsub(name, 1, 13) == "wild_mushroom" then
		-- Druids.
		return true
	elseif name == "prismatic_crystal" or name == "rune_of_power" then
		-- Mages.
		return true
	elseif strsub(name, -7, -1) == "_statue" then
		-- Monks.
		return true
	elseif strsub(name, -6, -1) == "_totem" then
		-- Shamans.
		return true
	end
	return false
end

--[[--------------------------
	Split-by-tag functions
--]]--------------------------

local function ConcatenatedConditionNode(conditionList, nodeList, annotation)
	local conditionNode
	if #conditionList == 1 then
		conditionNode = conditionList[1]
	elseif #conditionList > 1 then
		local lhsNode = conditionList[1]
		local rhsNode = conditionList[2]
		conditionNode = OvaleAST:NewNode(nodeList, true)
		conditionNode.type = "logical"
		conditionNode.expressionType = "binary"
		conditionNode.operator = "or"
		conditionNode.child[1] = lhsNode
		conditionNode.child[2] = rhsNode
		for k = 3, #conditionList do
			lhsNode = conditionNode
			rhsNode = conditionList[k]
			conditionNode = OvaleAST:NewNode(nodeList, true)
			conditionNode.type = "logical"
			conditionNode.expressionType = "binary"
			conditionNode.operator = "or"
			conditionNode.child[1] = lhsNode
			conditionNode.child[2] = rhsNode
		end
	end
	return conditionNode
end

local function ConcatenatedBodyNode(bodyList, nodeList, annotation)
	local bodyNode
	if #bodyList > 0 then
		bodyNode = OvaleAST:NewNode(nodeList, true)
		bodyNode.type = "group"
		for k, node in ipairs(bodyList) do
			bodyNode.child[k] = node
		end
	end
	return bodyNode
end

local function OvaleTaggedFunctionName(name, tag)
	local prefix, suffix = strmatch(name, "([A-Za-z]+)(Actions)")
	if prefix and suffix then
		local camelTag
		if tag == "shortcd" then
			camelTag = "ShortCd"
		else
			camelTag = CamelCase(tag)
		end
		name = prefix .. camelTag .. suffix
	end
	return name
end

local function TagPriority(tag)
	return OVALE_TAG_PRIORITY[tag] or 10
end

local SPLIT_BY_TAG_VISITOR = nil
-- Forward declarations of split-by-tag functions.
local SplitByTag = nil
local SplitByTagAction = nil
local SplitByTagAddFunction = nil
local SplitByTagCustomFunction = nil
local SplitByTagGroup = nil
local SplitByTagIf = nil
local SplitByTagState = nil

SplitByTag = function(tag, node, nodeList, annotation)
	local visitor = SPLIT_BY_TAG_VISITOR[node.type]
	if not visitor then
		OvaleSimulationCraft:Print("Unable to split-by-tag node of type '%s'.", node.type)
	else
		return visitor(tag, node, nodeList, annotation)
	end
end

SplitByTagAction = function(tag, node, nodeList, annotation)
	local bodyNode, conditionNode
	if node.func == "spell" then
		local spellIdNode = node.rawParams[1]
		local spellId, spellName
		if spellIdNode.type == "variable" then
			spellName = spellIdNode.name
			spellId = annotation.dictionary and annotation.dictionary[spellName]
		elseif spellIdNode.type == "value" then
			spellName = spellIdNode.value
			spellId = spellName
		end
		if spellId then
			local spellTag, invokesGCD = OvaleData:GetSpellTagInfo(spellId)
			if spellTag then
				if spellTag == tag then
					bodyNode = node
				elseif invokesGCD and TagPriority(spellTag) < TagPriority(tag) then
					conditionNode = node
				end
			else
				OvaleSimulationCraft:Print("Unable to determine tag for '%s'.", spellName)
				bodyNode = node
			end
		else
			OvaleSimulationCraft:Print("Unable to determine spell ID and tag for '%s'.", node.asString)
			bodyNode = node
		end
	end
	return bodyNode, conditionNode
end

SplitByTagAddFunction = function(tag, node, nodeList, annotation)
	-- Split the function body by the tag.
	local bodyNode = SplitByTag(tag, node.child[1], nodeList, annotation)
	if not bodyNode or bodyNode.type ~= "group" then
		local newGroupNode = OvaleAST:NewNode(nodeList, true)
		newGroupNode.type = "group"
		newGroupNode.child[1] = bodyNode
		bodyNode = newGroupNode
	end

	local addFunctionNode = OvaleAST:NewNode(nodeList, true)
	addFunctionNode.type = "add_function"
	addFunctionNode.name = OvaleTaggedFunctionName(node.name, tag)
	addFunctionNode.child[1] = bodyNode
	return addFunctionNode
end

SplitByTagCustomFunction = function(tag, node, nodeList, annotation)
	local bodyNode
	local functionName = node.name
	if annotation.taggedFunctionName[functionName] then
		local functionNode = OvaleAST:NewNode(nodeList)
		functionNode.name = OvaleTaggedFunctionName(functionName, tag)
		functionNode.lowername = strlower(functionNode.name)
		functionNode.type = "custom_function"
		functionNode.func = functionNode.name
		functionNode.asString = functionNode.name .. "()"
		bodyNode = functionNode
	else
		local functionTag = annotation.functionTag[functionName]
		if not functionTag then
			if strfind(functionName, "Bloodlust") then
				functionTag = "cd"
			elseif strfind(functionName, "GetInMeleeRange") then
				functionTag = "shortcd"
			elseif strfind(functionName, "InterruptActions") then
				functionTag = "cd"
			elseif strfind(functionName, "RighteousFury") then
				functionTag = "shortcd"
			elseif strfind(functionName, "SummonPet") then
				functionTag = "shortcd"
			elseif strfind(functionName, "UseItemActions") then
				functionTag = "cd"
			elseif strfind(functionName, "UsePotion") then
				functionTag = "cd"
			end
		end
		if functionTag then
			if functionTag == tag then
				bodyNode = node
			end
		else
			OvaleSimulationCraft:Print("Unable to determine tag for '%s()'.", node.name)
			bodyNode = node
		end
	end
	return bodyNode
end

SplitByTagGroup = function(tag, node, nodeList, annotation)
	local index = #node.child
	local bodyList = {}
	local conditionList = {}
	while index > 0 do
		local childNode = node.child[index]
		index = index - 1
		if childNode.type ~= "comment" then
			local bodyNode, conditionNode = SplitByTag(tag, childNode, nodeList, annotation)
			if bodyNode then
				if #conditionList == 0 then
					tinsert(bodyList, 1, bodyNode)
				elseif #bodyList == 0 then
					wipe(conditionList)
					tinsert(bodyList, 1, bodyNode)
				else -- if #conditionList > 0 and #bodyList > 0 then
					-- New body with pre-existing condition, so convert to an "unless" node.
					local unlessNode = OvaleAST:NewNode(nodeList, true)
					unlessNode.type = "unless"
					unlessNode.child[1] = ConcatenatedConditionNode(conditionList, nodeList, annotation)
					unlessNode.child[2] = ConcatenatedBodyNode(bodyList, nodeList, annotation)
					wipe(bodyList)
					wipe(conditionList)
					tinsert(bodyList, 1, unlessNode)
					-- Add a blank line above this "unless" node.
					local commentNode = OvaleAST:NewNode(nodeList)
					commentNode.type = "comment"
					tinsert(bodyList, 1, commentNode)
					-- Insert the new body.
					tinsert(bodyList, 1, bodyNode)
				end
				-- Peek at the previous statement to check if this is part of a "pool_resource" statement pair.
				if index > 0 then
					childNode = node.child[index]
					if childNode.type ~= "comment" then
						bodyNode, conditionNode = SplitByTag(tag, childNode, nodeList, annotation)
						if not bodyNode and index > 1 then
							-- The previous statement is not part of this tag, so check the comments above it for "pool_resource".
							local start = index - 1
							for k = index - 1, 1, -1 do
								childNode = node.child[k]
								if childNode.type == "comment" then
									if childNode.comment and strsub(childNode.comment, 1, 5) == "pool_" then
										-- Found the starting "pool_resource" comment.
										start = k
										break
									end
								else
									break
								end
							end
							if start < index - 1 then
								--[[
									This was part of a "pool_resource" statement pair where the previous statement
									is not part of this tag, so insert the comment block here as well for documentation,
									and "advance" the index to skip the previous statement altogether.
								--]]
								for k = index - 1, start, -1 do
									tinsert(bodyList, 1, node.child[k])
								end
								index = start - 1
							end
						end
					end
				end
				-- Insert the comment block from above the new body.
				while index > 0 do
					childNode = node.child[index]
					if childNode.type == "comment" then
						tinsert(bodyList, 1, childNode)
						index = index - 1
					else
						break
					end
				end
			elseif conditionNode then
				tinsert(conditionList, 1, conditionNode)
			end
		end
	end

	local bodyNode = ConcatenatedBodyNode(bodyList, nodeList, annotation)
	local conditionNode = ConcatenatedConditionNode(conditionList, nodeList, annotation)
	if bodyNode and conditionNode then
		-- Combine conditions and body into an "unless" node.
		local unlessNode = OvaleAST:NewNode(nodeList, true)
		unlessNode.type = "unless"
		unlessNode.child[1] = conditionNode
		unlessNode.child[2] = bodyNode
		-- Create "group" node around the "unless" node.
		local groupNode = OvaleAST:NewNode(nodeList, true)
		groupNode.type = "group"
		groupNode.child[1] = unlessNode
		-- Set return values.
		bodyNode = groupNode
		conditionNode = nil
	end
	return bodyNode, conditionNode
end

SplitByTagIf = function(tag, node, nodeList, annotation)
	local bodyNode, conditionNode = SplitByTag(tag, node.child[2], nodeList, annotation)
	if bodyNode then
		-- Combine pre-existing conditions and body into an "if/unless" node.
		local ifNode = OvaleAST:NewNode(nodeList, true)
		ifNode.type = node.type
		ifNode.child[1] = node.child[1]
		ifNode.child[2] = bodyNode
		-- Set return values.
		bodyNode = ifNode
		conditionNode = nil
	elseif conditionNode then
		-- Combine pre-existing conditions and new conditions into an "and" node.
		local andNode = OvaleAST:NewNode(nodeList, true)
		andNode.type = "logical"
		andNode.expressionType = "binary"
		andNode.operator = "and"
		if node.type == "if" then
			andNode.child[1] = node.child[1]
		else -- if node.type == "unless" then
			-- Flip the boolean condition if the original node was an "unless" node.
			local notNode = OvaleAST:NewNode(nodeList, true)
			notNode.type = "logical"
			notNode.expressionType = "unary"
			notNode.operator = "not"
			notNode.child[1] = node.child[1]
			andNode.child[1] = notNode
		end
		andNode.child[2] = conditionNode
		-- Set return values.
		bodyNode = nil
		conditionNode = andNode
	end
	return bodyNode, conditionNode
end

SplitByTagState = function(tag, node, nodeList, annotation)
	return node
end

do
	SPLIT_BY_TAG_VISITOR = {
		["action"] = SplitByTagAction,
		["add_function"] = SplitByTagAddFunction,
		["custom_function"] = SplitByTagCustomFunction,
		["group"] = SplitByTagGroup,
		["if"] = SplitByTagIf,
		["state"] = SplitByTagState,
		["unless"] = SplitByTagIf,
	}
end

local EMIT_VISITOR = nil
-- Forward declarations of code generation functions.
local Emit = nil
local EmitAction = nil
local EmitActionList = nil
local EmitExpression = nil
local EmitFunction = nil
local EmitModifier = nil
local EmitNumber = nil
local EmitOperand = nil
local EmitOperandAction = nil
local EmitOperandActiveDot = nil
local EmitOperandBuff = nil
local EmitOperandCharacter = nil
local EmitOperandCooldown = nil
local EmitOperandDisease = nil
local EmitOperandDot = nil
local EmitOperandGlyph = nil
local EmitOperandPet = nil
local EmitOperandPreviousSpell = nil
local EmitOperandRaidEvent = nil
local EmitOperandRune = nil
local EmitOperandSeal = nil
local EmitOperandSetBonus = nil
local EmitOperandSpecial = nil
local EmitOperandTalent = nil
local EmitOperandTotem = nil
local EmitOperandTrinket = nil

Emit = function(parseNode, nodeList, annotation, action)
	local visitor = EMIT_VISITOR[parseNode.type]
	if not visitor then
		OvaleSimulationCraft:Print("Unable to emit node of type '%s'.", parseNode.type)
	else
		return visitor(parseNode, nodeList, annotation, action)
	end
end

EmitAction = function(parseNode, nodeList, annotation)
	local node
	local canonicalizedName = gsub(parseNode.name, ":", "_")
	local class = annotation.class
	local specialization = annotation.specialization
	local action = Disambiguate(canonicalizedName, class, specialization)

	if action == "auto_attack" or action == "auto_shot" then
		-- skip
	elseif action == "choose_target" then
		-- skip
	elseif action == "elixir" or action == "flask" or action == "food" then
		-- skip
	elseif action == "snapshot_stats" then
		-- skip
	else
		local bodyNode, conditionNode
		local bodyCode, conditionCode
		local expressionType = "expression"
		local modifier = parseNode.child
		local isSpellAction = true
		if class == "DEATHKNIGHT" and action == "antimagic_shell" then
			-- Only suggest Anti-Magic Shell if there is incoming damage to absorb to generate runic power.
			conditionCode = "IncomingDamage(1.5) > 0"
		elseif class == "DEATHKNIGHT" and action == "blood_tap" then
			-- Blood Tap requires a minimum of five stacks of Blood Charge to be on the player.
			local buffName = "blood_charge_buff"
			AddSymbol(annotation, buffName)
			conditionCode = format("BuffStacks(%s) >= 5", buffName)
		elseif class == "DEATHKNIGHT" and action == "dark_transformation" then
			-- Dark Transformation requires a five stacks of Shadow Infusion to be on the player/pet.
			local buffName = "shadow_infusion_buff"
			AddSymbol(annotation, buffName)
			conditionCode = format("BuffStacks(%s) >= 5", buffName)
		elseif class == "DEATHKNIGHT" and action == "horn_of_winter" then
			-- Only cast Horn of Winter if not already raid-buffed.
			conditionCode = "BuffExpires(attack_power_multiplier_buff any=1)"
		elseif class == "DEATHKNIGHT" and action == "mind_freeze" then
			bodyCode = "InterruptActions()"
			annotation[action] = class
			isSpellAction = false
		elseif class == "DEATHKNIGHT" and action == "plague_leech" then
			-- Plague Leech requires diseases to exist on the target.
			-- Scripts should be checking that there is least one pair of fully-depleted runes,
			-- but they mostly don't, so add the check for all uses of Plague Leech.
			conditionCode = "target.DiseasesTicking() and { Rune(blood) < 1 or Rune(frost) < 1 or Rune(unholy) < 1 }"
		elseif class == "DRUID" and specialization == "guardian" and action == "rejuvenation" then
			-- Only cast Rejuvenation as a guardian druid if it is Enhanced Rejuvenation (castable in bear form).
			local spellName = "enhanced_rejuvenation"
			AddSymbol(annotation, spellName)
			conditionCode = format("SpellKnown(%s)", spellName)
		elseif class == "DRUID" and action == "prowl" then
			-- Don't Prowl if already stealthed.
			conditionCode = "BuffExpires(stealthed_buff any=1)"
		elseif class == "DRUID" and action == "pulverize" then
			-- Pulverize requires 3 stacks of Lacerate on the target.
			local debuffName = "lacerate_debuff"
			AddSymbol(annotation, debuffName)
			conditionCode = format("target.DebuffStacks(%s) >= 3", debuffName)
		elseif class == "DRUID" and action == "skull_bash" then
			bodyCode = "InterruptActions()"
			annotation[action] = class
			isSpellAction = false
		elseif class == "DRUID" and action == "wild_charge" then
			bodyCode = "GetInMeleeRange()"
			annotation[action] = class
			isSpellAction = false
		elseif class == "HUNTER" and action == "exotic_munitions" then
			if modifier.ammo_type then
				local name = Unparse(modifier.ammo_type)
				action = name .. "_ammo"
				-- Always have at least 20 minutes of an Exotic Munitions buff applied when out of combat.
				local buffName = "exotic_munitions_buff"
				AddSymbol(annotation, buffName)
				conditionCode = format("BuffRemaining(%s) < 1200", buffName)
			else
				isSpellAction = false
			end
		elseif class == "HUNTER" and action == "explosive_trap" then
			-- Glyph of Explosive Trap removes the damage component from Explosive Trap.
			local glyphName = "glyph_of_explosive_trap"
			AddSymbol(annotation, glyphName)
			annotation.trap_launcher = class
			conditionCode = format("CheckBoxOn(opt_trap_launcher) and not Glyph(%s)", glyphName)
		elseif class == "HUNTER" and action == "focus_fire" then
			-- Focus Fire requires at least one stack of Frenzy.
			local buffName = "frenzy_buff"
			AddSymbol(annotation, buffName)
			if modifier.five_stacks then
				local value = tonumber(Unparse(modifier.five_stacks))
				if value == 1 then
					conditionCode = format("BuffStacks(%s any=1) == 5", buffName)
				end
			end
			if not conditionCode then
				conditionCode = format("BuffPresent(%s any=1)", buffName)
			end
		elseif class == "HUNTER" and action == "kill_command" then
			-- Kill Command requires that a pet that can move freely.
			conditionCode = "pet.Present() and not pet.IsIncapacitated() and not pet.IsFeared() and not pet.IsStunned()"
		elseif class == "HUNTER" and action == "summon_pet" then
			if specialization == "beast_mastery" then
				bodyCode = "BeastMasterySummonPet()"
			else
				bodyCode = "SummonPet()"
			end
			annotation[action] = class
			isSpellAction = false
		elseif class == "HUNTER" and strsub(action, -5) == "_trap" then
			annotation.trap_launcher = class
			conditionCode = "CheckBoxOn(opt_trap_launcher)"
		elseif class == "MAGE" and action == "arcane_brilliance" then
			-- Only cast Arcane Brilliance if not already raid-buffed.
			conditionCode = "BuffExpires(critical_strike_buff any=1) or BuffExpires(spell_power_multiplier_buff any=1)"
		elseif class == "MAGE" and action == "arcane_missiles" then
			-- Arcane Missiles can only be fired if the Arcane Missiles! buff is present.
			local buffName = "arcane_missiles_buff"
			AddSymbol(annotation, buffName)
			conditionCode = format("BuffPresent(%s)", buffName)
		elseif class == "MAGE" and action == "counterspell" then
			bodyCode = "InterruptActions()"
			annotation[action] = class
			isSpellAction = false
		elseif class == "MAGE" and strfind(action, "pet_") then
			conditionCode = "pet.Present()"
		elseif class == "MAGE" and action == "start_pyro_chain" then
			bodyCode = "SetState(pyro_chain 1)"
			isSpellAction = false
		elseif class == "MAGE" and action == "stop_pyro_chain" then
			bodyCode = "SetState(pyro_chain 0)"
			isSpellAction = false
		elseif class == "MAGE" and action == "time_warp" then
			-- Only suggest Time Warp if it will have an effect.
			conditionCode = "CheckBoxOn(opt_time_warp) and DebuffExpires(burst_haste_debuff any=1)"
			annotation[action] = class
		elseif class == "MAGE" and action == "water_elemental" then
			-- Only suggest summoning the Water Elemental if the pet is not already summoned.
			conditionCode = "not pet.Present()"
		elseif class == "MONK" and action == "chi_burst" then
			-- Only suggest Chi Burst if it's toggled on.
			conditionCode = "CheckBoxOn(opt_chi_burst)"
			annotation[action] = class
		elseif class == "MONK" and action == "chi_sphere" then
			-- skip
			isSpellAction = false
		elseif class == "MONK" and action == "gift_of_the_ox" then
			-- skip
			isSpellAction = false
		elseif class == "MONK" and action == "touch_of_death" then
			-- Touch of Death can only be used if the Death Note buff is present on the player.
			local buffName = "death_note_buff"
			AddSymbol(annotation, buffName)
			conditionCode = format("BuffPresent(%s)", buffName)
		elseif class == "PALADIN" and action == "blessing_of_kings" then
			-- Only cast Blessing of Kings if it won't overwrite the player's own Blessing of Might.
			conditionCode = "BuffExpires(mastery_buff)"
		elseif class == "PALADIN" and action == "rebuke" then
			bodyCode = "InterruptActions()"
			annotation[action] = class
			isSpellAction = false
		elseif class == "PRIEST" and action == "insanity" then
			local buffName = "shadow_word_insanity_buff"
			AddSymbol(annotation, buffName)
			conditionCode = format("BuffPresent(%s)", buffName)
		elseif class == "ROGUE" and action == "apply_poison" then
			if modifier.lethal then
				local name = Unparse(modifier.lethal)
				action = name .. "_poison"
				-- Always have at least 20 minutes of a lethal poison applied when out of combat.
				local buffName = "lethal_poison_buff"
				AddSymbol(annotation, buffName)
				conditionCode = format("BuffRemaining(%s) < 1200", buffName)
			else
				isSpellAction = false
			end
		elseif class == "ROGUE" and action == "blade_flurry" then
			annotation.blade_flurry = class
			conditionCode = "CheckBoxOn(opt_blade_flurry)"
		elseif class == "ROGUE" and action == "honor_among_thieves" then
			if modifier.cooldown then
				local cooldown = Unparse(modifier.cooldown)
				local buffName = action .. "_cooldown_buff"
				annotation[buffName] = cooldown
				annotation[action] = class
			end
			isSpellAction = false
		elseif class == "ROGUE" and action == "kick" then
			bodyCode = "InterruptActions()"
			annotation[action] = class
			isSpellAction = false
		elseif class == "ROGUE" and specialization == "combat" and action == "slice_and_dice" then
			-- Don't suggest Slice and Dice if a more powerful buff is already in effect.
			local buffName = "slice_and_dice_buff"
			AddSymbol(annotation, buffName)
			conditionCode = format("BuffRemaining(%s) < BaseDuration(%s)", buffName, buffName)
		elseif class == "ROGUE" and action == "stealth" then
			-- Don't Stealth if already stealthed.
			conditionCode = "BuffExpires(stealthed_buff any=1)"
		elseif class == "SHAMAN" and strsub(action, 1, 11) == "ascendance_" then
			-- Ascendance doesn't go on cooldown until after the buff expires, so don't
			-- suggest Ascendance if already in Ascendance.
			local buffName = action .. "_buff"
			AddSymbol(annotation, buffName)
			conditionCode = format("BuffExpires(%s)", buffName)
		elseif class == "SHAMAN" and action == "bloodlust" then
			bodyCode = "Bloodlust()"
			annotation[action] = class
			isSpellAction = false
		elseif class == "SHAMAN" and action == "lava_beam" then
			-- Lava Beam is the elemental Ascendance version of Chain Lightning.
			local buffName = "ascendance_caster_buff"
			AddSymbol(annotation, buffName)
			conditionCode = format("BuffPresent(%s)", buffName)
		elseif class == "SHAMAN" and action == "magma_totem" then
			-- Only suggest Magma Totem if within melee range of the target.
			local spellName = "primal_strike"
			AddSymbol(annotation, spellName)
			conditionCode = format("target.InRange(%s)", spellName)
		elseif class == "SHAMAN" and action == "windstrike" then
			-- Windstrike is the enhancement Ascendance version of Stormstrike.
			local buffName = "ascendance_melee_buff"
			AddSymbol(annotation, buffName)
			conditionCode = format("BuffPresent(%s)", buffName)
		elseif class == "SHAMAN" and action == "wind_shear" then
			bodyCode = "InterruptActions()"
			annotation[action] = class
			isSpellAction = false
		elseif class == "WARLOCK" and action == "cancel_metamorphosis" then
			local spellName = "metamorphosis"
			local buffName = "metamorphosis_buff"
			AddSymbol(annotation, spellName)
			AddSymbol(annotation, buffName)
			bodyCode = format("Spell(%s text=cancel)", spellName)
			conditionCode = format("BuffPresent(%s)", buffName)
			isSpellAction = false
		elseif class == "WARLOCK" and action == "felguard_felstorm" then
			conditionCode = "pet.Present() and pet.CreatureFamily(Felguard)"
		elseif class == "WARLOCK" and action == "grimoire_of_sacrifice" then
			-- Grimoire of Sacrifice requires a pet to already be summoned.
			conditionCode = "pet.Present()"
		elseif class == "WARLOCK" and action == "havoc" then
			-- Havoc requires another target.
			conditionCode = "Enemies() > 1"
		elseif class == "WARLOCK" and action == "service_pet" then
			if annotation.pet then
				local spellName = "grimoire_" .. annotation.pet
				AddSymbol(annotation, spellName)
				bodyCode = format("Spell(%s)", spellName)
			else
				bodyCode = "Texture(spell_nature_removecurse help=ServicePet)"
			end
			isSpellAction = false
		elseif class == "WARLOCK" and action == "summon_pet" then
			if annotation.pet then
				local spellName = "summon_" .. annotation.pet
				AddSymbol(annotation, spellName)
				bodyCode = format("Spell(%s)", spellName)
			else
				bodyCode = "Texture(spell_nature_removecurse help=L(summon_pet))"
			end
			-- Only summon a pet if one is not already summoned.
			conditionCode = "not pet.Present()"
			isSpellAction = false
		elseif class == "WARLOCK" and action == "wrathguard_wrathstorm" then
			conditionCode = "pet.Present() and pet.CreatureFamily(Wrathguard)"
		elseif class == "WARRIOR" and action == "charge" then
			conditionCode = "target.InRange(charge)"
		elseif class == "WARRIOR" and action == "enraged_regeneration" then
			-- Only suggest Enraged Regeneration at below 80% health.
			conditionCode = "HealthPercent() < 80"
		elseif class == "WARRIOR" and strsub(action, 1, 7) == "execute" then
			if modifier.target then
				local target = Unparse(modifier.target)
				local target = tonumber(target)
				if target then
					-- Skip "execute" actions if they are not on the main target.
					isSpellAction = false
				end
			end
		elseif class == "WARRIOR" and action == "heroic_leap" then
			-- Use Charge as a range-finder for Heroic Leap.
			local spellName = "charge"
			AddSymbol(annotation, spellName)
			conditionCode = format("target.InRange(%s)", spellName)
		elseif class == "WARRIOR" and action == "victory_rush" then
			-- Victory Rush requires the Victorious buff to be on the player.
			local buffName = "victorious_buff"
			AddSymbol(annotation, buffName)
			conditionCode = format("BuffPresent(%s)", buffName)
		elseif class == "WARRIOR" and action == "raging_blow" then
			-- Raging Blow can only be used if the Raging Blow buff is present on the player.
			local buffName = "raging_blow_buff"
			AddSymbol(annotation, buffName)
			conditionCode = format("BuffPresent(%s)", buffName)
		elseif action == "call_action_list" or action == "run_action_list" or action == "swap_action_list" then
			if modifier.name then
				local name = Unparse(modifier.name)
				bodyCode = OvaleFunctionName(name, annotation) .. "()"
			end
			isSpellAction = false
		elseif action == "cancel_buff" then
			if modifier.name then
				local spellName = Unparse(modifier.name)
				local buffName = spellName .. "_buff"
				AddSymbol(annotation, spellName)
				AddSymbol(annotation, buffName)
				bodyCode = format("Texture(%s text=cancel)", spellName)
				conditionCode = format("BuffPresent(%s)", buffName)
				isSpellAction = false
			end
		elseif action == "mana_potion" then
			bodyCode = "UsePotionMana()"
			annotation.use_potion_mana = class
			isSpellAction = false
		elseif action == "pool_resource" then
			-- Create a special "simc_pool_resource" AST node that will be transformed in
			-- a later step into something OvaleAST can understand and unparse.
			bodyNode = OvaleAST:NewNode(nodeList)
			bodyNode.type = "simc_pool_resource"
			bodyNode.for_next = (modifier.for_next ~= nil)
			if modifier.extra_amount then
				bodyNode.extra_amount = tonumber(Unparse(modifier.extra_amount))
			end
			isSpellAction = false
		elseif action == "potion" then
			if modifier.name then
				local name = Unparse(modifier.name)
				local stat = POTION_STAT[name]
				if stat == "agility" then
					bodyCode = "UsePotionAgility()"
					annotation.use_potion_agility = class
				elseif stat == "armor" then
					bodyCode = "UsePotionArmor()"
					annotation.use_potion_armor = class
				elseif stat == "intellect" then
					bodyCode = "UsePotionIntellect()"
					annotation.use_potion_intellect = class
				elseif stat == "strength" then
					bodyCode = "UsePotionStrength()"
					annotation.use_potion_strength = class
				end
				isSpellAction = false
			end
		elseif action == "stance" then
			if modifier.choose then
				local name = Unparse(modifier.choose)
				if class == "MONK" then
					action = "stance_of_the_" .. name
				elseif class == "WARRIOR" then
					action = name .. "_stance"
				else
					action = name
				end
			else
				isSpellAction = false
			end
		elseif action == "summon_pet" then
			bodyCode = "SummonPet()"
			annotation[action] = class
			isSpellAction = false
		elseif action == "use_item" then
			if true then
				--[[
					When "use_item" is encountered in an action list, it is usually meant to use
					all of the equipped items at the same time, so all hand tinkers and on-use
					trinkets.  Assume a "UseItemActions()" function is available that does this.
				--]]
				bodyCode = "UseItemActions()"
				annotation[action] = true
			else
				if modifier.name == "name" then
					local name = Unparse(modifier.name)
					if strmatch(name, "gauntlets") or strmatch(name, "gloves") or strmatch(name, "grips") or strmatch(name, "handguards") then
						bodyCode = "Item(HandsSlot usable=1)"
					end
				elseif modifier.slot then
					local slot = Unparse(modifier.slot)
					if slot == "hands" then
						bodyCode = "Item(HandsSlot usable=1)"
					elseif strmatch(slot, "trinket") then
						bodyCode = "{ Item(Trinket0Slot usable=1) Item(Trinket1Slot usable=1) }"
						expressionType = "group"
					end
				end
			end
			isSpellAction = false
		elseif action == "wait" then
			--[[
				Create a special "wait" AST node that will be transformed in
				a later step into something OvaleAST can understand and unparse.
			--]]
			bodyNode = OvaleAST:NewNode(nodeList)
			bodyNode.type = "simc_wait"
			if modifier.sec then
				-- "wait,sec=expr" means to halt the processing of the action list if "expr > 0".
				local expressionNode = Emit(modifier.sec, nodeList, annotation, action)
				local code = OvaleAST:Unparse(expressionNode)
				conditionCode = code .. " > 0"
			end
			isSpellAction = false
		end
		if isSpellAction then
			AddSymbol(annotation, action)
			if modifier.target then
				local actionTarget = Unparse(modifier.target)
				if actionTarget == "2" then
					actionTarget = "other"
				end
				if actionTarget ~= "1" then
					bodyCode = format("Spell(%s text=%s)", action, actionTarget)
				end
			end
			bodyCode = bodyCode or "Spell(" .. action .. ")"
		end
		annotation.astAnnotation = annotation.astAnnotation or {}
		if not bodyNode and bodyCode then
			bodyNode = OvaleAST:ParseCode(expressionType, bodyCode, nodeList, annotation.astAnnotation)
		end
		if not conditionNode and conditionCode then
			conditionNode = OvaleAST:ParseCode(expressionType, conditionCode, nodeList, annotation.astAnnotation)
		end

		-- Conditions from modifiers, if present.
		if bodyNode then
			-- Put the extra conditions on the right-most side.
			local extraConditionNode = conditionNode
			conditionNode = nil
			-- Concatenate all of the conditions from modifiers using the "and" operator.
			for modifier, expressionNode in pairs(parseNode.child) do
				local rhsNode = EmitModifier(modifier, expressionNode, nodeList, annotation, action)
				if rhsNode then
					if not conditionNode then
						conditionNode = rhsNode
					else
						local lhsNode = conditionNode
						conditionNode = OvaleAST:NewNode(nodeList, true)
						conditionNode.type = "logical"
						conditionNode.expressionType = "binary"
						conditionNode.operator = "and"
						conditionNode.child[1] = lhsNode
						conditionNode.child[2] = rhsNode
					end
				end
			end
			if extraConditionNode then
				if conditionNode then
					local lhsNode = conditionNode
					local rhsNode = extraConditionNode
					conditionNode = OvaleAST:NewNode(nodeList, true)
					conditionNode.type = "logical"
					conditionNode.expressionType = "binary"
					conditionNode.operator = "and"
					conditionNode.child[1] = lhsNode
					conditionNode.child[2] = rhsNode
				else
					conditionNode = extraConditionNode
				end
			end

			-- Create "if" node.
			if conditionNode then
				node = OvaleAST:NewNode(nodeList, true)
				node.type = "if"
				node.child[1] = conditionNode
				node.child[2] = bodyNode
				if bodyNode.type == "simc_pool_resource" then
					node.simc_pool_resource = true
				elseif bodyNode.type == "simc_wait" then
					node.simc_wait = true
				end
			else
				node = bodyNode
			end
		end
	end

	return node
end

EmitActionList = function(parseNode, nodeList, annotation)
	-- Function body is a group of statements.
	local groupNode = OvaleAST:NewNode(nodeList, true)
	groupNode.type = "group"
	local child = groupNode.child
	local poolResourceNode
	local emit = true
	for _, actionNode in ipairs(parseNode.child) do
		-- Add a comment containing the action to be translated.
		local commentNode = OvaleAST:NewNode(nodeList)
		commentNode.type = "comment"
		commentNode.comment = actionNode.action
		child[#child + 1] = commentNode
		if emit then
			-- Add the translated statement.
			local statementNode = EmitAction(actionNode, nodeList, annotation)
			if statementNode then
				if statementNode.type == "simc_pool_resource" then
					local powerType = OvalePower.POOLED_RESOURCE[annotation.class]
					if powerType then
						if statementNode.for_next then
							poolResourceNode = statementNode
							poolResourceNode.powerType = powerType
						else
							-- This is a bare "pool_resource" statement, which means pool
							-- continually and skip the rest of the action list.
							emit = false
						end
					end
				elseif poolResourceNode then
					-- This is the action following "pool_resource,for_next=1".
					child[#child + 1] = statementNode
					local bodyNode
					local poolingConditionNode
					if statementNode.child then
						-- This is a conditional statement, so set the body to the "then" clause.
						poolingConditionNode = statementNode.child[1]
						bodyNode = statementNode.child[2]
					else
						bodyNode = statementNode
					end
					local powerType = CamelCase(poolResourceNode.powerType)
					local extra_amount = poolResourceNode.extra_amount
					if extra_amount and poolingConditionNode then
						-- Remove any 'Energy() >= N' conditions from the pooling condition.
						local code = OvaleAST:Unparse(poolingConditionNode)
						local extraAmountPattern = powerType .. "%(%) >= [%d.]+"
						local replaceString = format("True(pool_%s %d)", poolResourceNode.powerType, extra_amount)
						code = gsub(code, extraAmountPattern, replaceString)
						poolingConditionNode = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
					end
					if bodyNode.type == "action" and bodyNode.rawParams and bodyNode.rawParams[1] then
						local name = OvaleAST:Unparse(bodyNode.rawParams[1])
						-- Create a condition node that includes checking that the spell is not on cooldown.
						local powerCondition
						if extra_amount then
							powerCondition = format("TimeTo%s(%d)", powerType, extra_amount)
						else
							powerCondition = format("TimeTo%sFor(%s)", powerType, name)
						end
						local code = format("SpellUsable(%s) and SpellCooldown(%s) < %s", name, name, powerCondition)
						local conditionNode = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
						if statementNode.child then
							local rhsNode = conditionNode
							conditionNode = OvaleAST:NewNode(nodeList, true)
							conditionNode.type = "logical"
							conditionNode.expressionType = "binary"
							conditionNode.operator = "and"
							conditionNode.child[1] = poolingConditionNode
							conditionNode.child[2] = rhsNode
						end
						-- Create node to hold the rest of the statements.
						local restNode = OvaleAST:NewNode(nodeList, true)
						child[#child + 1] = restNode
						if statementNode.type == "unless" then
							restNode.type = "if"
						else
							restNode.type = "unless"
						end
						restNode.child[1] = conditionNode
						restNode.child[2] = OvaleAST:NewNode(nodeList, true)
						restNode.child[2].type = "group"
						child = restNode.child[2].child
					end
					poolResourceNode = nil
				elseif statementNode.type == "simc_wait" then
					-- This is a bare "wait" statement, which we don't know how to process, so
					-- skip it.
				elseif statementNode.simc_wait then
					-- Create an "unless" node with the remaining statements as the body.
					local restNode = OvaleAST:NewNode(nodeList, true)
					child[#child + 1] = restNode
					restNode.type = "unless"
					restNode.child[1] = statementNode.child[1]
					restNode.child[2] = OvaleAST:NewNode(nodeList, true)
					restNode.child[2].type = "group"
					child = restNode.child[2].child
				else
					child[#child + 1] = statementNode
					if statementNode.simc_pool_resource then
						-- Flip the "if/unless" statement and change the body into a group node
						-- containing all of the rest of the statements.
						if statementNode.type == "if" then
							statementNode.type = "unless"
						elseif statementNode.type == "unless" then
							statementNode.type = "if"
						end
						statementNode.child[2] = OvaleAST:NewNode(nodeList, true)
						statementNode.child[2].type = "group"
						child = statementNode.child[2].child
					end
				end
			end
		end
	end

	local node = OvaleAST:NewNode(nodeList, true)
	node.type = "add_function"
	node.name = OvaleFunctionName(parseNode.name, annotation)
	node.child[1] = groupNode
	return node
end

EmitExpression = function(parseNode, nodeList, annotation, action)
	local node
	local msg
	if parseNode.expressionType == "unary" then
		local opInfo = UNARY_OPERATOR[parseNode.operator]
		if opInfo then
			local operator
			if parseNode.operator == "!" then
				operator = "not"
			elseif parseNode.operator == "-" then
				operator = parseNode.operator
			end
			if operator then
				local rhsNode = Emit(parseNode.child[1], nodeList, annotation, action)
				if rhsNode then
					if operator == "-" and rhsNode.type == "value" then
						rhsNode.value = -1 * rhsNode.value
					else
						node = OvaleAST:NewNode(nodeList, true)
						node.type = opInfo[1]
						node.expressionType = "unary"
						node.operator = operator
						node.precedence = opInfo[2]
						node.child[1] = rhsNode
					end
				end
			end
		end
	elseif parseNode.expressionType == "binary" then
		local opInfo = BINARY_OPERATOR[parseNode.operator]
		if opInfo then
			local operator
			if parseNode.operator == "&" then
				operator = "and"
			elseif parseNode.operator == "^" then
				operator = "xor"
			elseif parseNode.operator == "|" then
				operator = "or"
			elseif parseNode.operator == "=" then
				operator = "=="
			elseif parseNode.operator == "%" then
				operator = "/"
			elseif parseNode.type == "compare" or parseNode.type == "arithmetic" then
				operator = parseNode.operator
			end
			if parseNode.type == "arithmetic" and parseNode.child[1].rune and parseNode.child[1].includeDeath and parseNode.child[2].name == "death" then
				--[[
					Special handling for "Blood-death", "Frost-death", and "Unholy-death" arithmetic expressions.
					These are trying to express the concept of "non-death runes of the given type".
				--]]
				local code = "Rune(" .. parseNode.child[1].rune .. " death=0)"
				annotation.astAnnotation = annotation.astAnnotation or {}
				node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
			elseif parseNode.type == "compare" and parseNode.child[1].rune then
				--[[
					Special handling for rune comparisons.
					This ONLY handles rune expressions of the form "<rune><operator><number>".
					These are translated to equivalent "Rune(<rune>) <operator> <number>" expressions,
					but with some munging of the numbers since Rune() returns a fractional number of runes.
				--]]
				local lhsNode = parseNode.child[1]
				local rhsNode = parseNode.child[2]
				local runeType = lhsNode.rune
				local number = (rhsNode.type == "number") and tonumber(Unparse(rhsNode)) or nil
				if rhsNode.type == "number" then
					number = tonumber(Unparse(rhsNode))
				end
				if runeType and number then
					local code
					local op = parseNode.operator
					local runeCondition
					if lhsNode.includeDeath then
						runeCondition = "Rune(" .. runeType .. " death=1)"
					else
						runeCondition = "Rune(" .. runeType .. ")"
					end
					if op == ">" then
						code = format("%s >= %d", runeCondition, number + 1)
					elseif op == ">=" then
						code = format("%s >= %d", runeCondition, number)
					elseif op == "=" then
						if runeType ~= "death" and number == 2 then
							-- We can never have more than 2 non-death runes of the same type.
							code = format("%s >= %d", runeCondition, number)
						else
							code = format("%s >= %d and %s < %d", runeCondition, number, runeCondition, number + 1)
						end
					elseif op == "<=" then
						code = format("%s < %d", runeCondition, number + 1)
					elseif op == "<" then
						code = format("%s < %d", runeCondition, number)
					end
					if not node and code then
						annotation.astAnnotation = annotation.astAnnotation or {}
						node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
					end
				end
			elseif (parseNode.operator == "=" or parseNode.operator == "!=") and (parseNode.child[1].name == "target" or parseNode.child[1].name == "current_target") then
				--[[
					Special handling for "target=X" or "current_target=X" expressions.
				--]]
				local rhsNode = parseNode.child[2]
				local name = rhsNode.name
				local code
				if parseNode.operator == "=" then
					code = format("target.Name(%s)", name)
				else -- if parseNode.operator == "!=" then
					code = format("not target.Name(%s)", name)
				end
				AddSymbol(annotation, name)
				annotation.astAnnotation = annotation.astAnnotation or {}
				node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
			elseif (parseNode.operator == "=" or parseNode.operator == "!=") and parseNode.child[1].name == "last_judgment_target" then
				--[[
					Special handling for "last_judgment_target=X" expressions.
					TODO: Track the target of the previous cast of a spell.
				--]]
				local code
				if parseNode.operator == "=" then
					code = "False(last_judgement_target)"
				else -- if parseNode.operator == "!=" then
					code = "True(last_judgement_target)"
				end
				annotation.astAnnotation = annotation.astAnnotation or {}
				node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
			elseif operator then
				local lhsNode = Emit(parseNode.child[1], nodeList, annotation, action)
				local rhsNode = Emit(parseNode.child[2], nodeList, annotation, action)
				if lhsNode and rhsNode then
					node = OvaleAST:NewNode(nodeList, true)
					node.type = opInfo[1]
					node.expressionType = "binary"
					node.operator = operator
					node.precedence = opInfo[2]
					node.child[1] = lhsNode
					node.child[2] = rhsNode
				elseif lhsNode then
					msg = Ovale:MakeString("Warning: %s operator '%s' right failed.", parseNode.type, parseNode.operator)
				elseif rhsNode then
					msg = Ovale:MakeString("Warning: %s operator '%s' left failed.", parseNode.type, parseNode.operator)
				else
					msg = Ovale:MakeString("Warning: %s operator '%s' left and right failed.", parseNode.type, parseNode.operator)
				end
			end
		end
	end
	if node then
		if parseNode.left and parseNode.right then
			node.left = "{"
			node.right = "}"
		end
	else
		msg = msg or Ovale:MakeString("Warning: Operator '%s' is not implemented.", parseNode.operator)
		OvaleSimulationCraft:Print(msg)
		node = OvaleAST:NewNode(nodeList)
		node.type = "string"
		node.value = "FIXME_" .. parseNode.operator
	end
	return node
end

EmitFunction = function(parseNode, nodeList, annotation, action)
	local node
	if parseNode.name == "ceil" or parseNode.name == "floor" then
		-- Pretend ceil and floor have no effect.
		node = EmitExpression(parseNode.child[1], nodeList, annotation, action)
	else
		OvaleSimulationCraft:Print("Warning: Function '%s' is not implemented.", parseNode.name)
		node = OvaleAST:NewNode(nodeList)
		node.type = "variable"
		node.name = "FIXME_" .. parseNode.name
	end
	return node
end

EmitModifier = function(modifier, parseNode, nodeList, annotation, action)
	local node, code
	local class = annotation.class
	local specialization = annotation.specialization

	if modifier == "if" then
		node = Emit(parseNode, nodeList, annotation, action)
	elseif modifier == "line_cd" then
		if not SPECIAL_ACTION[action] then
			AddSymbol(annotation, action)
			local expressionCode = OvaleAST:Unparse(Emit(parseNode, nodeList, annotation, action))
			code = format("TimeSincePreviousSpell(%s) > %s", action, expressionCode)
		end
	elseif modifier == "max_cycle_targets" then
		local debuffName = action .. "_debuff"
		AddSymbol(annotation, debuffName)
		local expressionCode = OvaleAST:Unparse(Emit(parseNode, nodeList, annotation, action))
		code = format("DebuffCountOnAny(%s) <= Enemies() and DebuffCountOnAny(%s) <= %s", debuffName, debuffName, expressionCode)
	elseif modifier == "max_energy" then
		local value = tonumber(Unparse(parseNode))
		if value == 1 then
			-- SimulationCraft's max_energy is the maximum energy cost of the action if used.
			code = format("Energy() >= EnergyCost(%s max=1)", action)
		end
	elseif modifier == "moving" then
		local value = tonumber(Unparse(parseNode))
		if value == 1 then
			code = "Speed() > 0"
		end
	elseif modifier == "sync" then
		local name = Unparse(parseNode)
		name = Disambiguate(name, class, specialization)
		AddSymbol(annotation, name)
		code = format("not SpellCooldown(%s) > 0", name)
	end
	if not node and code then
		annotation.astAnnotation = annotation.astAnnotation or {}
		node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
	end
	return node
end

EmitNumber = function(parseNode, nodeList, annotation, action)
	local node = OvaleAST:NewNode(nodeList)
	node.type = "value"
	node.value = parseNode.value
	node.origin = 0
	node.rate = 0
	return node
end

EmitOperand = function(parseNode, nodeList, annotation, action)
	local ok = false
	local node

	local operand = parseNode.name
	local token = strmatch(operand, OPERAND_TOKEN_PATTERN)	-- peek
	local target
	if token == "target" then
		target = token
		operand = strsub(operand, strlen(target) + 2)		-- consume
		token = strmatch(operand, OPERAND_TOKEN_PATTERN)	-- peek
	end
	ok, node = EmitOperandRune(operand, parseNode, nodeList, annotation, action)
	if not ok then
		ok, node = EmitOperandSpecial(operand, parseNode, nodeList, annotation, action, target)
	end
	if not ok then
		ok, node = EmitOperandRaidEvent(operand, parseNode, nodeList, annotation, action)
	end
	if not ok then
		ok, node = EmitOperandAction(operand, parseNode, nodeList, annotation, action, target)
	end
	if not ok then
		ok, node = EmitOperandCharacter(operand, parseNode, nodeList, annotation, action, target)
	end
	if not ok then
		if token == "active_dot" then
			target = target or "target"
			ok, node = EmitOperandActiveDot(operand, parseNode, nodeList, annotation, action, target)
		elseif token == "aura" then
			ok, node = EmitOperandBuff(operand, parseNode, nodeList, annotation, action, target)
		elseif token == "buff" then
			ok, node = EmitOperandBuff(operand, parseNode, nodeList, annotation, action, target)
		elseif token == "cooldown" then
			ok, node = EmitOperandCooldown(operand, parseNode, nodeList, annotation, action)
		elseif token == "debuff" then
			target = target or "target"
			ok, node = EmitOperandBuff(operand, parseNode, nodeList, annotation, action, target)
		elseif token == "disease" then
			target = target or "target"
			ok, node = EmitOperandDisease(operand, parseNode, nodeList, annotation, action, target)
		elseif token == "dot" then
			target = target or "target"
			ok, node = EmitOperandDot(operand, parseNode, nodeList, annotation, action, target)
		elseif token == "glyph" then
			ok, node = EmitOperandGlyph(operand, parseNode, nodeList, annotation, action)
		elseif token == "pet" then
			ok, node = EmitOperandPet(operand, parseNode, nodeList, annotation, action)
		elseif token == "prev" or token == "prev_gcd" then
			ok, node = EmitOperandPreviousSpell(operand, parseNode, nodeList, annotation, action)
		elseif token == "seal" then
			ok, node = EmitOperandSeal(operand, parseNode, nodeList, annotation, action)
		elseif token == "set_bonus" then
			ok, node = EmitOperandSetBonus(operand, parseNode, nodeList, annotation, action)
		elseif token == "talent" then
			ok, node = EmitOperandTalent(operand, parseNode, nodeList, annotation, action)
		elseif token == "totem" then
			ok, node = EmitOperandTotem(operand, parseNode, nodeList, annotation, action)
		elseif token == "trinket" then
			ok, node = EmitOperandTrinket(operand, parseNode, nodeList, annotation, action)
		end
	end
	if not ok then
		node = OvaleAST:NewNode(nodeList)
		node.type = "variable"
		node.name = "FIXME_" .. parseNode.name
	end

	return node
end

EmitOperandAction = function(operand, parseNode, nodeList, annotation, action, target)
	local ok = true
	local node

	local name
	local property
	if strsub(operand, 1, 7) == "action." then
		local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
		local token = tokenIterator()
		name = tokenIterator()
		property = tokenIterator()
	else
		name = action
		property = operand
	end

	local class, specialization = annotation.class, annotation.specialization
	name = Disambiguate(name, class, specialization)
	target = target and (target .. ".") or ""
	local buffName = name .. "_debuff"
	buffName = Disambiguate(buffName, class, specialization)
	local prefix = strfind(buffName, "_buff$") and "Buff" or "Debuff"
	local buffTarget = (prefix == "Debuff") and "target." or target
	local talentName = name .. "_talent"
	talentName = Disambiguate(talentName, class, specialization)
	local symbol = name

	local code
	if property == "active" then
		if IsTotem(name) then
			code = format("TotemPresent(%s)", name)
		else
			code = format("%s%sPresent(%s)", target, prefix, buffName)
			symbol = buffName
		end
	elseif property == "cast_regen" then
		code = format("FocusCastingRegen(%s)", name)
	elseif property == "cast_time" then
		code = format("CastTime(%s)", name)
	elseif property == "charges" then
		code = format("Charges(%s)", name)
	elseif property == "charges_fractional" then
		code = format("Charges(%s count=0)", name)
	elseif property == "cooldown" then
		code = format("SpellCooldown(%s)", name)
	elseif property == "cooldown_react" then
		code = format("not SpellCooldown(%s) > 0", name)
	elseif property == "duration" then
		code = format("BaseDuration(%s)", buffName)
		symbol = buffName
	elseif property == "enabled" then
		if parseNode.asType == "boolean" then
			code = format("Talent(%s)", talentName)
		else
			code = format("TalentPoints(%s)", talentName)
		end
		symbol = talentName
	elseif property == "execute_time" then
		code = format("ExecuteTime(%s)", name)
	elseif property == "gcd" then
		code = "GCD()"
	elseif property == "in_flight" or property == "in_flight_to_target" then
		code = format("InFlightToTarget(%s)", name)
	elseif property == "miss_react" then
		-- "miss_react" has no meaning in Ovale.
		code = "True(miss_react)"
	elseif property == "persistent_multiplier" then
		code = format("PersistentMultiplier(%s)", buffName)
	elseif property == "recharge_time" then
		code = format("SpellChargeCooldown(%s)", name)
	elseif property == "remains" then
		if IsTotem(name) then
			code = format("TotemRemaining(%s)", name)
		else
			code = format("%s%sRemaining(%s)", buffTarget, prefix, buffName)
			symbol = buffName
		end
	elseif property == "shard_react" then
		-- XXX
		code = "SoulShards() >= 1"
	elseif property == "tick_time" then
		code = format("%sTickTime(%s)", buffTarget, buffName)
		symbol = buffName
	elseif property == "ticking" then
		code = format("%s%sPresent(%s)", buffTarget, prefix, buffName)
		symbol = buffName
	elseif property == "ticks_remain" then
		code = format("%sTicksRemaining(%s)", buffTarget, buffName)
		symbol = buffName
	elseif property == "travel_time" then
		-- Translate to the maximum travel time since we can't gauge the distance dynamically.
		code = format("MaxTravelTime(%s)", name)
	else
		ok = false
	end
	if ok and code then
		annotation.astAnnotation = annotation.astAnnotation or {}
		node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
		if symbol then
			AddSymbol(annotation, symbol)
		end
	end

	return ok, node
end

EmitOperandActiveDot = function(operand, parseNode, nodeList, annotation, action, target)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "active_dot" then
		local name = tokenIterator()
		name = Disambiguate(name, annotation.class, annotation.specialization)
		local dotName = name .. "_debuff"
		dotName = Disambiguate(dotName, annotation.class, annotation.specialization)
		local prefix = strfind(dotName, "_buff$") and "Buff" or "Debuff"
		target = target and (target .. ".") or ""

		local code = format("%sCountOnAny(%s)", prefix, dotName)
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
			AddSymbol(annotation, dotName)
		end
	else
		ok = false
	end

	return ok, node
end

EmitOperandBuff = function(operand, parseNode, nodeList, annotation, action, target)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "aura" or token == "buff" or token == "debuff" then
		local name = tokenIterator()
		local property = tokenIterator()
		name = Disambiguate(name, annotation.class, annotation.specialization)
		local buffName = (token == "debuff") and name .. "_debuff" or name .. "_buff"
		buffName = Disambiguate(buffName, annotation.class, annotation.specialization)
		local prefix = strfind(buffName, "_buff$") and "Buff" or "Debuff"
		local any = OvaleData.buffSpellList[buffName] and " any=1" or ""
		target = target and (target .. ".") or ""

		-- Unholy death knight's Dark Transformation applies the buff to the ghoul/pet.
		if buffName == "dark_transformation_buff" then
			if target == "" then
				target = "pet."
			end
			any = " any=1"
		end

		-- Assume that the "potion" action has already been seen.
		if buffName == "potion_buff" then
			if annotation.use_potion_agility then
				buffName = "potion_agility_buff"
			elseif annotation.use_potion_armor then
				buffName = "potion_armor_buff"
			elseif annotation.use_potion_intellect then
				buffName = "potion_intellect_buff"
			elseif annotation.use_potion_strength then
				buffName = "potion_strength_buff"
			end
		end

		local code
		if property == "cooldown_remains" then
			-- Assume that the spell and the buff have the same name.
			code = format("SpellCooldown(%s)", name)
		elseif property == "down" then
			code = format("%s%sExpires(%s%s)", target, prefix, buffName, any)
		elseif property == "duration" then
			code = format("BaseDuration(%s)", buffName)
		elseif property == "max_stack" then
			code = format("SpellData(%s max_stacks)", buffName)
		elseif property == "react" or property == "stack" then
			if parseNode.asType == "boolean" then
				code = format("%s%sPresent(%s%s)", target, prefix, buffName, any)
			else
				code = format("%s%sStacks(%s%s)", target, prefix, buffName, any)
			end
		elseif property == "remains" then
			if parseNode.asType == "boolean" then
				code = format("%s%sPresent(%s%s)", target, prefix, buffName, any)
			else
				code = format("%s%sRemaining(%s%s)", target, prefix, buffName, any)
			end
		elseif property == "up" then
			code = format("%s%sPresent(%s%s)", target, prefix, buffName, any)
		elseif property == "value" then
			code = format("%s%sAmount(%s%s)", target, prefix, buffName, any)
		else
			ok = false
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
			AddSymbol(annotation, buffName)
		end
	else
		ok = false
	end

	return ok, node
end

do
	local CHARACTER_PROPERTY = {
		["active_enemies"]		= "Enemies()",
		["blood.frac"]			= "Rune(blood)",
		["chi"]					= "Chi()",
		["chi.max"]				= "MaxChi()",
		["combo_points"]		= "ComboPoints()",
		["demonic_fury"]		= "DemonicFury()",
		["eclipse_change"]		= "TimeToEclipse()",	-- XXX
		["eclipse_energy"]		= "EclipseEnergy()",	-- XXX
		["energy"]				= "Energy()",
		["energy.max"]			= "MaxEnergy()",
		["energy.regen"]		= "EnergyRegenRate()",
		["energy.time_to_max"]	= "TimeToMaxEnergy()",
		["focus"]				= "Focus()",
		["focus.deficit"]		= "FocusDeficit()",
		["focus.regen"]			= "FocusRegenRate()",
		["focus.time_to_max"]	= "TimeToMaxFocus()",
		["frost.frac"]			= "Rune(frost)",
		["health"]				= "Health()",
		["health.deficit"]		= "HealthMissing()",
		["health.max"]			= "MaxHealth()",
		["health.pct"]			= "HealthPercent()",
		["health.percent"]		= "HealthPercent()",
		["holy_power"]			= "HolyPower()",
		["level"]				= "Level()",
		["lunar_max"]			= "TimeToEclipse(lunar)",	-- XXX
		["mana"]				= "Mana()",
		["mana.deficit"]		= "ManaDeficit()",
		["mana.max"]			= "MaxMana()",
		["mana.pct"]			= "ManaPercent()",
		["rage"]				= "Rage()",
		["rage.max"]			= "MaxRage()",
		["runic_power"]			= "RunicPower()",
		["shadow_orb"]			= "ShadowOrbs()",
		["soul_shard"]			= "SoulShards()",
		["stat.multistrike_pct"]= "MultistrikeChance()",
		["time"]				= "TimeInCombat()",
		["time_to_die"]			= "TimeToDie()",
		["unholy.frac"]			= "Rune(unholy)",
	}

	EmitOperandCharacter = function(operand, parseNode, nodeList, annotation, action, target)
		local ok = true
		local node

		local class = annotation.class
		local specialization = annotation.specialization

		target = target and (target .. ".") or ""
		local code
		if CHARACTER_PROPERTY[operand] then
			code = target .. CHARACTER_PROPERTY[operand]
		elseif class == "MAGE" and operand == "incanters_flow_dir" then
			local name = "incanters_flow_buff"
			code = format("BuffDirection(%s)", name)
			AddSymbol(annotation, name)
		elseif class == "PALADIN" and operand == "time_to_hpg" then
			if specialization == "holy" then
				code = "HolyTimeToHPG()"
				annotation.time_to_hpg_heal = class
			elseif specialization == "protection" then
				code = "ProtectionTimeToHPG()"
				annotation.time_to_hpg_tank = class
			elseif specialization == "retribution" then
				code = "RetributionTimeToHPG()"
				annotation.time_to_hpg_melee = class
			end
		elseif class == "ROGUE" and operand == "anticipation_charges" then
			local name = "anticipation_buff"
			code = format("BuffStacks(%s)", name)
			AddSymbol(annotation, name)
		elseif class == "WARLOCK" and operand == "burning_ember" then
			code = format("%sBurningEmbers() / 10", target)
		elseif strfind(operand, "^incoming_damage_") then
			local seconds, measure = strmatch(operand, "^incoming_damage_([%d]+)(m?s?)$")
			seconds = tonumber(seconds)
			if measure == "ms" then
				seconds = seconds / 1000
			end
			if parseNode.asType == "boolean" then
				code = format("IncomingDamage(%f) > 0", seconds)
			else
				code = format("IncomingDamage(%f)", seconds)
			end
		elseif operand == "mastery_value" then
			code = format("%sMasteryEffect() / 100", target)
		elseif operand == "position_front" then
			-- "position_front" should always be false in Ovale because we assume the
			-- player can get into the optimal attack position at all times.
			code = "False(position_front)"
		elseif strsub(operand, 1, 5) == "role." then
			local role = strmatch(operand, "^role%.([%w_]+)")
			if role and role == annotation.role then
				code = format("True(role_%s)", role)
			else
				code = format("False(role_%s)", role)
			end
		elseif operand == "spell_haste" or operand == "stat.spell_haste" then
			-- "spell_haste" is the player's spell factor, e.g.,
			-- 25% haste corresponds to a "spell_haste" value of 1/(1 + 0.25) = 0.8.
			code = "100 / { 100 + SpellHaste() }"
		else
			ok = false
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
		end

		return ok, node
	end
end

EmitOperandCooldown = function(operand, parseNode, nodeList, annotation, action)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "cooldown" then
		local name = tokenIterator()
		local property = tokenIterator()
		name = Disambiguate(name, annotation.class, annotation.specialization)
		local prefix = "Spell"

		-- Assume that the "potion" action has already been seen.
		if name == "potion" then
			prefix = "Item"
			if annotation.use_potion_agility then
				name = "draenic_agility_potion"
			elseif annotation.use_potion_armor then
				name = "draenic_armor_potion"
			elseif annotation.use_potion_intellect then
				name = "draenic_intellect_potion"
			elseif annotation.use_potion_strength then
				name = "draenic_strength_potion"
			end
		end

		local code
		if property == "duration" then
			code = format("%sCooldownDuration(%s)", prefix, name)
		elseif property == "remains" then
			if parseNode.asType == "boolean" then
				code = format("%sCooldown(%s) > 0", prefix, name)
			else
				code = format("%sCooldown(%s)", prefix, name)
			end
		elseif property == "up" then
			code = format("not %sCooldown(%s) > 0", prefix, name)
		else
			ok = false
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
			AddSymbol(annotation, name)
		end
	else
		ok = false
	end

	return ok, node
end

EmitOperandDisease = function(operand, parseNode, nodeList, annotation, action, target)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "disease" then
		local property = tokenIterator()
		target = target and (target .. ".") or ""

		local code
		if property == "max_ticking" then
			code = target .. "DiseasesAnyTicking()"
		elseif property == "min_remains" then
			code = target .. "DiseasesRemaining()"
		elseif property == "min_ticking" then
			code = target .. "DiseasesTicking()"
		elseif property == "ticking" then
			code = target .. "DiseasesAnyTicking()"
		else
			ok = false
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
		end
	else
		ok = false
	end

	return ok, node
end

EmitOperandDot = function(operand, parseNode, nodeList, annotation, action, target)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "dot" then
		local name = tokenIterator()
		local property = tokenIterator()
		name = Disambiguate(name, annotation.class, annotation.specialization)
		local dotName = name .. "_debuff"
		dotName = Disambiguate(dotName, annotation.class, annotation.specialization)
		local prefix = strfind(dotName, "_buff$") and "Buff" or "Debuff"
		target = target and (target .. ".") or ""

		local code
		if property == "duration" then
			code = format("%s%sDuration(%s)", target, prefix, dotName)
		elseif property == "pmultiplier" then
			code = format("%s%sPersistentMultiplier(%s)", target, prefix, dotName)
		elseif property == "remains" then
			code = format("%s%sRemaining(%s)", target, prefix, dotName)
		elseif property == "stack" then
			code = format("%s%sStacks(%s)", target, prefix, dotName)
		elseif property == "ticking" then
			code = format("%s%sPresent(%s)", target, prefix, dotName)
		elseif property == "ticks_remain" then
			code = format("%sTicksRemaining(%s)", target, dotName)
		else
			ok = false
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
			AddSymbol(annotation, dotName)
		end
	else
		ok = false
	end

	return ok, node
end

EmitOperandGlyph = function(operand, parseNode, nodeList, annotation, action)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "glyph" then
		local name = tokenIterator()
		local property = tokenIterator()
		name = Disambiguate(name, annotation.class, annotation.specialization)
		local glyphName = "glyph_of_" .. name
		glyphName = Disambiguate(glyphName, annotation.class, annotation.specialization)

		local code
		if property == "disabled" then
			code = format("not Glyph(%s)", glyphName)
		elseif property == "enabled" then
			code = format("Glyph(%s)", glyphName)
		else
			ok = false
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
			AddSymbol(annotation, glyphName)
		end
	else
		ok = false
	end

	return ok, node
end

EmitOperandPet = function(operand, parseNode, nodeList, annotation, action)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "pet" then
		local name = tokenIterator()
		local property = tokenIterator()
		name = Disambiguate(name, annotation.class, annotation.specialization)
		local isTotem = IsTotem(name)

		local code
		if isTotem and property == "active" then
			code = format("TotemPresent(%s)", name)
		elseif isTotem and property == "remains" then
			code = format("TotemRemaining(%s)", name)
		else
			-- Strip the "pet.<name>." from the operand and re-evaluate.
			local pattern = format("^pet%%.%s%%.([%%w_.]+)", name)
			local petOperand = strmatch(operand, pattern)
			local target = "pet"
			if petOperand then
				ok, node = EmitOperandSpecial(petOperand, parseNode, nodeList, annotation, action, target)
				if not ok then
					ok, node = EmitOperandAction(petOperand, parseNode, nodeList, annotation, action, target)
				end
				if not ok then
					ok, node = EmitOperandCharacter(petOperand, parseNode, nodeList, annotation, action, target)
				end
				if not ok then
					if property == "buff" then
						ok, node = EmitOperandBuff(petOperand, parseNode, nodeList, annotation, action, target)
					elseif property == "cooldown" then
						ok, node = EmitOperandCooldown(petOperand, parseNode, nodeList, annotation, action)
					elseif property == "debuff" then
						ok, node = EmitOperandBuff(petOperand, parseNode, nodeList, annotation, action, target)
					else
						ok = false
					end
				end
			else
				ok = false
			end
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
			AddSymbol(annotation, name)
		end
	else
		ok = false
	end

	return ok, node
end

EmitOperandPreviousSpell = function(operand, parseNode, nodeList, annotation, action)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "prev" or token == "prev_gcd" then
		local name = tokenIterator()
		name = Disambiguate(name, annotation.class, annotation.specialization)
		local code
		if token == "prev" then
			code = format("PreviousSpell(%s)", name)
		else -- if token == "prev_gcd" then
			code = format("PreviousGCDSpell(%s)", name)
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
			AddSymbol(annotation, name)
		end
	else
		ok = false
	end

	return ok, node
end

EmitOperandRaidEvent = function(operand, parseNode, nodeList, annotation, action)
	local ok = true
	local node

	local name
	local property
	if strsub(operand, 1, 11) == "raid_event." then
		local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
		local token = tokenIterator()
		name = tokenIterator()
		property = tokenIterator()
	else
		local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
		name = tokenIterator()
		property = tokenIterator()
	end

	local code
	if name == "movement" then
		--[[
			The "movement" raid event simulates needing to move during the encounter.
			We always assume the fight is Patchwerk-style, meaning no movement is
			necessary.
		--]]
		if property == "cooldown" or property == "in" then
			-- Pretend the next "movement" raid event is ten minutes from now.
			code = "600"
		elseif property == "distance" then
			code = "0"
		elseif property == "exists" then
			code = "False(raid_event_movement_exists)"
		elseif property == "remains" then
			code = "0"
		else
			ok = false
		end
	elseif name == "adds" then
		--[[
			The "adds" raid event simulates waves of adds on regular intervals.
			This is separate from the dynamic number of active enemies.
			We always assume that there are no add waves.
		--]]
		if property == "cooldown" then
			-- Pretend the next "adds" raid event is ten minutes from now.
			code = "600"
		elseif property == "count" then
			code = "0"
		elseif property == "exists" then
			code = "False(raid_event_adds_exists)"
		else
			ok = false
		end
	else
		ok = false
	end
	if ok and code then
		annotation.astAnnotation = annotation.astAnnotation or {}
		node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
	end

	return ok, node
end

EmitOperandRune = function(operand, parseNode, nodeList, annotation, action)
	local ok = true
	local node

	local code
	if parseNode.rune then
		local runeParameters
		if parseNode.includeDeath then
			runeParameters = parseNode.rune .. " death=1"
		else
			runeParameters = parseNode.rune
		end
		if parseNode.asType == "boolean" then
			code = format("Rune(%s) >= 1", runeParameters)
		else
			code = format("RuneCount(%s)", runeParameters)
		end
	else
		ok = false
	end
	if ok and code then
		annotation.astAnnotation = annotation.astAnnotation or {}
		node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
	end

	return ok, node
end

EmitOperandSetBonus = function(operand, parseNode, nodeList, annotation, action)
	local ok = true
	local node

	local setBonus = strmatch(operand, "^set_bonus%.(.*)$")
	local code
	if setBonus then
		local tokenIterator = gmatch(setBonus, "[^_]+")
		local name = tokenIterator()
		local count = tokenIterator()
		local role = tokenIterator()
		if name and count then
			local setName, level = strmatch(name, "^(%a+)(%d*)$")
			if setName == "tier" then
				setName = "T"
			else
				setName = strupper(setName)
			end
			if level then
				name = setName .. tostring(level)
			end
			if role then
				name = name .. "_" .. role
			end
			count = strmatch(count, "(%d+)pc")
			if name and count then
				code = format("ArmorSetBonus(%s %d)", name, count)
			end
		end
		if not code then
			ok = false
		end
	else
		ok = false
	end
	if ok and code then
		annotation.astAnnotation = annotation.astAnnotation or {}
		node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
	end

	return ok, node
end

EmitOperandSeal = function(operand, parseNode, nodeList, annotation, action)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "seal" then
		local name = tokenIterator()
		local code
		if name then
			code = format("Stance(paladin_seal_of_%s)", name)
		else
			ok = false
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
		end
	else
		ok = false
	end

	return ok, node
end

EmitOperandSpecial = function(operand, parseNode, nodeList, annotation, action, target)
	local ok = true
	local node

	local class = annotation.class
	local specialization = annotation.specialization

	target = target and (target .. ".") or ""
	local code
	if class == "DEATHKNIGHT" and operand == "dot.breath_of_sindragosa.ticking" then
		-- Breath of Sindragosa is the player buff from channeling the spell.
		local buffName = "breath_of_sindragosa_buff"
		code = format("BuffPresent(%s)", buffName)
		AddSymbol(annotation, buffName)
	elseif class == "DEATHKNIGHT" and strsub(operand, -9, -1) == ".ready_in" then
		local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
		local spellName = tokenIterator()
		spellName = Disambiguate(spellName, class, specialization)
		code = format("TimeToSpell(%s)", spellName)
		AddSymbol(annotation, spellName)
	elseif class == "DRUID" and operand == "buff.wild_charge_movement.down" then
		-- "wild_charge_movement" is a fake SimulationCraft buff that lasts for the
		-- duration of the movement during Wild Charge.
		code = "True(wild_charge_movement_down)"
	elseif class == "DRUID" and operand == "max_fb_energy" then
		-- SimulationCraft's max_fb_energy is the maximum cost of Ferocious Bite if used.
		local spellName = "ferocious_bite"
		code = format("EnergyCost(%s max=1)", spellName)
		AddSymbol(annotation, spellName)
	elseif class == "HUNTER" and operand == "buff.beast_cleave.down" then
		-- Beast Cleave is a buff on the hunter's pet.
		local buffName = "pet_beast_cleave_buff"
		code = format("pet.BuffExpires(%s any=1)", buffName)
		AddSymbol(annotation, buffName)
	elseif class == "HUNTER" and operand == "buff.careful_aim.up" then
		-- The "careful_aim" buff is a fake SimulationCraft buff.
		code = "target.HealthPercent() > 80 or BuffPresent(rapid_fire_buff)"
		AddSymbol(annotation, "rapid_fire_buff")
	elseif class == "MAGE" and (operand == "in_flight" and action == "fireball" or operand == "action.fireball.in_flight") then
		-- Frostfire Bolt can be substituted for Fireball when testing whether the spell is in flight.
		local fbName = "fireball"
		local ffbName = "frostfire_bolt"
		code = format("InFlightToTarget(%s) or InFlightToTarget(%s)", fbName, ffbName)
		AddSymbol(annotation, fbName)
		AddSymbol(annotation, ffbName)
	elseif class == "MAGE" and operand == "buff.rune_of_power.remains" then
		code = "TotemRemaining(rune_of_power)"
	elseif class == "MAGE" and operand == "dot.frozen_orb.ticking" then
		-- The Frozen Orb is ticking if fewer than 10s have elapsed since it was cast.
		local name = "frozen_orb"
		code = format("SpellCooldown(%s) > SpellCooldownDuration(%s) - 10", name, name)
		AddSymbol(annotation, name)
	elseif class == "MAGE" and operand == "pyro_chain" then
		if parseNode.asType == "boolean" then
			code = "GetState(pyro_chain) > 0"
		else
			code = "GetState(pyro_chain)"
		end
	elseif class == "MONK" and operand == "dot.zen_sphere.ticking" then
		-- Zen Sphere is a helpful DoT.
		local buffName = "zen_sphere_buff"
		code = format("BuffPresent(%s)", buffName)
		AddSymbol(annotation, buffName)
	elseif class == "MONK" and (operand == "stagger.heavy" or operand == "stagger.light" or operand == "stagger.moderate") then
		local property = strmatch(operand, "^stagger%.(%w+)")
		local buffName = format("%s_stagger_debuff", property)
		code = format("DebuffPresent(%s)", buffName)
		AddSymbol(annotation, buffName)
	elseif class == "PALADIN" and operand == "dot.sacred_shield.remains" then
		--[[
			Sacred Shield is handled specially because SimulationCraft treats it like
			a damaging spell, e.g., "target.dot.sacred_shield.remains" to represent the
			buff on the player.
		--]]
		local buffName = "sacred_shield_buff"
		code = format("BuffPresent(%s)", buffName)
		AddSymbol(annotation, buffName)
	elseif class == "PRIEST" and operand == "mind_harvest" then
		-- TODO: "mind_harvest" on the current target is 0 if no Mind Blast has been cast on the target yet.
		code = "0"
	elseif class == "PRIEST" and operand == "primary_target" then
		-- TODO: "primary_target" is 1 if the current target is the "main/boss" target.
		code = "0"
	elseif class == "ROGUE" and specialization == "subtlety" and operand == "cooldown.honor_among_thieves.remains" then
		-- The cooldown of Honor Among Thieves is implemented as a hidden buff.
		code = "BuffRemaining(honor_among_thieves_cooldown_buff)"
		annotation.honor_among_thieves = class
	elseif operand == "debuff.casting.react" then
		code = target .. "IsInterruptible()"
	elseif operand == "debuff.flying.down" then
		code = target .. "True(debuff_flying_down)"
	elseif operand == "distance" then
		code = target .. "Distance()"
	elseif operand == "gcd.max" then
		code = "GCD()"
	elseif operand == "gcd.remains" then
		code = "GCDRemaining()"
	else
		ok = false
	end
	if ok and code then
		annotation.astAnnotation = annotation.astAnnotation or {}
		node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
	end

	return ok, node
end

EmitOperandTalent = function(operand, parseNode, nodeList, annotation, action)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "talent" then
		local name = tokenIterator()
		local property = tokenIterator()
		-- Talent names need no disambiguation as they are the same across all specializations.
		--name = Disambiguate(name, annotation.class, annotation.specialization)
		local talentName = name .. "_talent"
		talentName = Disambiguate(talentName, annotation.class, annotation.specialization)

		local code
		if property == "disabled" then
			code = format("not Talent(%s)", talentName)
		elseif property == "enabled" then
			if parseNode.asType == "boolean" then
				code = format("Talent(%s)", talentName)
			else
				code = format("TalentPoints(%s)", talentName)
			end
		else
			ok = false
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
			AddSymbol(annotation, talentName)
		end
	else
		ok = false
	end

	return ok, node
end

EmitOperandTotem = function(operand, parseNode, nodeList, annotation, action)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "totem" then
		local name = tokenIterator()
		local property = tokenIterator()

		local code
		if property == "active" then
			code = format("TotemPresent(%s)", name)
		elseif property == "remains" then
			code = format("TotemRemaining(%s)", name)
		else
			ok = false
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
		end
	else
		ok = false
	end

	return ok, node
end

EmitOperandTrinket = function(operand, parseNode, nodeList, annotation, action)
	local ok = true
	local node

	local tokenIterator = gmatch(operand, OPERAND_TOKEN_PATTERN)
	local token = tokenIterator()
	if token == "trinket" then
		local procType = tokenIterator()
		local statName = tokenIterator()

		local code
		if strsub(procType, 1, 4) == "has_" then
			-- Assume these conditions are always true.
			-- TODO: Teach OvaleEquipment to check these conditions.
			code = format("True(trinket_%s_%s)", procType, statName)
		else
			local property = tokenIterator()
			local buffName = format("trinket_%s_%s_buff", procType, statName)
			buffName = Disambiguate(buffName, annotation.class, annotation.specialization)

			if property == "cooldown_remains" then
				code = format("BuffCooldown(%s)", buffName)
			elseif property == "down" then
				code = format("BuffExpires(%s)", buffName)
			elseif property == "react" then
				if parseNode.asType == "boolean" then
					code = format("BuffPresent(%s)", buffName)
				else
					code = format("BuffStacks(%s)", buffName)
				end
			elseif property == "remains" then
				code = format("BuffRemaining(%s)", buffName)
			elseif property == "stack" then
				code = format("BuffStacks(%s)", buffName)
			elseif property == "up" then
				code = format("BuffPresent(%s)", buffName)
			else
				ok = false
			end
			if ok then
				AddSymbol(annotation, buffName)
			end
		end
		if ok and code then
			annotation.astAnnotation = annotation.astAnnotation or {}
			node = OvaleAST:ParseCode("expression", code, nodeList, annotation.astAnnotation)
		end
	else
		ok = false
	end

	return ok, node
end

do
	EMIT_VISITOR = {
		["action"] = EmitAction,
		["action_list"] = EmitActionList,
		["arithmetic"] = EmitExpression,
		["compare"] = EmitExpression,
		["function"] = EmitFunction,
		["logical"] = EmitExpression,
		["number"] = EmitNumber,
		["operand"] = EmitOperand,
	}
end

local function Prune(node, removed)
	if node.type == "add_function" or node.type == "group" or node.type == "script" or node.type == "wait" then
		local isChanged = false
		local index = #node.child
		while index > 0 do
			local childNode = node.child[index]
			local changed, pruned = Prune(childNode, removed)
			if pruned then
				tremove(node.child, index)
				-- Remove the block of comments above this pruned line as well.
				local k = index - 1
				while k > 0 do
					childNode = node.child[k]
					if childNode.type == "comment" then
						tremove(node.child, k)
						k = k - 1
					else
						break
					end
				end
				index = k + 1
				isChanged = isChanged or changed
			end
			index = index - 1
		end
		-- Remove blank lines at the top of groups and scripts.
		if node.type == "group" or node.type == "script" then
			while true do
				local childNode = node.child[1]
				if childNode and childNode.type == "comment" and (not childNode.comment or childNode.comment == "") then
					tremove(node.child, 1)
					isChanged = true
				else
					break
				end
			end
		end
		local isPruned = #node.child == 0
		if isPruned and node.type == "add_function" then
			removed[node.name] = true
		end
		return isChanged, isPruned
	elseif node.type == "if" or node.type == "unless" then
		return Prune(node.child[2], removed)
	elseif node.type == "custom_function" and removed[node.name] then
		return true, true
	end
	return false, false
end

local function InsertSupportingFunctions(child, annotation)
	local count = 0
	local nodeList = annotation.astAnnotation.nodeList
	if annotation.mind_freeze == "DEATHKNIGHT" then
		local code = [[
			AddFunction InterruptActions
			{
				if not target.IsFriend() and target.IsInterruptible()
				{
					if target.InRange(mind_freeze) Spell(mind_freeze)
					if not target.Classification(worldboss)
					{
						if target.InRange(asphyxiate) Spell(asphyxiate)
						if target.InRange(strangulate) Spell(strangulate)
						Spell(arcane_torrent_runicpower)
						if target.InRange(quaking_palm) Spell(quaking_palm)
						Spell(war_stomp)
					}
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "arcane_torrent_runicpower")
		AddSymbol(annotation, "asphyxiate")
		AddSymbol(annotation, "mind_freeze")
		AddSymbol(annotation, "quaking_palm")
		AddSymbol(annotation, "strangulate")
		AddSymbol(annotation, "war_stomp")
		count = count + 1
	end
	if annotation.skull_bash == "DRUID" then
		local code = [[
			AddFunction InterruptActions
			{
				if not target.IsFriend() and target.IsInterruptible()
				{
					if target.InRange(skull_bash) Spell(skull_bash)
					if not target.Classification(worldboss)
					{
						if target.InRange(mighty_bash) Spell(mighty_bash)
						Spell(typhoon)
						if target.InRange(maim) Spell(maim)
						Spell(war_stomp)
					}
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "maim")
		AddSymbol(annotation, "mighty_bash")
		AddSymbol(annotation, "skull_bash")
		AddSymbol(annotation, "typhoon")
		AddSymbol(annotation, "war_stomp")
		count = count + 1
	end
	if annotation.melee == "DRUID" then
		local code = [[
			AddFunction GetInMeleeRange
			{
				if Stance(druid_bear_form) and not target.InRange(mangle) or { Stance(druid_cat_form) or Stance(druid_claws_of_shirvallah) } and not target.InRange(shred)
				{
					if target.InRange(wild_charge) Spell(wild_charge)
					Texture(misc_arrowlup help=L(not_in_melee_range))
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "shortcd"
		AddSymbol(annotation, "mangle")
		AddSymbol(annotation, "shred")
		AddSymbol(annotation, "wild_charge")
		AddSymbol(annotation, "wild_charge_bear")
		AddSymbol(annotation, "wild_charge_cat")
		count = count + 1
	end
	if annotation.summon_pet == "HUNTER" then
		local code
		if annotation.specialization == "beast_mastery" then
			code = [[
				AddFunction BeastMasterySummonPet
				{
					if not pet.Present() Texture(ability_hunter_beastcall help=L(summon_pet))
					if pet.IsDead() Spell(revive_pet)
				}
			]]
		else
			code = [[
				AddFunction SummonPet
				{
					if not Talent(lone_wolf_talent)
					{
						if not pet.Present() Texture(ability_hunter_beastcall help=L(summon_pet))
						if pet.IsDead() Spell(revive_pet)
					}
				}
			]]
			AddSymbol(annotation, "lone_wolf_talent")
		end
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "shortcd"
		AddSymbol(annotation, "revive_pet")
		count = count + 1
	end
	if annotation.counter_shot == "HUNTER" then
		local code = [[
			AddFunction InterruptActions
			{
				if not target.IsFriend() and target.IsInterruptible()
				{
					Spell(counter_shot)
					if not target.Classification(worldboss)
					{
						Spell(arcane_torrent_focus)
						if target.InRange(quaking_palm) Spell(quaking_palm)
						Spell(war_stomp)
					}
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "arcane_torrent_focus")
		AddSymbol(annotation, "counter_shot")
		AddSymbol(annotation, "quaking_palm")
		AddSymbol(annotation, "war_stomp")
		count = count + 1
	end
	if annotation.counterspell == "MAGE" then
		local code = [[
			AddFunction InterruptActions
			{
				if not target.IsFriend() and target.IsInterruptible()
				{
					Spell(counterspell)
					if not target.Classification(worldboss)
					{
						Spell(arcane_torrent_mana)
						if target.InRange(quaking_palm) Spell(quaking_palm)
					}
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "arcane_torrent_mana")
		AddSymbol(annotation, "counterspell")
		AddSymbol(annotation, "quaking_palm")
		count = count + 1
	end
	if annotation.spear_hand_strike == "MONK" then
		local code = [[
			AddFunction InterruptActions
			{
				if not target.IsFriend() and target.IsInterruptible()
				{
					if target.InRange(spear_hand_strike) Spell(spear_hand_strike)
					if not target.Classification(worldboss)
					{
						if target.InRange(paralysis) Spell(paralysis)
						Spell(arcane_torrent_chi)
						if target.InRange(quaking_palm) Spell(quaking_palm)
						Spell(war_stomp)
					}
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "arcane_torrent_chi")
		AddSymbol(annotation, "paralysis")
		AddSymbol(annotation, "quaking_palm")
		AddSymbol(annotation, "spear_hand_strike")
		AddSymbol(annotation, "war_stomp")
		count = count + 1
	end
	if annotation.time_to_hpg_melee == "PALADIN" then
		local code = [[
			AddFunction RetributionTimeToHPG
			{
				SpellCooldown(crusader_strike exorcism exorcism_glyphed hammer_of_wrath hammer_of_wrath_empowered judgment usable=1)
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "crusader_strike")
		AddSymbol(annotation, "exorcism")
		AddSymbol(annotation, "exorcism_glyphed")
		AddSymbol(annotation, "hammer_of_wrath")
		AddSymbol(annotation, "judgment")
		count = count + 1
	end
	if annotation.time_to_hpg_tank == "PALADIN" then
		local code = [[
			AddFunction ProtectionTimeToHPG
			{
				if Talent(sanctified_wrath_talent) SpellCooldown(crusader_strike holy_wrath judgment)
				if not Talent(sanctified_wrath_talent) SpellCooldown(crusader_strike judgment)
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "crusader_strike")
		AddSymbol(annotation, "holy_wrath")
		AddSymbol(annotation, "judgment")
		AddSymbol(annotation, "sanctified_wrath_talent")
		count = count + 1
	end
	if annotation.class == "PALADIN" then
		local code
		if annotation.specialization == "protection" then
			code = [[
				AddFunction ProtectionRighteousFury
				{
					if CheckBoxOn(opt_righteous_fury_check) and BuffExpires(righteous_fury) Spell(righteous_fury)
				}
			]]
		else
			code = [[
				AddFunction RighteousFuryOff
				{
					if CheckBoxOn(opt_righteous_fury_check) and BuffPresent(righteous_fury) Texture(spell_holy_sealoffury text=cancel)
				}
			]]
		end
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "shortcd"
		AddSymbol(annotation, "righteous_fury")
		count = count + 1
	end
	if annotation.time_to_hpg_heal == "PALADIN" then
		local code = [[
			AddFunction HolyTimeToHPG
			{
				SpellCooldown(crusader_strike holy_shock judgment)
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "crusader_strike")
		AddSymbol(annotation, "holy_shock")
		AddSymbol(annotation, "judgment")
		count = count + 1
	end
	if annotation.rebuke == "PALADIN" then
		local code = [[
			AddFunction InterruptActions
			{
				if not target.IsFriend() and target.IsInterruptible()
				{
					if target.InRange(rebuke) Spell(rebuke)
					if not target.Classification(worldboss)
					{
						if target.InRange(fist_of_justice) Spell(fist_of_justice)
						if target.InRange(hammer_of_justice) Spell(hammer_of_justice)
						Spell(blinding_light)
						Spell(arcane_torrent_holy)
						if target.InRange(quaking_palm) Spell(quaking_palm)
						Spell(war_stomp)
					}
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "arcane_torrent_holy")
		AddSymbol(annotation, "blinding_light")
		AddSymbol(annotation, "fist_of_justice")
		AddSymbol(annotation, "hammer_of_justice")
		AddSymbol(annotation, "quaking_palm")
		AddSymbol(annotation, "rebuke")
		AddSymbol(annotation, "war_stomp")
		count = count + 1
	end
	if annotation.melee == "PALADIN" then
		local code = [[
			AddFunction GetInMeleeRange
			{
				if not target.InRange(rebuke) Texture(misc_arrowlup help=L(not_in_melee_range))
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "shortcd"
		AddSymbol(annotation, "rebuke")
		count = count + 1
	end
	if annotation.silence == "PRIEST" then
		local code = [[
			AddFunction InterruptActions
			{
				if not target.IsFriend() and target.IsInterruptible()
				{
					Spell(silence)
					if not target.Classification(worldboss)
					{
						Spell(arcane_torrent_mana)
						if target.InRange(quaking_palm) Spell(quaking_palm)
						Spell(war_stomp)
					}
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "arcane_torrent_mana")
		AddSymbol(annotation, "quaking_palm")
		AddSymbol(annotation, "silence")
		AddSymbol(annotation, "war_stomp")
		count = count + 1
	end
	if annotation.kick == "ROGUE" then
		local code = [[
			AddFunction InterruptActions
			{
				if not target.IsFriend() and target.IsInterruptible()
				{
					if target.InRange(kick) Spell(kick)
					if not target.Classification(worldboss)
					{
						if target.InRange(cheap_shot) Spell(cheap_shot)
						if target.InRange(deadly_throw) and ComboPoints() == 5 Spell(deadly_throw)
						if target.InRange(kidney_shot) Spell(kidney_shot)
						Spell(arcane_torrent_energy)
						if target.InRange(quaking_palm) Spell(quaking_palm)
					}
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "arcane_torrent_energy")
		AddSymbol(annotation, "cheap_shot")
		AddSymbol(annotation, "deadly_throw")
		AddSymbol(annotation, "kick")
		AddSymbol(annotation, "kidney_shot")
		AddSymbol(annotation, "quaking_palm")
		count = count + 1
	end
	if annotation.melee == "ROGUE" then
		local code = [[
			AddFunction GetInMeleeRange
			{
				if not target.InRange(kick)
				{
					Spell(shadowstep)
					Texture(misc_arrowlup help=L(not_in_melee_range))
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "shortcd"
		AddSymbol(annotation, "kick")
		AddSymbol(annotation, "shadowstep")
		count = count + 1
	end
	if annotation.wind_shear == "SHAMAN" then
		local code = [[
			AddFunction InterruptActions
			{
				if not target.IsFriend() and target.IsInterruptible()
				{
					Spell(wind_shear)
					if not target.Classification(worldboss)
					{
						Spell(arcane_torrent_mana)
						if target.InRange(quaking_palm) Spell(quaking_palm)
						Spell(war_stomp)
					}
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "arcane_torrent_mana")
		AddSymbol(annotation, "quaking_palm")
		AddSymbol(annotation, "wind_shear")
		AddSymbol(annotation, "war_stomp")
		count = count + 1
	end
	if annotation.bloodlust == "SHAMAN" then
		local code = [[
			AddFunction Bloodlust
			{
				if CheckBoxOn(opt_bloodlust) and DebuffExpires(burst_haste_debuff any=1)
				{
					Spell(bloodlust)
					Spell(heroism)
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "bloodlust")
		AddSymbol(annotation, "heroism")
		count = count + 1
	end
	if annotation.pummel == "WARRIOR" then
		local code = [[
			AddFunction InterruptActions
			{
				if not target.IsFriend() and target.IsInterruptible()
				{
					if target.InRange(pummel) Spell(pummel)
					if Glyph(glyph_of_gag_order) and target.InRange(heroic_throw) Spell(heroic_throw)
					if not target.Classification(worldboss)
					{
						Spell(arcane_torrent_rage)
						if target.InRange(quaking_palm) Spell(quaking_palm)
						Spell(war_stomp)
					}
				}
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "arcane_torrent_rage")
		AddSymbol(annotation, "glyph_of_gag_order")
		AddSymbol(annotation, "heroic_throw")
		AddSymbol(annotation, "pummel")
		AddSymbol(annotation, "quaking_palm")
		AddSymbol(annotation, "war_stomp")
		count = count + 1
	end
	if annotation.melee == "WARRIOR" then
		local code = [[
			AddFunction GetInMeleeRange
			{
				if not target.InRange(pummel) Texture(misc_arrowlup help=L(not_in_melee_range))
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "shortcd"
		AddSymbol(annotation, "pummel")
		count = count + 1
	end
	if annotation.use_item then
		local code = [[
			AddFunction UseItemActions
			{
				Item(HandSlot usable=1)
				Item(Trinket0Slot usable=1)
				Item(Trinket1Slot usable=1)
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		count = count + 1
	end
	if annotation.use_potion_strength then
		local code = [[
			AddFunction UsePotionStrength
			{
				if CheckBoxOn(opt_potion_strength) and target.Classification(worldboss) Item(draenic_strength_potion usable=1)
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "draenic_strength_potion")
		count = count + 1
	end
	if annotation.use_potion_mana then
		local code = [[
			AddFunction UsePotionMana
			{
				if CheckBoxOn(opt_potion_mana) Item(draenic_mana_potion usable=1)
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "draenic_mana_potion")
		count = count + 1
	end
	if annotation.use_potion_intellect then
		local code = [[
			AddFunction UsePotionIntellect
			{
				if CheckBoxOn(opt_potion_intellect) and target.Classification(worldboss) Item(draenic_intellect_potion usable=1)
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "draenic_intellect_potion")
		count = count + 1
	end
	if annotation.use_potion_armor then
		local code = [[
			AddFunction UsePotionArmor
			{
				if CheckBoxOn(opt_potion_armor) and target.Classification(worldboss) Item(draenic_armor_potion usable=1)
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "draenic_armor_potion")
		count = count + 1
	end
	if annotation.use_potion_agility then
		local code = [[
			AddFunction UsePotionAgility
			{
				if CheckBoxOn(opt_potion_agility) and target.Classification(worldboss) Item(draenic_agility_potion usable=1)
			}
		]]
		local node = OvaleAST:ParseCode("add_function", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		annotation.functionTag[node.name] = "cd"
		AddSymbol(annotation, "draenic_agility_potion")
		count = count + 1
	end
	return count
end

local function InsertSupportingControls(child, annotation)
	local count = 0
	local nodeList = annotation.astAnnotation.nodeList
	if annotation.trap_launcher == "HUNTER" then
		local code = [[
			AddCheckBox(opt_trap_launcher SpellName(trap_launcher) default)
		]]
		local node = OvaleAST:ParseCode("checkbox", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "trap_launcher")
		count = count + 1
	end
	if annotation.time_warp == "MAGE" then
		local code = [[
			AddCheckBox(opt_time_warp SpellName(time_warp) default)
		]]
		local node = OvaleAST:ParseCode("checkbox", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "time_warp")
		count = count + 1
	end
	if annotation.chi_burst == "MONK" then
		local code = [[
			AddCheckBox(opt_chi_burst SpellName(chi_burst) default)
		]]
		local node = OvaleAST:ParseCode("checkbox", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "chi_burst")
		count = count + 1
	end
	if annotation.blade_flurry == "ROGUE" then
		local code = [[
			AddCheckBox(opt_blade_flurry SpellName(blade_flurry) default)
		]]
		local node = OvaleAST:ParseCode("checkbox", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "blade_flurry")
		count = count + 1
	end
	if annotation.bloodlust == "SHAMAN" then
		local code = [[
			AddCheckBox(opt_bloodlust SpellName(bloodlust) default)
		]]
		local node = OvaleAST:ParseCode("checkbox", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "bloodlust")
		count = count + 1
	end
	if annotation.class == "PALADIN" then
		local code = [[
			AddCheckBox(opt_righteous_fury_check SpellName(righteous_fury) default)
		]]
		local node = OvaleAST:ParseCode("checkbox", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "righteous_fury")
		count = count + 1
	end
	if annotation.use_potion_strength then
		local code = [[
			AddCheckBox(opt_potion_strength ItemName(draenic_strength_potion) default)
		]]
		local node = OvaleAST:ParseCode("checkbox", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "draenic_strength_potion")
		count = count + 1
	end
	if annotation.use_potion_mana then
		local code = [[
			AddCheckBox(opt_potion_mana ItemName(draenic_mana_potion) default)
		]]
		local node = OvaleAST:ParseCode("checkbox", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "draenic_mana_potion")
		count = count + 1
	end
	if annotation.use_potion_intellect then
		local code = [[
			AddCheckBox(opt_potion_intellect ItemName(draenic_intellect_potion) default)
		]]
		local node = OvaleAST:ParseCode("checkbox", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "draenic_intellect_potion")
		count = count + 1
	end
	if annotation.use_potion_armor then
		local code = [[
			AddCheckBox(opt_potion_armor ItemName(draenic_armor_potion) default)
		]]
		local node = OvaleAST:ParseCode("checkbox", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "draenic_armor_potion")
		count = count + 1
	end
	if annotation.use_potion_agility then
		local code = [[
			AddCheckBox(opt_potion_agility ItemName(draenic_agility_potion) default)
		]]
		local node = OvaleAST:ParseCode("checkbox", code, nodeList, annotation.astAnnotation)
		tinsert(child, 1, node)
		AddSymbol(annotation, "draenic_agility_potion")
		count = count + 1
	end
	return count
end

local function InsertSupportingDefines(child, annotation)
	local count = 0
	local nodeList = annotation.astAnnotation.nodeList
	if annotation.honor_among_thieves == "ROGUE" then
		local buffName = "honor_among_thieves_cooldown_buff"
		do
			local code = format("SpellInfo(%s duration=%f)", buffName, annotation[buffName])
			local node = OvaleAST:ParseCode("spell_info", code, nodeList, annotation.astAnnotation)
			tinsert(child, 1, node)
			count = count + 1
		end
		do
			local code = format("Define(%s %d)", buffName, OvaleHonorAmongThieves.spellId)
			local node = OvaleAST:ParseCode("define", code, nodeList, annotation.astAnnotation)
			tinsert(child, 1, node)
			count = count + 1
		end
		AddSymbol(annotation, buffName)
	end
	return count
end

local function GenerateIconBody(output, tag, profile)
	local annotation = profile.annotation
	local precombatName = OvaleFunctionName("precombat", annotation)
	local defaultName = OvaleFunctionName("_default", annotation)
	local removed = annotation.removedFunction

	local functionName
	if profile["actions.precombat"] then
		functionName = OvaleTaggedFunctionName(precombatName, tag)
		if not removed[functionName] then
			tinsert(output, format("	if not InCombat() %s()", functionName))
		end
	end
	functionName = OvaleTaggedFunctionName(defaultName, tag)
	if not removed[functionName] then
		tinsert(output, format("	%s()", functionName))
	end
end
--</private-static-methods>

--<public-static-methods>
function OvaleSimulationCraft:OnInitialize()
	-- Resolve module dependencies.
	OvaleAST = Ovale.OvaleAST
	OvaleCompile = Ovale.OvaleCompile
	OvaleData = Ovale.OvaleData
	OvaleHonorAmongThieves = Ovale.OvaleHonorAmongThieves
	OvaleLexer = Ovale.OvaleLexer
	OvalePower = Ovale.OvalePower

	InitializeDisambiguation()
	self:CreateOptions()
end

function OvaleSimulationCraft:DebuggingInfo()
	self_pool:DebuggingInfo()
	self_childrenPool:DebuggingInfo()
	self_outputPool:DebuggingInfo()
end

function OvaleSimulationCraft:ToString(tbl)
	local output = print_r(tbl)
	return tconcat(output, "\n")
end

function OvaleSimulationCraft:Release(profile)
	if profile.annotation then
		local annotation = profile.annotation
		if annotation.astAnnotation then
			OvaleAST:ReleaseAnnotation(annotation.astAnnotation)
		end
		if annotation.nodeList then
			for _, node in ipairs(annotation.nodeList) do
				self_pool:Release(node)
			end
		end
		for key, value in pairs(annotation) do
			if type(value) == "table" then
				wipe(value)
			end
			annotation[key] = nil
		end
		profile.annotation = nil
	end
	profile.actionList = nil
end

function OvaleSimulationCraft:ParseProfile(simc)
	local profile = {}
	for line in gmatch(simc, "[^\r\n]+") do
		-- Trim leading and trailing whitespace.
		line = strmatch(line, "^%s*(.-)%s*$")
		if not (strmatch(line, "^#.*") or strmatch(line, "^$")) then
			-- Line is not a comment or an empty string.
			local key, operator, value = strmatch(line, "([^%+=]+)(%+?=)(.*)")
			if operator == "=" then
				profile[key] = value
			elseif operator == "+=" then
				if type(profile[key]) ~= "table" then
					local oldValue = profile[key]
					profile[key] = {}
					tinsert(profile[key], oldValue)
				end
				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
			profile[k] = tconcat(v)
		end
	end
	-- Parse the action lists.
	local ok = true
	local annotation = {}
	local nodeList = {}
	local actionList = {}
	for k, v in pairs(profile) do
		if ok and strmatch(k, "^actions") then
			-- Name the default action list "_default" so it's first alphabetically.
			local name = strmatch(k, "^actions%.([%w_]+)") or "_default"
			local node
			ok, node = ParseActionList(name, v, nodeList, annotation)
			if ok then
				actionList[#actionList + 1] = node
			else
				break
			end
		end
	end
	-- Sort the action lists alphabetically.
	tsort(actionList, function(a, b) return a.name < b.name end)
	-- Set the name, class, specialization, and role from the profile.
	for class in pairs(RAID_CLASS_COLORS) do
		local lowerClass = strlower(class)
		if profile[lowerClass] then
			annotation.class = class
			annotation.name = profile[lowerClass]
		end
	end
	annotation.specialization = profile.spec
	annotation.level = profile.level
	ok = ok and (annotation.class and annotation.specialization and annotation.level)
	annotation.pet = profile.default_pet
	annotation.role = profile.role

	-- Set the attack range of the class and role.
	if profile.role == "tank" then
		annotation.melee = annotation.class
	elseif profile.role == "spell" then
		annotation.ranged = annotation.class
	elseif profile.role == "attack" or profile.role == "dps" then
		if profile.position == "ranged_back" then
			annotation.ranged = annotation.class
		else
			annotation.melee = annotation.class
		end
	end

	-- Create table of tagged Ovale function names.
	local taggedFunctionName = {}
	for _, node in ipairs(actionList) do
		local fname = OvaleFunctionName(node.name, annotation)
		taggedFunctionName[fname] = true
		for _, tag in pairs(OVALE_TAGS) do
			local taggedName = OvaleTaggedFunctionName(fname, tag)
			taggedFunctionName[taggedName] = true
		end
	end
	annotation.taggedFunctionName = taggedFunctionName
	annotation.functionTag = {}

	profile.actionList = actionList
	profile.annotation = annotation
	annotation.nodeList = nodeList

	if not ok then
		self:Release(profile)
		profile = nil
	end
	return profile
end

function OvaleSimulationCraft:Unparse(profile)
	local output = self_outputPool:Get()
	if profile.actionList then
		for _, node in ipairs(profile.actionList) do
			output[#output + 1] = Unparse(node)
		end
	end
	local s = tconcat(output, "\n")
	self_outputPool:Release(output)
	return s
end

function OvaleSimulationCraft:EmitAST(profile)
	local nodeList = {}
	local ast = OvaleAST:NewNode(nodeList, true)
	local child = ast.child
	ast.type = "script"

	local annotation = profile.annotation
	local ok = true
	if profile.actionList then
		annotation.astAnnotation = annotation.astAnnotation or {}
		annotation.astAnnotation.nodeList = nodeList
		-- Load the dictionary of defined symbols from evaluating the script headers.
		local dictionaryAST
		do
			OvaleDebug:ResetTrace()
			local dictionaryAnnotation = { nodeList = {} }
			local dictionaryCode = "Include(ovale_common) Include(ovale_" .. strlower(annotation.class) .. "_spells)"
			dictionaryAST = OvaleAST:ParseCode("script", dictionaryCode, dictionaryAnnotation.nodeList, dictionaryAnnotation)
			if dictionaryAST then
				dictionaryAST.annotation = dictionaryAnnotation
				annotation.dictionary = dictionaryAnnotation.definition
				OvaleAST:PropagateConstants(dictionaryAST)
				OvaleAST:PropagateStrings(dictionaryAST)
				OvaleAST:FlattenParameters(dictionaryAST)
				Ovale:ResetControls()
				OvaleCompile:EvaluateScript(dictionaryAST, true)
			end
		end
		-- Generate the AST "add_function" nodes from the action lists.
		local removedFunction = {}
		for _, node in ipairs(profile.actionList) do
			local addFunctionNode = EmitActionList(node, nodeList, annotation)
			if addFunctionNode then
				local actionListName = gsub(node.name, "^_+", "")
				local commentNode = OvaleAST:NewNode(nodeList)
				commentNode.type = "comment"
				commentNode.comment = "## actions." .. actionListName
				child[#child + 1] = commentNode
				-- Split this action list function by each tag.
				for _, tag in pairs(OVALE_TAGS) do
					local bodyNode = SplitByTag(tag, addFunctionNode, nodeList, annotation)
					local groupNode = bodyNode.child[1]
					if #groupNode.child > 0 then
						child[#child + 1] = bodyNode
					else
						removedFunction[bodyNode.name] = true
					end
				end
			else
				ok = false
				break
			end
		end
		-- Walk the AST and remove empty functions.
		local changed = Prune(ast, removedFunction)
		while changed do
			changed = Prune(ast, removedFunction)
		end
		annotation.removedFunction = removedFunction
		-- Clean up the dictionary that was only needed to generate the AST.
		if dictionaryAST then
			OvaleAST:Release(dictionaryAST)
			annotation.dictionary = nil
		end
	end
	if ok then
		-- Fixups.
		do
			-- Some profiles don't include any interrupt actions.
			local class = annotation.class
			annotation.mind_freeze = class			-- deathknight
			annotation.counter_shot = class			-- hunter
			annotation.spear_hand_strike = class	-- monk
			annotation.silence = class				-- priest
			annotation.pummel = class				-- warrior
		end
		annotation.supportingFunctionCount = InsertSupportingFunctions(child, annotation)
		annotation.supportingControlCount = InsertSupportingControls(child, annotation)
		annotation.supportingDefineCount = InsertSupportingDefines(child, annotation)
	end
	if not ok then
		OvaleAST:Release(ast)
		ast = nil
	end
	return ast
end

function OvaleSimulationCraft:Emit(profile)
	local nodeList = {}
	local ast = self:EmitAST(profile)
	local annotation = profile.annotation
	local class = annotation.class
	local lowerclass = strlower(class)
	local specialization = annotation.specialization

	local output = self_outputPool:Get()
	-- Prepend a comment block header for the script.
	do
		output[#output + 1] = "# Based on SimulationCraft profile " .. annotation.name .. "."
		output[#output + 1] = "#	class=" .. lowerclass
		output[#output + 1] = "#	spec=" .. specialization
		if profile.talents then
			output[#output + 1] = "#	talents=" .. profile.talents
		end
		if profile.glyphs then
			output[#output + 1] = "#	glyphs=" .. profile.glyphs
		end
		if profile.default_pet then
			output[#output + 1] = "#	pet=" .. profile.default_pet
		end
	end
	-- Includes.
	do
		output[#output + 1] = ""
		output[#output + 1] = "Include(ovale_common)"
		output[#output + 1] = format("Include(ovale_%s_spells)", lowerclass)
		-- Insert an extra blank line to separate section for controls from the includes.
		if annotation.supportingDefineCount + annotation.supportingControlCount > 0 then
			output[#output + 1] = ""
		end
	end
	-- Output the script itself.
	output[#output + 1] = OvaleAST:Unparse(ast)
	-- Output a standard four-icon layout for the rotation: [shortcd] [main] [aoe] [cd]
	do
		local aoeToggle = "opt_" .. lowerclass .. "_" .. specialization .. "_aoe"
		output[#output + 1] = ""
		output[#output + 1] = "### " .. CamelCase(specialization) .. " icons."
		output[#output + 1] = format("AddCheckBox(%s L(AOE) specialization=%s default)", aoeToggle, specialization)

		-- Short CD rotation.
		output[#output + 1] = ""
		output[#output + 1] = format("AddIcon specialization=%s help=shortcd enemies=1 checkbox=!%s", specialization, aoeToggle)
		output[#output + 1] = "{"
		GenerateIconBody(output, "shortcd", profile)
		output[#output + 1] = "}"
		output[#output + 1] = ""
		output[#output + 1] = format("AddIcon specialization=%s help=shortcd checkbox=%s", specialization, aoeToggle)
		output[#output + 1] = "{"
		GenerateIconBody(output, "shortcd", profile)
		output[#output + 1] = "}"
		-- Single-target rotation.
		output[#output + 1] = ""
		output[#output + 1] = format("AddIcon specialization=%s help=main enemies=1", specialization)
		output[#output + 1] = "{"
		GenerateIconBody(output, "main", profile)
		output[#output + 1] = "}"
		-- AoE rotation.
		output[#output + 1] = ""
		output[#output + 1] = format("AddIcon specialization=%s help=aoe checkbox=%s", specialization, aoeToggle)
		output[#output + 1] = "{"
		GenerateIconBody(output, "main", profile)
		output[#output + 1] = "}"
		-- CD rotation.
		output[#output + 1] = ""
		output[#output + 1] = format("AddIcon specialization=%s help=cd enemies=1 checkbox=!%s", specialization, aoeToggle)
		output[#output + 1] = "{"
		GenerateIconBody(output, "cd", profile)
		output[#output + 1] = "}"
		output[#output + 1] = ""
		output[#output + 1] = format("AddIcon specialization=%s help=cd checkbox=%s", specialization, aoeToggle)
		output[#output + 1] = "{"
		GenerateIconBody(output, "cd", profile)
		output[#output + 1] = "}"
	end
	-- Append the required symbols for the script.
	if profile.annotation.symbolTable then
		output[#output + 1] = ""
		output[#output + 1] = "### Required symbols"
		tsort(profile.annotation.symbolTable)
		for _, symbol in ipairs(profile.annotation.symbolTable) do
			output[#output + 1] = "# " .. symbol
		end
	end
	local s = tconcat(output, "\n")
	self_outputPool:Release(output)
	OvaleAST:Release(ast)
	return s
end

function OvaleSimulationCraft:CreateOptions()
	local options = {
		name = OVALE .. " SimulationCraft",
		type = "group",
		args = {
			input = {
				name = L["Input"],
				type = "group",
				args = {
					input = {
						name = L["SimulationCraft Profile"],
						desc = L["The contents of a SimulationCraft profile (*.simc)."],
						type = "input",
						multiline = 25,
						width = "full",
						get = function(info) return self_lastSimC end,
						set = function(info, value)
							self_lastSimC = value
							local profile = self:ParseProfile(self_lastSimC)
							local code = ""
							if profile then
								code = self:Emit(profile) .. "\n"
							end
							-- Substitute spaces for tabs.
							self_lastScript = gsub(code, "\t", "    ")
						end,
					},
				},
			},
			output = {
				name = L["Output"],
				type = "group",
				args = {
					output = {
						name = L["Script"],
						desc = L["The script translated from the SimulationCraft profile."],
						type = "input",
						multiline = 25,
						width = "full",
						get = function() return self_lastScript end,
					},
				},
			},
		},
	}

	local appName = self:GetName()
	AceConfig:RegisterOptionsTable(appName, options)
	AceConfigDialog:AddToBlizOptions(appName, "SimulationCraft", OVALE)
end
--</public-static-methods>