/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include #include #include #include #include #include #include namespace ExpressionEvaluation { namespace StructuralParsers { class InternalExpressionElementParser : public ExpressionElementParser { public: AZ_CLASS_ALLOCATOR(InternalExpressionElementParser, AZ::SystemAllocator, 0); InternalExpressionElementParser() // Just consume spaces, tabs, or commas : m_whiteSpaceRegex(R"(^[ ,]+)") { } ExpressionParserId GetParserId() const { return InternalTypes::Interfaces::InternalParser; } ParseResult ParseElement(const AZStd::string& inputText, size_t offset) const { ParseResult result; AZStd::smatch match; if (AZStd::regex_search(&inputText.at(offset), match, m_whiteSpaceRegex)) { result.m_charactersConsumed = match[0].length(); } else if (inputText.at(offset) == '(') { result.m_charactersConsumed = 1; result.m_element.m_id = InternalTypes::OpenParen; result.m_element.m_priority = std::numeric_limits::min(); } else if (inputText.at(offset) == ')') { result.m_charactersConsumed = 1; result.m_element.m_id = InternalTypes::CloseParen; result.m_element.m_priority = std::numeric_limits::min(); } return result; } void EvaluateToken(const ElementInformation& parseResult, ExpressionResultStack& evaluationStack) const { AZ_UNUSED(parseResult); AZ_UNUSED(evaluationStack); AZ_Error("ExpressionEngine", false, "IgnoredSymbolParser should not be used to evaluate tokens."); } private: AZStd::regex m_whiteSpaceRegex; }; } //////////////////////////////////////// // ExpressionEvaluationSystemComponent //////////////////////////////////////// static bool ExpressionTokenConverter(AZ::SerializeContext& serializeContext, AZ::SerializeContext::DataElementNode& rootElement) { if (rootElement.GetVersion() < 1) { AZ::Crc32 interfaceId; rootElement.GetChildData(AZ_CRC("InterfaceId", 0x221346a5), interfaceId); rootElement.RemoveElementByName(AZ_CRC("InterfaceId", 0x221346a5)); rootElement.AddElementWithData(serializeContext, "ParserId", static_cast(interfaceId)); } return true; } void ExpressionEvaluationSystemComponent::Reflect(AZ::ReflectContext* context) { if (AZ::SerializeContext* serialize = azrtti_cast(context)) { serialize->Class() ->Version(0); // Only Serializing the information we need to serialize->Class() ->Version(0) ->Field("Id", &ElementInformation::m_id) ->Field("ExtraData", &ElementInformation::m_extraStore) ; serialize->Class() ->Version(1, ExpressionTokenConverter) ->Field("ParserId", &ExpressionToken::m_parserId) ->Field("TokenInformation", &ExpressionToken::m_information) ; serialize->Class() ->Version(0) ->Field("DisplayName", &VariableDescriptor::m_displayName) ->Field("NameHash", &VariableDescriptor::m_nameHash) ; serialize->Class() ->Version(0) ->Field("SupportedTypes", &ExpressionTree::VariableDescriptor::m_supportedTypes) ->Field("Value", &ExpressionTree::VariableDescriptor::m_value) ; serialize->Class() ->Version(0) ->Field("Variables", &ExpressionTree::m_variables) ->Field("VariableDisplayOrder", &ExpressionTree::m_orderedVariables) ->Field("Tokens", &ExpressionTree::m_tokens) ; if (AZ::EditContext* ec = serialize->GetEditContext()) { ec->Class("ExpressionEvaluationGem", "[Description of functionality provided by this System Component]") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ; } } } void ExpressionEvaluationSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("ExpressionEvaluationGemService", 0xad59526b)); } void ExpressionEvaluationSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC("ExpressionEvaluationGemService", 0xad59526b)); } void ExpressionEvaluationSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) { AZ_UNUSED(required); } void ExpressionEvaluationSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) { AZ_UNUSED(dependent); } ExpressionEvaluationSystemComponent::~ExpressionEvaluationSystemComponent() { for (auto internalParser : m_internalParsers) { delete internalParser; } for (auto parserPair : m_elementInterfaces) { delete parserPair.second; } } void ExpressionEvaluationSystemComponent::Init() { m_internalParsers.emplace_back(aznew StructuralParsers::InternalExpressionElementParser()); m_internalParsers.emplace_back(aznew VariableParser()); RegisterExpressionInterface(aznew NumericPrimitiveParser()); RegisterExpressionInterface(aznew MathExpressionOperators()); RegisterExpressionInterface(aznew BooleanPrimitiveParser()); } void ExpressionEvaluationSystemComponent::Activate() { ExpressionEvaluationRequestBus::Handler::BusConnect(); } void ExpressionEvaluationSystemComponent::Deactivate() { ExpressionEvaluationRequestBus::Handler::BusDisconnect(); } void ExpressionEvaluationSystemComponent::RegisterExpressionInterface(ExpressionElementParser* elementParser) { auto interfaceIter = m_elementInterfaces.find(elementParser->GetParserId()); if (interfaceIter != m_elementInterfaces.end()) { delete elementParser; } else { m_elementInterfaces[elementParser->GetParserId()] = elementParser; } } void ExpressionEvaluationSystemComponent::RemoveExpressionInterface(ExpressionParserId parserId) { auto interfaceIter = m_elementInterfaces.find(parserId); if (interfaceIter != m_elementInterfaces.end()) { delete interfaceIter->second; m_elementInterfaces.erase(interfaceIter); } } ParseOutcome ExpressionEvaluationSystemComponent::ParseExpression(AZStd::string_view expressionString) const { return ParseRestrictedExpression({}, expressionString); } ParseInPlaceOutcome ExpressionEvaluationSystemComponent::ParseExpressionInPlace(AZStd::string_view expressionString, ExpressionTree& expressionTree) const { return ParseRestrictedExpressionInPlace({}, expressionString, expressionTree); } ParseOutcome ExpressionEvaluationSystemComponent::ParseRestrictedExpression(const AZStd::unordered_set& availableParsers, AZStd::string_view expressionString) const { ExpressionTree expressionTree; AZ::Outcome result = ParseRestrictedExpressionInPlace(availableParsers, expressionString, expressionTree); if (result) { return AZ::Success(expressionTree); } return AZ::Failure(result.GetError()); } AZ::Outcome ExpressionEvaluationSystemComponent::ParseRestrictedExpressionInPlace(const AZStd::unordered_set& parsers, AZStd::string_view expressionString, ExpressionTree& expressionTree) const { AZ_PROFILE_TIMER("ExpressionEvaluation", __FUNCTION__); expressionTree.ClearTree(); size_t offset = 0; size_t lastOffset = 0; size_t endpoint = expressionString.length(); AZStd::vector< ExpressionToken > operatorStack; // Pre-reserve a bunch of space using the size of the string as a rough metric. // Should likely be too large assuming variables are used. operatorStack.reserve(expressionString.size() / 2); AZStd::vector< ExpressionElementParser*> parserList; parserList.reserve(m_internalParsers.size() + parsers.size()); parserList.insert(parserList.begin(), m_internalParsers.begin(), m_internalParsers.end()); if (!parsers.empty()) { for (const auto& interfaceId : parsers) { auto interfaceIter = m_elementInterfaces.find(interfaceId); if (interfaceIter != m_elementInterfaces.end()) { parserList.emplace_back(interfaceIter->second); } } } else { parserList.reserve(parserList.size() + m_elementInterfaces.size()); for (auto interfacePair : m_elementInterfaces) { parserList.emplace_back(interfacePair.second); } } AZStd::stack openParenOffsetStack; // We want to make sure our parsing makes logical sense(i.e. goes in the pattern of Value Operator Value Operator Value) // Otherwise elements might not function correctly bool expectOperator = false; // This is using a ShuntingYard Algorithm to sort out the expression into Reverse Polish Notation. while (offset < endpoint) { for (ExpressionElementParser* parser : parserList) { ExpressionElementParser::ParseResult result = parser->ParseElement(expressionString, offset); // Handle any tree elements that might have been returned. if (result.m_element.m_id >= 0) { ExpressionToken expressionToken; expressionToken.m_parserId = parser->GetParserId(); expressionToken.m_information = AZStd::move(result.m_element); // Handle all of the internal elements if (expressionToken.m_parserId == InternalTypes::Interfaces::InternalParser) { if (result.m_element.m_id == InternalTypes::OpenParen) { if (expectOperator) { return ReportUnexpectedSymbol(expressionString, offset, result.m_charactersConsumed); } operatorStack.emplace_back(AZStd::move(expressionToken)); openParenOffsetStack.push(offset); } else if (result.m_element.m_id == InternalTypes::CloseParen) { // Handling the weird case of () being the first element in an expression. Silly, but valid. // If nothing has been added to the tree, we don't want to error on a close paren. if (!expectOperator && expressionTree.GetTreeSize() != 0) { return ReportUnexpectedSymbol(expressionString, offset, result.m_charactersConsumed); } bool foundOpenParen = false; while (!operatorStack.empty()) { auto searchExpressionToken = operatorStack.back(); operatorStack.pop_back(); if (searchExpressionToken.m_parserId == InternalTypes::Interfaces::InternalParser) { if (searchExpressionToken.m_information.m_id == InternalTypes::OpenParen) { foundOpenParen = true; openParenOffsetStack.pop(); break; } } else { expressionTree.PushElement(AZStd::move(searchExpressionToken)); } } if (!foundOpenParen) { return ReportUnexpectedSymbol(expressionString, offset, result.m_charactersConsumed); } } else if (expressionToken.m_information.m_id == InternalTypes::Variable) { if (expectOperator) { return ReportUnexpectedValue(expressionString, offset, result.m_charactersConsumed); } VariableDescriptor descriptor = Utils::GetAnyValue(expressionToken.m_information.m_extraStore); expressionTree.RegisterVariable(descriptor.m_displayName); expressionTree.PushElement(AZStd::move(expressionToken)); expectOperator = true; } else { ParsingError parsingError; parsingError.m_offsetIndex = offset; parsingError.m_errorString = AZStd::string::format("Unknown internal tree element with id %i", expressionToken.m_information.m_id); return AZ::Failure(parsingError); } } else if (expressionToken.m_information.m_allowOnOperatorStack) { if (!expectOperator) { return ReportUnexpectedOperator(expressionString, offset, result.m_charactersConsumed); } if (operatorStack.empty()) { operatorStack.emplace_back(AZStd::move(expressionToken)); } else { int currentPriority = expressionToken.m_information.m_priority; while (!operatorStack.empty()) { const ExpressionToken& lastExpressionToken = operatorStack.back(); int lastPriority = lastExpressionToken.m_information.m_priority; if (lastPriority < currentPriority) { break; } else if (lastExpressionToken.m_information.m_associativity == ElementInformation::OperatorAssociativity::Left) { ExpressionToken tempToken = lastExpressionToken; operatorStack.pop_back(); expressionTree.PushElement(AZStd::move(tempToken)); } } operatorStack.emplace_back(AZStd::move(expressionToken)); } expectOperator = false; } else { if (expectOperator) { return ReportUnexpectedValue(expressionString, offset, result.m_charactersConsumed); } expressionTree.PushElement(AZStd::move(expressionToken)); expectOperator = true; } } // Increment out character by the amount of space consumed. // Then restart the parsing loop if (result.m_charactersConsumed > 0) { offset += result.m_charactersConsumed; break; } } if (offset == lastOffset) { return ReportUnknownCharacter(expressionString, offset); } lastOffset = offset; } if (!expectOperator && lastOffset > 0) { return ReportMissingValue(offset); } if (!openParenOffsetStack.empty()) { size_t initialOffset = openParenOffsetStack.top(); AZStd::string unbalancedParensString; AZStd::vector reversedList; while (!openParenOffsetStack.empty()) { reversedList.push_back(openParenOffsetStack.top()); openParenOffsetStack.pop(); } for (auto reverseIter = reversedList.rbegin(); reverseIter != reversedList.rend(); ++reverseIter) { if (!unbalancedParensString.empty()) { unbalancedParensString.append(", "); } unbalancedParensString.append(AZStd::to_string((*reverseIter))); } return ReportUnbalancedParen(initialOffset, unbalancedParensString); } while (!operatorStack.empty()) { ExpressionToken token = operatorStack.back(); operatorStack.pop_back(); expressionTree.PushElement(AZStd::move(token)); } return AZ::Success(); } EvaluateStringOutcome ExpressionEvaluationSystemComponent::EvaluateExpression(AZStd::string_view expression) const { ParseOutcome treeOutcome = ParseExpression(expression); if (!treeOutcome.IsSuccess()) { ParsingError parsingError = treeOutcome.GetError(); return AZ::Failure(parsingError); } return AZ::Success(Evaluate(treeOutcome.GetValue())); } ExpressionResult ExpressionEvaluationSystemComponent::Evaluate(const ExpressionTree& expressionTree) const { AZ_PROFILE_TIMER("ExpressionEvaluation", __FUNCTION__); ExpressionResultStack resultStack; for (auto expressionToken : expressionTree.GetTokens()) { // Empty one is reserved for internal elements(literals and variables) if (expressionToken.m_parserId == InternalTypes::Interfaces::InternalParser) { if (expressionToken.m_information.m_id == InternalTypes::Variable) { VariableDescriptor variableDescriptor = Utils::GetAnyValue(expressionToken.m_information.m_extraStore); AZStd::any variable = expressionTree.GetVariable(variableDescriptor.m_nameHash); resultStack.emplace(AZStd::move(variable)); } } else { auto interfaceIter = m_elementInterfaces.find(expressionToken.m_parserId); if (interfaceIter != m_elementInterfaces.end()) { interfaceIter->second->EvaluateToken(expressionToken.m_information, resultStack); } else { break; } } } AZ_Error("ExpressionEngine", resultStack.size() == 1, "Expression Tree should evaluate down to a single result. %i results found.", resultStack.size()); return resultStack.PopAndReturn(); } AZ::Outcome ExpressionEvaluationSystemComponent::ReportMissingValue(size_t offset) const { ParsingError parsingError; parsingError.m_offsetIndex = offset; parsingError.m_errorString = "Parsing completed after processing an Operator and not upon a value, invalid expression."; return AZ::Failure(parsingError); } AZ::Outcome ExpressionEvaluationSystemComponent::ReportUnexpectedOperator(const AZStd::string& parseString, size_t offset, size_t charactersConsumed) const { AZStd::string substring = parseString.substr(offset, charactersConsumed); ParsingError parsingError; parsingError.m_offsetIndex = offset; parsingError.m_errorString = AZStd::string::format("Unexpected Operator '%s' found at character %zu. Expected a Value.", substring.c_str(), offset); return AZ::Failure(parsingError); } AZ::Outcome ExpressionEvaluationSystemComponent::ReportUnexpectedValue(const AZStd::string& parseString, size_t offset, size_t charactersConsumed) const { AZStd::string substring = parseString.substr(offset, charactersConsumed); ParsingError parsingError; parsingError.m_offsetIndex = offset; parsingError.m_errorString = AZStd::string::format("Unexpected Value '%s' found at character %zu. Expected an Operator or end of expression.", substring.c_str(), offset); return AZ::Failure(parsingError); } AZ::Outcome ExpressionEvaluationSystemComponent::ReportUnexpectedSymbol(const AZStd::string& parseString, size_t offset, size_t charactersConsumed) const { AZStd::string substring = parseString.substr(offset, charactersConsumed); ParsingError parsingError; parsingError.m_offsetIndex = offset; parsingError.m_errorString = AZStd::string::format("Unexpected Symbol '%s' found at character %zu.", substring.c_str(), offset); return AZ::Failure(parsingError); } AZ::Outcome ExpressionEvaluationSystemComponent::ReportUnknownCharacter(const AZStd::string& parseString, size_t offset) const { ParsingError parsingError; parsingError.m_offsetIndex = offset; parsingError.m_errorString = AZStd::string::format("Unknown character '%c' found in expression.", parseString.at(offset)); return AZ::Failure(parsingError); } AZ::Outcome ExpressionEvaluationSystemComponent::ReportUnbalancedParen(size_t offset, const AZStd::string& openParenOffsetString) const { ParsingError parsingError; parsingError.m_offsetIndex = offset; parsingError.m_errorString = AZStd::string::format("Unbalanced ( found at character(s) '%s' in expression.", openParenOffsetString.c_str()); return AZ::Failure(parsingError); } }