Quantcast
--[[--------------------------------------------------------------------
    Ovale Spell Priority
    Copyright (C) 2014 Johnny C. Lam

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License in the LICENSE
    file accompanying this program.
--]]--------------------------------------------------------------------

--[[----------------------------------------------------------------------------
	This module implements a parser that generates an abstract syntax tree (AST)
	from an Ovale script.

	An AST data structure is a table with the following public properties:

		ast.annotation
		ast.annotation.customFunction
		ast.annotation.definition
		ast.annotation.functionCall
		ast.child
--]]----------------------------------------------------------------------------

local _, Ovale = ...
local OvaleAST = Ovale:NewModule("OvaleAST")
Ovale.OvaleAST = OvaleAST

--<private-static-properties>
local L = Ovale.L
local OvalePool = Ovale.OvalePool

-- Forward declarations for module dependencies.
local OvaleCondition = nil
local OvaleLexer = nil
local OvaleScripts = nil

local format = string.format
local gsub = string.gsub
local next = next
local pairs = pairs
local rawset = rawset
local setmetatable = setmetatable
local strlower = string.lower
local strmatch = string.match
local strsub = string.sub
local tconcat = table.concat
local tinsert = table.insert
local tonumber = tonumber
local tostring = tostring
local type = type
local wipe = table.wipe
local yield = coroutine.yield
local API_GetItemInfo = GetItemInfo
local API_GetSpellInfo = GetSpellInfo

-- Profiling set-up.
local Profiler = Ovale.Profiler
local profiler = nil
do
	local group = OvaleAST:GetName()

	local function EnableProfiling()
		API_GetItemInfo = Profiler:Wrap(group, "OvaleAST_API_GetItemInfo", GetItemInfo)
		API_GetSpellInfo = Profiler:Wrap(group, "OvaleAST_API_GetSpellInfo", GetSpellInfo)
	end

	local function DisableProfiling()
		API_GetItemInfo = GetItemInfo
		API_GetSpellInfo = GetSpellInfo
	end

	Profiler:RegisterProfilingGroup(group, EnableProfiling, DisableProfiling)
	profiler = Profiler:GetProfilingGroup(group)
end

-- Keywords for the Ovale script language.
local KEYWORD = {
	["and"] = true,
	["if"] = true,
	["not"] = true,
	["or"] = true,
	["unless"] = true,
	["wait"] = true,
}

local DECLARATION_KEYWORD = {
	["AddActionIcon"] = true,
	["AddCheckBox"] = true,
	["AddFunction"] = true,
	["AddIcon"] = true,
	["AddListItem"] = true,
	["Define"] = true,
	["Include"] = true,
	["ItemInfo"] = true,
	["ItemList"] = true,
	["ScoreSpells"] = true,
	["SpellInfo"] = true,
	["SpellList"] = true,
}

local PARAMETER_KEYWORD = {
	["checkbox"] = true,
	["checkboxoff"] = true,		-- deprecated
	["checkboxon"] = true,		-- deprecated
	["glyph"] = true,
	["if_spell"] = true,
	["if_stance"] = true,
	["item"] = true,			-- deprecated
	["itemcount"] = true,
	["itemset"] = true,
	["list"] = true,			-- deprecated
	["listitem"] = true,
	["specialization"] = true,
	["stance"] = true,
	["talent"] = true,
	["wait"] = true,
}

local SPELL_AURA_KEYWORD = {
	["SpellAddBuff"] = true,
	["SpellAddDebuff"] = true,
	["SpellAddTargetBuff"] = true,
	["SpellAddTargetDebuff"] = true,
	["SpellDamageBuff"] = true,
	["SpellDamageDebuff"] = true,
}

do
	-- SpellAuraList keywords are declaration keywords.
	for keyword, value in pairs(SPELL_AURA_KEYWORD) do
		DECLARATION_KEYWORD[keyword] = value
	end
	-- All keywords are Ovale script keywords.
	for keyword, value in pairs(DECLARATION_KEYWORD) do
		KEYWORD[keyword] = value
	end
	for keyword, value in pairs(PARAMETER_KEYWORD) do
		KEYWORD[keyword] = value
	end
end

-- Table of pattern/tokenizer pairs for the Ovale script language.
local MATCHES = nil

-- Functions that are actions.
local ACTION = {
	["item"] = true,
	["macro"] = true,
	["spell"] = true,
	["texture"] = true,
}

-- Functions for accessing string databases.
local STRING_LOOKUP_FUNCTION = {
	["ItemName"] = true,
	["L"] = true,
	["SpellName"] = true,
}

-- Unary and binary operators with precedence.
local UNARY_OPERATOR = {
	["not"] = { "logical", 15 },
	["-"]   = { "arithmetic", 50 },
}
local BINARY_OPERATOR = {
	-- logical
	["and"] = { "logical", 10 },
	["or"]  = { "logical", 10 },
	-- comparison
	["!="]  = { "compare", 20 },
	["<"]   = { "compare", 20 },
	["<="]  = { "compare", 20 },
	["=="]  = { "compare", 20 },
	[">"]   = { "compare", 20 },
	[">="]  = { "compare", 20 },
	-- addition, subtraction
	["+"]   = { "arithmetic", 30 },
	["-"]   = { "arithmetic", 30 },
	-- multiplication, division, modulus
	["%"]   = { "arithmetic", 40 },
	["*"]   = { "arithmetic", 40 },
	["/"]   = { "arithmetic", 40 },
	-- exponentiation
	["^"]   = { "arithmetic", 100 },
}

-- 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 self_indent = 0
local self_outputPool = OvalePool("OvaleAST_outputPool")

local self_controlPool = OvalePool("OvaleAST_controlPool")
local self_parametersPool = OvalePool("OvaleAST_parametersPool")
local self_childrenPool = OvalePool("OvaleAST_childrenPool")
local self_pool = OvalePool("OvaleAST_pool")
do
	self_pool.Clean = function(self, node)
		if node.child then
			self_childrenPool:Release(node.child)
			node.child = nil
		end
	end
end
--</private-static-properties>

--<public-static-properties>
-- Export list of parameters keywords.
OvaleAST.PARAMETER_KEYWORD = PARAMETER_KEYWORD
--</public-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
				if value.type then
					tinsert(output, indent .. "[" .. tostring(key) .. "] =>")
				else
					tinsert(output, indent .. "[" .. tostring(key) .. "] => {")
				end
				print_r(value, indent .. "    ", done, output)
				if not value.type then
					tinsert(output, indent .. "}")
				end
			end
		else
			tinsert(output, indent .. "[" .. tostring(key) .. "] => " .. tostring(value))
		end
	end
	return output
end

-- Follow the flyweight pattern for number nodes.
local function GetNumberNode(value, nodeList, annotation)
	-- Check for a flyweight node with this exact numerical value.
	annotation.numberFlyweight = annotation.numberFlyweight or {}
	local node = annotation.numberFlyweight[value]
	if not node then
		node = OvaleAST:NewNode(nodeList)
		node.type = "value"
		node.value = value
		node.origin = 0
		node.rate = 0
		-- Store the first node with this exact numerical value in numberFlyweight.
		annotation.numberFlyweight[value] = node
	end
	return node
end

--[[---------------------------------------------
	Lexer functions (for use with OvaleLexer)
--]]---------------------------------------------
local function TokenizeComment(token)
	return yield("comment", token)
end

local function TokenizeLua(token, options)
	-- Strip off leading [[ and trailing ]].
	token = strsub(token, 3, -3)
	return yield("lua", token)
end

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 TokenizeString(token, options)
	-- Strip leading and trailing quote characters.
	if options and options.string then
		token = strsub(token, 2, -2)
	end
	return yield("string", token)
end

local function TokenizeWhitespace(token)
	return yield("space", token)
end

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

local function NoToken()
	return yield(nil)
end

do
	MATCHES = {
		{ "^%s+", TokenizeWhitespace },
		{ "^%d+%.?%d*", TokenizeNumber },
		{ "^[%a_][%w_]*", TokenizeName },
		{ "^((['\"])%2)", TokenizeString },	-- empty string
		{ [[^(['\"]).-\\%1]], TokenizeString },
		{ [[^(['\"]).-[^\]%1]], TokenizeString },
		{ "^#.-\n", TokenizeComment },
		{ "^!=", Tokenize },
		{ "^==", Tokenize },
		{ "^<=", Tokenize },
		{ "^>=", Tokenize },
		{ "^.", Tokenize },
		{ "^$", NoToken },
	}
end

local function GetTokenIterator(s)
	local exclude = { space = true, comments = true }
	do
		-- Fix some API brokenness in the Penlight lexer.
		if exclude.space then
			exclude[TokenizeWhitespace] = true
		end
		if exclude.comments then
			exclude[TokenizeComment] = true
		end
	end
	return OvaleLexer.scan(s, MATCHES, exclude)
end

-- "Flatten" a parameter value node into a string.
local function FlattenParameterValue(parameterValue)
	local value = parameterValue
	if type(parameterValue) == "table" then
		local node = parameterValue
		local isBang = false
		if node.type == "bang_value" then
			isBang = true
			node = node.child[1]
		end
		if node.type == "value" then
			value = node.value
		elseif node.type == "variable" then
			value = node.name
		elseif node.type == "string" then
			value = node.value
		end
		if isBang then
			value = "!" .. tostring(value)
		end
	end
	return value
end

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

-- Forward declarations of functions needed to implement the recursive unparser.
local UNPARSE_VISITOR = nil
local Unparse = nil
local UnparseAddCheckBox = nil
local UnparseAddFunction = nil
local UnparseAddIcon = nil
local UnparseAddListItem = nil
local UnparseComment = nil
local UnparseDefine = nil
local UnparseExpression = nil
local UnparseFunction = nil
local UnparseGroup = nil
local UnparseIf = nil
local UnparseItemInfo = nil
local UnparseList = nil
local UnparseNumber = nil
local UnparseParameterValue = nil
local UnparseParameters = nil
local UnparseScoreSpells = nil
local UnparseScript = nil
local UnparseSpellAuraList = nil
local UnparseSpellInfo = nil
local UnparseString = nil
local UnparseUnless = nil
local UnparseVariable = nil
local UnparseWait = nil

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

UnparseAddCheckBox = function(node)
	local s
	if node.rawParams and next(node.rawParams) then
		s = format("AddCheckBox(%s %s %s)", node.name, Unparse(node.description), UnparseParameters(node.rawParams))
	else
		s = format("AddCheckBox(%s %s)", node.name, Unparse(node.description))
	end
	return s
end

UnparseAddFunction = function(node)
	local s
	if node.rawParams and next(node.rawParams) then
		s = format("AddFunction %s %s %s", node.name, UnparseParameters(node.rawParams), Unparse(node.child[1]))
	else
		s = format("AddFunction %s %s", node.name, Unparse(node.child[1]))
	end
	return s
end

UnparseAddIcon = function(node)
	local s
	if node.rawParams and next(node.rawParams) then
		s = format("AddIcon %s %s", UnparseParameters(node.rawParams), Unparse(node.child[1]))
	else
		s = format("AddIcon %s", Unparse(node.child[1]))
	end
	return s
end

UnparseAddListItem = function(node)
	local s
	if node.rawParams and next(node.rawParams) then
		s = format("AddListItem(%s %s %s %s)", node.name, node.item, Unparse(node.description), UnparseParameters(node.rawParams))
	else
		s = format("AddListItem(%s %s %s)", node.name, node.item, Unparse(node.description))
	end
	return s
end

UnparseComment = function(node)
	return "#" .. node.comment
end

UnparseDefine = function(node)
	return format("Define(%s %s)", node.name, node.value)
end

UnparseExpression = function(node)
	local expression
	if node.expressionType == "unary" then
		if node.operator == "-" then
			expression = "-" .. Unparse(node.child[1])
		else
			expression = format("%s %s", node.operator, Unparse(node.child[1]))
		end
	elseif node.expressionType == "binary" then
		expression = format("%s %s %s", Unparse(node.child[1]), node.operator, Unparse(node.child[2]))
	end
	if node.left and node.right then
		local left, right = node.left, node.right
		local left = (node.left == "{") and "{ " or node.left
		local right = (node.right == "}") and " }" or node.right
		return left .. expression .. right
	else
		return expression
	end
end

UnparseFunction = function(node)
	local s
	if node.rawParams and next(node.rawParams) then
		local name
		local filter = node.rawParams.filter
		if filter == "debuff" then
			name = gsub(node.name, "^Buff", "Debuff")
		else
			name = node.name
		end
		local target = node.rawParams.target
		if target then
			s = format("%s.%s(%s)", target, name, UnparseParameters(node.rawParams))
		else
			s = format("%s(%s)", name, UnparseParameters(node.rawParams))
		end
	else
		s = format("%s()", node.name)
	end
	return s
end

UnparseGroup = function(node)
	local output = self_outputPool:Get()
	output[#output + 1] = ""
	output[#output + 1] = INDENT[self_indent] .. "{"
	self_indent = self_indent + 1
	for _, statementNode in ipairs(node.child) do
		output[#output + 1] = INDENT[self_indent] .. Unparse(statementNode)
	end
	self_indent = self_indent - 1
	output[#output + 1] = INDENT[self_indent] .. "}"

	local outputString = tconcat(output, "\n")
	self_outputPool:Release(output)
	return outputString
end

UnparseIf = function(node)
	return format("if %s %s", Unparse(node.child[1]), Unparse(node.child[2]))
end

UnparseItemInfo = function(node)
	return format("ItemInfo(%s %s)", node.name, UnparseParameters(node.rawParams))
end

UnparseList = function(node)
	return format("%s(%s %s)", node.keyword, node.name, UnparseParameters(node.rawParams))
end

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

UnparseParameterValue = function(node)
	return "!" .. Unparse(node.child[1])
end

UnparseParameters = function(parameters)
	local output = self_outputPool:Get()
	local N = #parameters
	for k = 1, N do
		output[#output + 1] = Unparse(parameters[k])
	end
	for k, v in pairs(parameters) do
		if type(k) == "number" and k <= N then
			-- Already output in previous loop.
		elseif k == "checkbox" then
			for _, name in ipairs(v) do
				output[#output + 1] = format("checkbox=%s", Unparse(name))
			end
		elseif k == "listitem" then
			for list, item in pairs(v) do
				output[#output + 1] = format("listitem=%s:%s", list, Unparse(item))
			end
		elseif type(v) == "table" then
			output[#output + 1] = format("%s=%s", k, Unparse(v))
		elseif k == "filter" or k == "target" then
			-- Skip output of "filter" or "target".
		else
			output[#output + 1] = format("%s=%s", k, v)
		end
	end
	local outputString = tconcat(output, " ")
	self_outputPool:Release(output)
	return outputString
end

UnparseScoreSpells = function(node)
	return format("ScoreSpells(%s)", UnparseParameters(node.rawParams))
end

UnparseScript = function(node)
	local output = self_outputPool:Get()
	for _, declarationNode in ipairs(node.child) do
		if declarationNode.type == "item_info" or declarationNode.type == "spell_aura_list" or declarationNode.type == "spell_info" then
			output[#output + 1] = INDENT[self_indent + 1] .. Unparse(declarationNode)
		else
			-- Add an extra blank line preceding "AddFunction" or "AddIcon".
			if declarationNode.type == "add_function" or declarationNode.type == "icon" then
				output[#output + 1] = ""
			end
			output[#output + 1] = Unparse(declarationNode)
		end
	end
	local outputString = tconcat(output, "\n")
	self_outputPool:Release(output)
	return outputString
end

UnparseSpellAuraList = function(node)
	return format("%s(%s %s)", node.keyword, node.name, UnparseParameters(node.rawParams))
end

UnparseSpellInfo = function(node)
	return format("SpellInfo(%s %s)", node.name, UnparseParameters(node.rawParams))
end

UnparseString = function(node)
	return '"' .. node.value .. '"'
end

UnparseUnless = function(node)
	return format("unless %s %s", Unparse(node.child[1]), Unparse(node.child[2]))
end

UnparseVariable = function(node)
	return node.name
end

UnparseWait = function(node)
	return format("wait %s", Unparse(node.child[1]))
end

do
	UNPARSE_VISITOR = {
		["action"] = UnparseFunction,
		["add_function"] = UnparseAddFunction,
		["arithmetic"] = UnparseExpression,
		["bang_value"] = UnparseParameterValue,
		["checkbox"] = UnparseAddCheckBox,
		["compare"] = UnparseExpression,
		["comment"] = UnparseComment,
		["custom_function"] = UnparseFunction,
		["define"] = UnparseDefine,
		["function"] = UnparseFunction,
		["group"] = UnparseGroup,
		["icon"] = UnparseAddIcon,
		["if"] = UnparseIf,
		["item_info"] = UnparseItemInfo,
		["list"] = UnparseList,
		["list_item"] = UnparseAddListItem,
		["logical"] = UnparseExpression,
		["score_spells"] = UnparseScoreSpells,
		["script"] = UnparseScript,
		["spell_aura_list"] = UnparseSpellAuraList,
		["spell_info"] = UnparseSpellInfo,
		["string"] = UnparseString,
		["unless"] = UnparseUnless,
		["value"] = UnparseNumber,
		["variable"] = UnparseVariable,
		["wait"] = UnparseWait,
	}
end

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

-- Prints the error message and the next 20 tokens from tokenStream.
local function SyntaxError(tokenStream, ...)
	Ovale:FormatPrint(...)
	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
	Ovale:Print(tconcat(context, " "))
end

-- Forward declarations of parser functions needed to implement a recursive descent parser.
local PARSE_VISITOR = nil
local Parse = nil
local ParseAddCheckBox = nil
local ParseAddFunction = nil
local ParseAddIcon = nil
local ParseAddListItem = nil
local ParseDeclaration = nil
local ParseDefine = nil
local ParseExpression = nil
local ParseFunction = nil
local ParseGroup = nil
local ParseIf = nil
local ParseInclude = nil
local ParseItemInfo = nil
local ParseList = nil
local ParseNumber = nil
local ParseParameterValue = nil
local ParseParameters = nil
local ParseParentheses = nil
local ParseScoreSpells = nil
local ParseScript = nil
local ParseSimpleExpression = nil
local ParseSpellAuraList = nil
local ParseSpellInfo = nil
local ParseString = nil
local ParseStatement = nil
local ParseUnless = nil
local ParseVariable = nil
local ParseWait = nil

Parse = function(nodeType, tokenStream, nodeList, annotation)
	local visitor = PARSE_VISITOR[nodeType]
	if not visitor then
		Ovale:FormatPrint("Unable to parse node of type '%s'.", nodeType)
	else
		return visitor(tokenStream, nodeList, annotation)
	end
end

ParseAddCheckBox = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the 'AddCheckBox' token.
	do
		local tokenType, token = tokenStream:Consume()
		if not (tokenType == "keyword" and token == "AddCheckBox") then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing ADDCHECKBOX; 'AddCheckBox' 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 ADDCHECKBOX; '(' expected.", token)
			ok = false
		end
	end
	-- Consume the checkbox name.
	local name
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType == "name" then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing ADDCHECKBOX; name expected.", token)
			ok = false
		end
	end
	-- Consume the description string.
	local descriptionNode
	if ok then
		ok, descriptionNode = ParseString(tokenStream, nodeList, annotation)
	end
	-- Consume any parameters.
	local parameters
	if ok then
		ok, parameters = ParseParameters(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 ADDCHECKBOX; ')' expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList)
		node.type = "checkbox"
		node.name = name
		node.description = descriptionNode
		node.rawParams = parameters
		annotation.parametersReference = annotation.parametersReference or {}
		annotation.parametersReference[#annotation.parametersReference + 1] = node
	end
	return ok, node
end

ParseAddFunction = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the 'AddFunction' token.
	local tokenType, token = tokenStream:Consume()
	if not (tokenType == "keyword" and token == "AddFunction") then
		SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing ADDFUNCTION; 'AddFunction' expected.", token)
		ok = false
	end
	-- Consume the function name.
	local name
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType == "name" then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing ADDFUNCTION; name expected.", token)
			ok = false
		end
	end
	-- Consume any parameters.
	local parameters
	if ok then
		ok, parameters = ParseParameters(tokenStream, nodeList, annotation)
	end
	-- Consume the body.
	local bodyNode
	if ok then
		ok, bodyNode = ParseGroup(tokenStream, nodeList, annotation)
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList, true)
		node.type = "add_function"
		node.name = name
		node.child[1] = bodyNode
		node.rawParams = parameters
		annotation.parametersReference = annotation.parametersReference or {}
		annotation.parametersReference[#annotation.parametersReference + 1] = node
		annotation.customFunction = annotation.customFunction or {}
		annotation.customFunction[name] = node
	end
	return ok, node
end

ParseAddIcon = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the 'AddIcon' token.
	local tokenType, token = tokenStream:Consume()
	if not (tokenType == "keyword" and token == "AddIcon") then
		SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing ADDICON; 'AddIcon' expected.", token)
		ok = false
	end
	-- Consume any parameters.
	local parameters
	if ok then
		ok, parameters = ParseParameters(tokenStream, nodeList, annotation)
	end
	-- Consume the body.
	local bodyNode
	if ok then
		ok, bodyNode = ParseGroup(tokenStream, nodeList, annotation)
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList, true)
		node.type = "icon"
		node.child[1] = bodyNode
		node.rawParams = parameters
		annotation.parametersReference = annotation.parametersReference or {}
		annotation.parametersReference[#annotation.parametersReference + 1] = node
	end
	return ok, node
end

ParseAddListItem = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the 'AddListItem' token.
	do
		local tokenType, token = tokenStream:Consume()
		if not (tokenType == "keyword" and token == "AddListItem") then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing ADDLISTITEM; 'AddListItem' 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 ADDLISTITEM; '(' expected.", token)
			ok = false
		end
	end
	-- Consume the list name.
	local name
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType == "name" then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing ADDLISTITEM; name expected.", token)
			ok = false
		end
	end
	-- Consume the item name.
	local item
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType == "name" then
			item = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing ADDLISTITEM; name expected.", token)
			ok = false
		end
	end
	-- Consume the description string.
	local descriptionNode
	if ok then
		ok, descriptionNode = ParseString(tokenStream, nodeList, annotation)
	end
	-- Consume any parameters.
	local parameters
	if ok then
		ok, parameters = ParseParameters(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 ADDLISTITEM; ')' expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList)
		node.type = "list_item"
		node.name = name
		node.item = item
		node.description = descriptionNode
		node.rawParams = parameters
		annotation.parametersReference = annotation.parametersReference or {}
		annotation.parametersReference[#annotation.parametersReference + 1] = node
	end
	return ok, node
end

ParseDeclaration = function(tokenStream, nodeList, annotation)
	local ok = true
	local node
	local tokenType, token = tokenStream:Peek()
	if tokenType == "keyword" and DECLARATION_KEYWORD[token] then
		if token == "AddCheckBox" then
			ok, node = ParseAddCheckBox(tokenStream, nodeList, annotation)
		elseif token == "AddFunction" then
			ok, node = ParseAddFunction(tokenStream, nodeList, annotation)
		elseif token == "AddIcon" then
			ok, node = ParseAddIcon(tokenStream, nodeList, annotation)
		elseif token == "AddListItem" then
			ok, node = ParseAddListItem(tokenStream, nodeList, annotation)
		elseif token == "Define" then
			ok, node = ParseDefine(tokenStream, nodeList, annotation)
		elseif token == "Include" then
			ok, node = ParseInclude(tokenStream, nodeList, annotation)
		elseif token == "ItemInfo" then
			ok, node = ParseItemInfo(tokenStream, nodeList, annotation)
		elseif token == "ItemList" then
			ok, node = ParseList(tokenStream, nodeList, annotation)
		elseif token == "ScoreSpells" then
			ok, node = ParseScoreSpells(tokenStream, nodeList, annotation)
		elseif SPELL_AURA_KEYWORD[token] then
			ok, node = ParseSpellAuraList(tokenStream, nodeList, annotation)
		elseif token == "SpellInfo" then
			ok, node = ParseSpellInfo(tokenStream, nodeList, annotation)
		elseif token == "SpellList" then
			ok, node = ParseList(tokenStream, nodeList, annotation)
		end
	else
		SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing DECLARATION; declaration keyword expected.", token)
		tokenStream:Consume()
		ok = false
	end
	return ok, node
end

ParseDefine = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the 'Define' token.
	do
		local tokenType, token = tokenStream:Consume()
		if not (tokenType == "keyword" and token == "Define") then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing DEFINE; 'Define' 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 DEFINE; '(' expected.", token)
			ok = false
		end
	end
	-- Consume the variable name.
	local name
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType == "name" then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing DEFINE; name expected.", token)
			ok = false
		end
	end
	-- Consume the value.
	local value
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType == "-" then
			-- Negative number.
			tokenType, token = tokenStream:Consume()
			if tokenType == "number" then
				-- Elide the unary negation operator into the number.
				value = -1 * tonumber(token)
			else
				SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing DEFINE; number expected after '-'.", token)
				ok = false
			end
		elseif tokenType == "number" then
			value = tonumber(token)
		elseif tokenType == "string" then
			value = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing DEFINE; number or string expected.", token)
			ok = false
		end
	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 DEFINE; ')' expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList)
		node.type = "define"
		node.name = name
		node.value = value
		annotation.definition = annotation.definition or {}
		annotation.definition[name] = value
	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]
				tokenStream:Consume()
				local operator = token
				local rhsNode
				ok, rhsNode = ParseExpression(tokenStream, nodeList, annotation, precedence)
				if ok then
					if operator == "-" and rhsNode.type == "value" then
						-- Elide the unary negation operator into the number.
						local value = -1 * rhsNode.value
						node = GetNumberNode(value, nodeList, annotation)
					else
						node = OvaleAST:NewNode(nodeList, true)
						node.type = opType
						node.expressionType = "unary"
						node.operator = operator
						node.precedence = precedence
						node.child[1] = rhsNode
					end
				end
			else
				ok, node = ParseSimpleExpression(tokenStream, nodeList, annotation)
			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]
				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 = OvaleAST:NewNode(nodeList, true)
						node.type = opType
						node.expressionType = "binary"
						node.operator = operator
						node.precedence = precedence
						node.child[1] = lhsNode
						node.child[2] = rhsNode
					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, lowername
	-- Consume the name.
	do
		local tokenType, token = tokenStream:Consume()
		if tokenType == "name" then
			name = token
			lowername = strlower(name)
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing FUNCTION; name expected.", token)
			ok = false
		end
	end
	-- Check for <target>.<function>.
	local target
	if ok then
		local tokenType, token = tokenStream:Peek()
		if tokenType == "." then
			target = name
			tokenType, token = tokenStream:Consume(2)
			if tokenType == "name" then
				name = token
				lowername = strlower(name)
			else
				SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing FUNCTION; name expected.", token)
				ok = false
			end
		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 any function parameters.
	local parameters
	if ok then
		ok, parameters = ParseParameters(tokenStream, nodeList, annotation)
	end
	-- Verify that an action has at least one fixed parameter.
	if ok and ACTION[lowername] and not parameters[1] then
		SyntaxError(tokenStream, "Syntax error: action '%s' requires at least one fixed parameter.", name)
		ok = false
	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
	if ok then
		-- Parse the function name.
		if not parameters.target then
			-- Auto-set the target if the function name starts with "Target".
			if strsub(lowername, 1, 6) == "target" then
				parameters.target = "target"
				lowername = strsub(lowername, 7)
				name = strsub(name, 7)
			end
		end
		if not parameters.filter then
			-- Auto-set the aura filter if the function name starts with "Debuff" or "Buff".
			if strsub(lowername, 1, 6) == "debuff" then
				parameters.filter = "debuff"
			elseif strsub(lowername, 1, 4) == "buff" then
				parameters.filter = "buff"
			elseif strsub(lowername, 1, 11) == "otherdebuff" then
				parameters.filter = "debuff"
			elseif strsub(lowername, 1, 9) == "otherbuff" then
				parameters.filter = "buff"
			end
		end
		-- Set the target if given in a prefix.
		if target then
			parameters.target = target
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList)
		node.name = name
		node.lowername = lowername
		if ACTION[lowername] then
			node.type = "action"
			-- Built-in functions are case-insensitive.
			node.func = lowername
		elseif STRING_LOOKUP_FUNCTION[name] then
			node.type = "function"
			-- String-lookup functions are case-sensitive.
			node.func = name
		elseif OvaleCondition:IsCondition(lowername) then
			node.type = "function"
			-- Built-in functions are case-insensitive.
			node.func = lowername
		else
			node.type = "custom_function"
			-- Script-defined functions are case-sensitive.
			node.func = name
		end
		node.rawParams = parameters
		node.asString = UnparseFunction(node)
		annotation.parametersReference = annotation.parametersReference or {}
		annotation.parametersReference[#annotation.parametersReference + 1] = node
		annotation.functionCall = annotation.functionCall or {}
		annotation.functionCall[node.func] = true
		annotation.functionReference = annotation.functionReference or {}
		annotation.functionReference[#annotation.functionReference + 1] = node
	end
	return ok, node
end

ParseGroup = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the left brace.
	do
		local tokenType, token = tokenStream:Consume()
		if tokenType ~= "{" then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing GROUP; '{' expected.", token)
			ok = false
		end
	end
	-- Consume any statements up to the matching right brace.
	local child = self_childrenPool:Get()
	local tokenType, token = tokenStream:Peek()
	while ok and tokenType and tokenType ~= "}" do
		local statementNode
		ok, statementNode = ParseStatement(tokenStream, nodeList, annotation)
		if ok then
			child[#child + 1] = statementNode
			tokenType, token = tokenStream:Peek()
		else
			break
		end
	end
	-- Consume the right brace.
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType ~= "}" then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing GROUP; '}' expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList)
		node.type = "group"
		node.child = child
	else
		self_childrenPool:Release(child)
	end
	return ok, node
end

ParseIf = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the 'if' token.
	do
		local tokenType, token = tokenStream:Consume()
		if not (tokenType == "keyword" and token == "if") then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing IF; 'if' expected.", token)
			ok = false
		end
	end
	-- Consume the condition and body.
	local conditionNode, bodyNode
	if ok then
		ok, conditionNode = ParseExpression(tokenStream, nodeList, annotation)
	end
	if ok then
		ok, bodyNode = ParseStatement(tokenStream, nodeList, annotation)
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList, true)
		node.type = "if"
		node.child[1] = conditionNode
		node.child[2] = bodyNode
	end
	return ok, node
end

ParseInclude = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the 'Include' token.
	do
		local tokenType, token = tokenStream:Consume()
		if not (tokenType == "keyword" and token == "Include") then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing INCLUDE; 'Include' 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 INCLUDE; '(' expected.", token)
			ok = false
		end
	end
	-- Consume the script name.
	local name
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType == "name" then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing INCLUDE; script name expected.", token)
			ok = false
		end
	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 INCLUDE; ')' expected.", token)
			ok = false
		end
	end
	-- Get the code associated with the script name.
	local code = OvaleScripts.script[name] and OvaleScripts.script[name].code
	if not code then
		Ovale:FormatPrint("Script '%s' not found when parsing INCLUDE.", name)
		ok = false
	end
	-- Create the AST node.
	local node
	if ok then
		local includeTokenStream = OvaleLexer(name, GetTokenIterator(code))
		ok, node = ParseScript(includeTokenStream, nodeList, annotation)
		includeTokenStream:Release()
	end
	return ok, node
end

ParseItemInfo = function(tokenStream, nodeList, annotation)
	local ok = true
	local name, lowername
	-- Consume the 'ItemInfo' token.
	do
		local tokenType, token = tokenStream:Consume()
		if not (tokenType == "keyword" and token == "ItemInfo") then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing ITEMINFO; 'ItemInfo' 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 ITEMINFO; '(' expected.", token)
			ok = false
		end
	end
	-- Consume the item ID.
	local itemId, name
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType == "number" then
			spellId = token
		elseif tokenType == "name" then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing ITEMINFO; number or name expected.", token)
			ok = false
		end
	end
	-- Consume any ItemInfo parameters.
	local parameters
	if ok then
		ok, parameters = ParseParameters(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 ITEMINFO; ')' expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList)
		node.type = "item_info"
		node.itemId = itemId
		node.name = name
		node.rawParams = parameters
		annotation.parametersReference = annotation.parametersReference or {}
		annotation.parametersReference[#annotation.parametersReference + 1] = node
		if name then
			annotation.nameReference = annotation.nameReference or {}
			annotation.nameReference[#annotation.nameReference + 1] = node
		end
	end
	return ok, node
end

ParseList = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the list token.
	local keyword
	do
		local tokenType, token = tokenStream:Consume()
		if tokenType == "keyword" and (token == "ItemList" or token == "SpellList") then
			keyword = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing LIST; keyword 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 LIST; '(' expected.", token)
			ok = false
		end
	end
	-- Consume the list name.
	local name
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType == "name" then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing LIST; name expected.", token)
			ok = false
		end
	end
	-- Consume the list.
	local parameters
	if ok then
		ok, parameters = ParseParameters(tokenStream, nodeList, annotation, true)
	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 LIST; ')' expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList)
		node.type = "list"
		node.keyword = keyword
		node.name = name
		node.rawParams = parameters
		annotation.parametersReference = annotation.parametersReference or {}
		annotation.parametersReference[#annotation.parametersReference + 1] = node
	end
	return ok, node
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 = GetNumberNode(value, nodeList, annotation)
	end
	return ok, node
end

ParseParameterValue = function(tokenStream, nodeList, annotation)
	local ok = true
	local isBang = false
	local tokenType, token = tokenStream:Peek()
	if tokenType == "!" then
		isBang = true
		-- Consume the '!' token.
		tokenStream:Consume()
	end
	local expressionNode
	tokenType, token = tokenStream:Peek()
	if tokenType == "(" or tokenType == "-" then
		ok, expressionNode = ParseExpression(tokenStream, nodeList, annotation)
	else
		ok, expressionNode = ParseSimpleExpression(tokenStream, nodeList, annotation)
	end
	local node
	if isBang then
		node = OvaleAST:NewNode(nodeList, true)
		node.type = "bang_value"
		node.child[1] = expressionNode
	else
		node = expressionNode
	end
	return ok, node
end

ParseParameters = function(tokenStream, nodeList, annotation, isList)
	local ok = true
	local parameters = self_parametersPool:Get()
	while ok do
		local tokenType, token = tokenStream:Peek()
		if tokenType then
			local name, node
			if tokenType == "name" then
				ok, node = ParseVariable(tokenStream, nodeList, annotation)
				if ok then
					name = node.name
				end
			elseif tokenType == "number" then
				ok, node = ParseNumber(tokenStream, nodeList, annotation)
				if ok then
					name = node.value
				end
			elseif PARAMETER_KEYWORD[token] then
				if isList then
					SyntaxError(tokenStream, "Syntax error: unexpected keyword '%s' when parsing PARAMETERS; simple expression expected.", token)
					ok = false
				else
					tokenStream:Consume()
					name = token
				end
			else
				break
			end
			-- Check if this is a bare value or the start of a "name=value" pair.
			if ok and name then
				tokenType, token = tokenStream:Peek()
				if tokenType == "=" then
					-- Consume the '=' token.
					tokenStream:Consume()
					if name == "checkbox" or name == "listitem" then
						local control = parameters[name] or self_controlPool:Get()
						if name == "checkbox" then
							-- Get the checkbox name.
							ok, node = ParseParameterValue(tokenStream, nodeList, annotation)
							if ok then
								control[#control + 1] = node
							end
						else -- if name == "listitem" then
							-- Consume the list name.
							tokenType, token = tokenStream:Consume()
							local list
							if tokenType == "name" then
								list = token
							else
								SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing PARAMETERS; name expected.", token)
								ok = false
							end
							if ok then
								-- Consume the ':' token.
								tokenType, token = tokenStream:Consume()
								if tokenType ~= ":" then
									SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing PARAMETERS; ':' expected.", token)
									ok = false
								end
							end
							if ok then
								-- Consume the list item.
								ok, node = ParseParameterValue(tokenStream, nodeList, annotation)
							end
							if ok then
								control[list] = node
							end
						end
						if not parameters[name] then
							parameters[name] = control
							annotation.controlList = annotation.controlList or {}
							annotation.controlList[#annotation.controlList + 1] = control
						end
					else
						-- Get the value.
						ok, node = ParseParameterValue(tokenStream, nodeList, annotation)
						parameters[name] = node
					end
				else
					parameters[#parameters + 1] = node
				end
			end
		else
			break
		end
	end
	if ok then
		annotation.parametersList = annotation.parametersList or {}
		annotation.parametersList[#annotation.parametersList + 1] = parameters
	else
		parameters = nil
	end
	return ok, parameters
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

ParseScoreSpells = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the 'ScoreSpells' token.
	do
		local tokenType, token = tokenStream:Consume()
		if not (tokenType == "keyword" and token == "ScoreSpells") then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing SCORESPELLS; 'ScoreSpells' 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 SCORESPELLS; '(' expected.", token)
			ok = false
		end
	end
	-- Consume the list of spells.
	local parameters
	if ok then
		ok, parameters = ParseParameters(tokenStream, nodeList, annotation, true)
	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 SCORESPELLS; ')' expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList)
		node.type = "score_spells"
		node.rawParams = parameters
		annotation.parametersReference = annotation.parametersReference or {}
		annotation.parametersReference[#annotation.parametersReference + 1] = node
	end
	return ok, node
end

ParseScript = function(tokenStream, nodeList, annotation)
	profiler.Start("OvaleAST_ParseScript")
	local ok = true
	-- Consume each declaration.
	local child = self_childrenPool:Get()
	while ok do
		local tokenType, token = tokenStream:Peek()
		if tokenType then
			local declarationNode
			ok, declarationNode = ParseDeclaration(tokenStream, nodeList, annotation)
			if ok then
				if declarationNode.type == "script" then
					for _, node in ipairs(declarationNode.child) do
						child[#child + 1] = node
					end
					-- All "script" nodes are standalone and need to be explicitly released.
					self_pool:Release(declarationNode)
				else
					child[#child + 1] = declarationNode
				end
			end
		else
			break
		end
	end
	-- Create the AST node.
	local ast
	if ok then
		-- Create a standalone AST node.
		ast = OvaleAST:NewNode()
		ast.type = "script"
		ast.child = child
	else
		self_childrenPool:Release(child)
	end
	profiler.Stop("OvaleAST_ParseScript")
	return ok, ast
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 == "name" then
		tokenType, token = tokenStream:Peek(2)
		if tokenType == "." or tokenType == "(" then
			ok, node = ParseFunction(tokenStream, nodeList, annotation)
		else
			ok, node = ParseVariable(tokenStream, nodeList, annotation)
		end
	elseif tokenType == "(" or tokenType == "{" then
		ok, node = ParseParentheses(tokenStream, nodeList, annotation)
	else
		tokenStream:Consume()
		SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing SIMPLE EXPRESSION", token)
		ok = false
	end
	return ok, node
end

ParseSpellAuraList = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the keyword token.
	local keyword
	do
		local tokenType, token = tokenStream:Consume()
		if tokenType == "keyword" and SPELL_AURA_KEYWORD[token] then
			keyword = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing SPELLAURALIST; keyword 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 SPELLAURALIST; '(' expected.", token)
			ok = false
		end
	end
	-- Consume the spell ID.
	local spellId, name
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType == "number" then
			spellId = token
		elseif tokenType == "name" then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing SPELLAURALIST; number or name expected.", token)
			ok = false
		end
	end
	-- Consume any parameters.
	local parameters
	if ok then
		ok, parameters = ParseParameters(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 SPELLAURALIST; ')' expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList)
		node.type = "spell_aura_list"
		node.keyword = keyword
		node.spellId = spellId
		node.name = name
		node.rawParams = parameters
		annotation.parametersReference = annotation.parametersReference or {}
		annotation.parametersReference[#annotation.parametersReference + 1] = node
		if name then
			annotation.nameReference = annotation.nameReference or {}
			annotation.nameReference[#annotation.nameReference + 1] = node
		end
	end
	return ok, node
end

ParseSpellInfo = function(tokenStream, nodeList, annotation)
	local ok = true
	local name, lowername
	-- Consume the 'SpellInfo' token.
	do
		local tokenType, token = tokenStream:Consume()
		if not (tokenType == "keyword" and token == "SpellInfo") then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing SPELLINFO; 'SpellInfo' 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 SPELLINFO; '(' expected.", token)
			ok = false
		end
	end
	-- Consume the spell ID.
	local spellId, name
	if ok then
		local tokenType, token = tokenStream:Consume()
		if tokenType == "number" then
			spellId = token
		elseif tokenType == "name" then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing SPELLINFO; number or name expected.", token)
			ok = false
		end
	end
	-- Consume any SpellInfo parameters.
	local parameters
	if ok then
		ok, parameters = ParseParameters(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 SPELLINFO; ')' expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList)
		node.type = "spell_info"
		node.spellId = spellId
		node.name = name
		node.rawParams = parameters
		annotation.parametersReference = annotation.parametersReference or {}
		annotation.parametersReference[#annotation.parametersReference + 1] = node
		if name then
			annotation.nameReference = annotation.nameReference or {}
			annotation.nameReference[#annotation.nameReference + 1] = node
		end
	end
	return ok, node
end

ParseStatement = function(tokenStream, nodeList, annotation)
	local ok = true
	local node
	local tokenType, token = tokenStream:Peek()
	if tokenType then
		local parser
		if token == "{" then
			-- Find the matching '}' and inspect the next token to see if this is an expression or a group.
			local i = 1
			local count = 0
			while tokenType do
				if token == "{" then
					count = count + 1
				elseif token == "}" then
					count = count - 1
				end
				i = i + 1
				tokenType, token = tokenStream:Peek(i)
				if count == 0 then
					break
				end
			end
			if tokenType then
				if BINARY_OPERATOR[token] then
					ok, node = ParseExpression(tokenStream, nodeList, annotation)
				else
					ok, node = ParseGroup(tokenStream, nodeList, annotation)
				end
			else
				SyntaxError(tokenStream, "Syntax error: unexpected end of script.")
			end
		elseif token == "if" then
			ok, node = ParseIf(tokenStream, nodeList, annotation)
		elseif token == "unless" then
			ok, node = ParseUnless(tokenStream, nodeList, annotation)
		elseif token == "wait" then
			ok, node = ParseWait(tokenStream, nodeList, annotation)
		else
			ok, node = ParseExpression(tokenStream, nodeList, annotation)
		end
	end
	return ok, node
end

ParseString = function(tokenStream, nodeList, annotation)
	local ok = true
	local node
	local value
	if ok then
		local tokenType, token = tokenStream:Peek()
		if tokenType == "string" then
			value = token
			tokenStream:Consume()
		elseif tokenType == "name" then
			if STRING_LOOKUP_FUNCTION[token] then
				ok, node = ParseFunction(tokenStream, nodeList, annotation)
			else
				value = token
				tokenStream:Consume()
			end
		else
			tokenStream:Consume()
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing STRING; string, variable, or function expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	if ok then
		if not node then
			node = OvaleAST:NewNode(nodeList)
			node.type = "string"
			node.value = value
		end
		annotation.stringReference = annotation.stringReference or {}
		annotation.stringReference[#annotation.stringReference + 1] = node
	end
	return ok, node
end

ParseUnless = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the 'unless' token.
	do
		local tokenType, token = tokenStream:Consume()
		if not (tokenType == "keyword" and token == "unless") then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing UNLESS; 'unless' expected.", token)
			ok = false
		end
	end
	-- Consume the condition and body.
	local conditionNode, bodyNode
	if ok then
		ok, conditionNode = ParseExpression(tokenStream, nodeList, annotation)
	end
	if ok then
		ok, bodyNode = ParseStatement(tokenStream, nodeList, annotation)
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList, true)
		node.type = "unless"
		node.child[1] = conditionNode
		node.child[2] = bodyNode
	end
	return ok, node
end

ParseVariable = function(tokenStream, nodeList, annotation)
	local ok = true
	local name
	-- Consume the variable name.
	do
		local tokenType, token = tokenStream:Consume()
		if tokenType == "name" then
			name = token
		else
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing VARIABLE; name expected.", token)
			ok = false
		end
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList)
		node.type = "variable"
		node.name = name
		annotation.nameReference = annotation.nameReference or {}
		annotation.nameReference[#annotation.nameReference + 1] = node
	end
	return ok, node
end

ParseWait = function(tokenStream, nodeList, annotation)
	local ok = true
	-- Consume the 'wait' token.
	do
		local tokenType, token = tokenStream:Consume()
		if not (tokenType == "keyword" and token == "wait") then
			SyntaxError(tokenStream, "Syntax error: unexpected token '%s' when parsing WAIT; 'wait' expected.", token)
			ok = false
		end
	end
	-- Consume the statement body.
	local bodyNode
	if ok then
		ok, bodyNode = ParseStatement(tokenStream, nodeList, annotation)
	end
	-- Create the AST node.
	local node
	if ok then
		node = OvaleAST:NewNode(nodeList, true)
		node.type = "wait"
		node.child[1] = bodyNode
	end
	return ok, node
end

do
	PARSE_VISITOR = {
		["action"] = ParseFunction,
		["add_function"] = ParseAddFunction,
		["arithmetic"] = ParseExpression,
		["bang_value"] = ParseParameterValue,
		["checkbox"] = ParseAddCheckBox,
		["compare"] = ParseExpression,
		["comment"] = ParseComment,
		["custom_function"] = ParseFunction,
		["define"] = ParseDefine,
		["expression"] = ParseExpression,
		["function"] = ParseFunction,
		["group"] = ParseGroup,
		["icon"] = ParseAddIcon,
		["if"] = ParseIf,
		["item_info"] = ParseItemInfo,
		["list"] = ParseList,
		["list_item"] = ParseAddListItem,
		["logical"] = ParseExpression,
		["score_spells"] = ParseScoreSpells,
		["script"] = ParseScript,
		["spell_aura_list"] = ParseSpellAuraList,
		["spell_info"] = ParseSpellInfo,
		["string"] = ParseString,
		["unless"] = ParseUnless,
		["value"] = ParseNumber,
		["variable"] = ParseVariable,
		["wait"] = ParseWait,
	}
end
--</private-static-methods>

--<public-static-methods>
function OvaleAST:OnInitialize()
	-- Resolve module dependencies.
	OvaleCondition = Ovale.OvaleCondition
	OvaleLexer = Ovale.OvaleLexer
	OvaleScripts = Ovale.OvaleScripts
end

function OvaleAST:Debug()
	self_pool:Debug()
	self_parametersPool:Debug()
	self_controlPool:Debug()
	self_childrenPool:Debug()
	self_outputPool:Debug()
end

-- Get a new node from the pool and save it in the nodes array.
function OvaleAST: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

function OvaleAST:NodeToString(node)
	local output = print_r(node)
	return tconcat(output, "\n")
end

function OvaleAST:ReleaseAnnotation(annotation)
	if annotation.controlList then
		for _, control in ipairs(annotation.controlList) do
			self_controlPool:Release(control)
		end
	end
	if annotation.parametersList then
		for _, parameters in ipairs(annotation.parametersList) do
			self_parametersPool:Release(parameters)
		end
	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
end

function OvaleAST:Release(ast)
	if ast.annotation then
		self:ReleaseAnnotation(ast.annotation)
		ast.annotation = nil
	end
	self_pool:Release(ast)
end

function OvaleAST:ParseCode(nodeType, code, nodeList, annotation)
	nodeList = nodeList or {}
	annotation = annotation or {}
	local tokenStream = OvaleLexer("Ovale", GetTokenIterator(code))
	local ok, node = Parse(nodeType, tokenStream, nodeList, annotation)
	tokenStream:Release()
	return node, nodeList, annotation
end

function OvaleAST:ParseScript(name, options)
	-- Get the code associated with the script name.
	local code = OvaleScripts.script[name] and OvaleScripts.script[name].code
	local ast
	if code then
		options = options or { verify = true }
		-- Annotation table for the AST.
		local annotation = {
			nodeList = {},
			verify = options.verify,
		}
		ast = self:ParseCode("script", code, annotation.nodeList, annotation)
		if ast then
			ast.annotation = annotation
			self:PropagateConstants(ast)
			self:PropagateStrings(ast)
			self:FlattenParameters(ast)
			self:VerifyFunctionCalls(ast)
		else
			-- Create a dummy node to properly release resources.
			ast = self:NewNode()
			ast.annotation = annotation
			self:Release(ast)
			ast = nil
		end
	end
	return ast
end

function OvaleAST:Unparse(node)
	return Unparse(node)
end

-- Replaces variables with their defined values.
function OvaleAST:PropagateConstants(ast)
	profiler.Start("OvaleAST_PropagateConstants")
	if ast.annotation then
		local dictionary = ast.annotation.definition
		if dictionary and ast.annotation.nameReference then
			for _, node in ipairs(ast.annotation.nameReference) do
				if node.type == "item_info" and node.name then
					local itemId = dictionary[node.name]
					if itemId then
						node.itemId = itemId
					end
				elseif (node.type == "spell_aura_list" or node.type == "spell_info") and node.name then
					local spellId = dictionary[node.name]
					if spellId then
						node.spellId = spellId
					end
				elseif node.type == "variable" then
					local name = node.name
					local value = dictionary[name]
					if value then
						-- Convert to a value node.
						node.previousType = "variable"
						node.type = "value"
						node.value = value
						node.origin = 0
						node.rate = 0
					end
				end
			end
		end
	end
	profiler.Stop("OvaleAST_PropagateConstants")
end

-- Replaces variables and string-lookup function calls with string values.
function OvaleAST:PropagateStrings(ast)
	profiler.Start("OvaleAST_PropagateStrings")
	if ast.annotation and ast.annotation.stringReference then
		for _, node in ipairs(ast.annotation.stringReference) do
			if node.type == "string" then
				-- do nothing
			elseif node.type == "variable" then
				local value = node.name
				-- Convert to a string node.
				node.previousType = "variable"
				node.type = "string"
				node.value = value
			elseif node.type == "function" then
				-- Get the lookup key for the string database.
				local key = node.rawParams[1]
				if type(key) == "table" then
					if key.type == "value" then
						key = key.value
					elseif key.type == "variable" then
						key = key.name
					elseif key.type == "string" then
						key = key.value
					end
				end
				local value
				if key then
					local name = node.name
					if name == "ItemName" then
						value = API_GetItemInfo(key)
					elseif name == "L" then
						value = L[key]
					elseif name == "SpellName" then
						value = API_GetSpellInfo(key)
					end
				end
				if value then
					-- Convert to a string node.
					node.previousType = "function"
					node.type = "string"
					node.value = value
					node.key = key
				end
			end
		end
	end
	profiler.Stop("OvaleAST_PropagateStrings")
end

-- "Flattens" parameter tables by replacing table values with the bare numerical or string values
-- so that the parameter table can be used directly by script conditions.
function OvaleAST:FlattenParameters(ast)
	profiler.Start("OvaleAST_FlattenParameters")
	local annotation = ast.annotation
	if annotation and annotation.parametersReference then
		local dictionary = annotation.definition
		for _, node in ipairs(annotation.parametersReference) do
			if node.rawParams then
				local parameters = self_parametersPool:Get()
				for key, value in pairs(node.rawParams) do
					-- Lookup the key.
					if key == "checkbox" or key == "listitem" then
						local control = parameters[key] or self_controlPool:Get()
						if key == "checkbox" then
							for i, name in ipairs(value) do
								control[i] = FlattenParameterValue(name)
							end
						else -- if key == "listitem" then
							for list, item in pairs(value) do
								control[list] = FlattenParameterValue(item)
							end
						end
						if not parameters[key] then
							parameters[key] = control
							annotation.controlList = annotation.controlList or {}
							annotation.controlList[#annotation.controlList + 1] = control
						end
					elseif key == "checkboxon" or key == "checkboxoff" then
						-- Deprecated: checkboxon
						-- Deprecated: checkboxoff
						local control = parameters.checkbox or self_controlPool:Get()
						local name = FlattenParameterValue(value)
						if key == "checkboxon" then
							Ovale:OneTimeMessage("Warning: 'checkboxon=%s' is deprecated; use 'checkbox=%s' instead.", name, name)
							control[#control + 1] = name
						elseif key == "checkboxoff" then
							Ovale:OneTimeMessage("Warning: 'checkboxoff=%s' is deprecated; use 'checkbox=!%s' instead.", name, name)
							control[#control + 1] = "!" .. name
						end
						if not parameters.checkbox then
							parameters.checkbox = control
							annotation.controlList = annotation.controlList or {}
							annotation.controlList[#annotation.controlList + 1] = control
						end
					elseif key == "list" or key == "item" then
						-- Deprecated: list
						-- Deprecated: item
						if key == "list" and node.rawParams.item then
							local control = parameters.listitem or self_controlPool:Get()
							local list = FlattenParameterValue(value)
							local item = FlattenParameterValue(node.rawParams.item)
							Ovale:OneTimeMessage("Warning: 'list=%s item=%s' is deprecated; use 'listitem=%s:%s' instead.", list, item, list, item)
							control[list] = item
							if not parameters.listitem then
								parameters.listitem = control
								annotation.controlList = annotation.controlList or {}
								annotation.controlList[#annotation.controlList + 1] = control
							end
						end
					elseif key == "mastery" then
						-- Deprecated: mastery -> specialization
						local spec = FlattenParameterValue(value)
						Ovale:OneTimeMessage("Warning: 'mastery=%s' is deprecated; use 'specialization=%s' instead.", spec, spec)
						parameters.specialization = spec
					else
						if type(key) ~= "number" and dictionary and dictionary[key] then
							key = dictionary[key]
						end
						parameters[key] = FlattenParameterValue(value)
					end
				end
				node.params = parameters
				annotation.parametersList = annotation.parametersList or {}
				annotation.parametersList[#annotation.parametersList + 1] = parameters

				-- Save a flattened string representation of the parameters.
				local output = self_outputPool:Get()
				local N = #parameters
				for k = 1, N do
					output[k] = parameters[k]
				end
				for k, v in pairs(parameters) do
					if type(k) == "number" and k <= N then
						-- Already output in previous loop.
					elseif k == "checkbox" then
						for _, name in ipairs(v) do
							output[#output + 1] = format("checkbox=%s", name)
						end
					elseif k == "listitem" then
						for list, item in ipairs(v) do
							output[#output + 1] = format("listitem=%s:%s", list, item)
						end
					else
						output[#output + 1] = format("%s=%s", k, v)
					end
				end
				node.paramsAsString = tconcat(output, " ")
				self_outputPool:Release(output)
			end
		end
	end
	profiler.Stop("OvaleAST_FlattenParameters")
end

-- Verify that all functions called within the script are known.
function OvaleAST:VerifyFunctionCalls(ast)
	profiler.Start("OvaleAST_VerifyFunctionCalls")
	if ast.annotation and ast.annotation.verify then
		local customFunction = ast.annotation.customFunction
		local functionCall = ast.annotation.functionCall
		if functionCall then
			for name in pairs(functionCall) do
				if ACTION[name] then
					-- Function call is an action.
				elseif STRING_LOOKUP_FUNCTION[name] then
					-- Function call is a string-lookup function.
				elseif OvaleCondition:IsCondition(name) then
					-- Function call is a registered script condition.
				elseif customFunction and customFunction[name] then
					-- Function call is a script-defined function (via AddFunction).
				else
					Ovale:Errorf("unknown function '%s'.", name)
				end
			end
		end
	end
	profiler.Stop("OvaleAST_VerifyFunctionCalls")
end

function OvaleAST:Optimize(ast)
	self:CommonFunctionElimination(ast)
end

--[[----------------------------------------------------------------------------
	Common Function Elimination

	This is an optimizing transformation of the AST that globally replaces
	references to function nodes to the node of the first function call made
	with identical parameters.
--]]----------------------------------------------------------------------------
function OvaleAST:CommonFunctionElimination(ast)
	profiler.Start("OvaleAST_CommonFunctionElimination")
	if ast.annotation then
		-- Hash all of the function calls.
		if ast.annotation.functionReference then
			local functionHash = ast.annotation.functionHash or {}
			for _, node in ipairs(ast.annotation.functionReference) do
				if node.params then
					local parameters = node.params
					local N = #parameters

					local output = self_outputPool:Get()
					output[#output + 1] = node.name
					output[#output + 1] = "("
					for k, v in ipairs(parameters) do
						output[#output + 1] = v
					end
					for k, v in pairs(parameters) do
						if type(k) == "number" and k <= N then
							-- Already output in previous loop.
						else
							output[#output + 1] = format("%s=%s", k, v)
						end
					end
					output[#output + 1] = ")"
					local hash = tconcat(output, " ")
					self_outputPool:Release(output)

					node.functionHash = hash
					functionHash[hash] = functionHash[hash] or node
				end
			end
			ast.annotation.functionHash = functionHash
		end

		-- Walk the AST and search for child nodes that are function nodes and
		-- replace with a reference to the hashed node.
		if ast.annotation.functionHash and ast.annotation.nodeList then
			local functionHash = ast.annotation.functionHash
			for _, node in ipairs(ast.annotation.nodeList) do
				if node.child then
					for k, childNode in ipairs(node.child) do
						if childNode.functionHash then
							node.child[k] = functionHash[childNode.functionHash]
						end
					end
				end
			end
		end
	end
	profiler.Stop("OvaleAST_CommonFunctionElimination")
end
--</public-static-methods>