You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
626 lines
24 KiB
C++
626 lines
24 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/Serialization/EditContextConstants.inl>
|
|
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <ExpressionEngine/InternalTypes.h>
|
|
#include <ExpressionEngine/MathOperators/MathExpressionOperators.h>
|
|
#include <ExpressionEngine/Utils.h>
|
|
#include <ExpressionEvaluationSystemComponent.h>
|
|
#include <ExpressionPrimitivesSerializers.inl>
|
|
#include <ElementInformationSerializer.inl>
|
|
|
|
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<int>::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<int>::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<unsigned int>(serializeContext, "ParserId", static_cast<unsigned int>(interfaceId));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ExpressionEvaluationSystemComponent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
|
{
|
|
serialize->Class<ExpressionEvaluationSystemComponent, AZ::Component>()
|
|
->Version(0);
|
|
|
|
// Only Serializing the information we need to
|
|
serialize->Class<ElementInformation>()
|
|
->Version(0)
|
|
->Field("Id", &ElementInformation::m_id)
|
|
->Field("ExtraData", &ElementInformation::m_extraStore)
|
|
;
|
|
|
|
serialize->Class<ExpressionToken>()
|
|
->Version(1, ExpressionTokenConverter)
|
|
->Field("ParserId", &ExpressionToken::m_parserId)
|
|
->Field("TokenInformation", &ExpressionToken::m_information)
|
|
;
|
|
|
|
serialize->Class<VariableDescriptor>()
|
|
->Version(0)
|
|
->Field("DisplayName", &VariableDescriptor::m_displayName)
|
|
->Field("NameHash", &VariableDescriptor::m_nameHash)
|
|
;
|
|
|
|
serialize->Class<ExpressionTree::VariableDescriptor>()
|
|
->Version(0)
|
|
->Field("SupportedTypes", &ExpressionTree::VariableDescriptor::m_supportedTypes)
|
|
->Field("Value", &ExpressionTree::VariableDescriptor::m_value)
|
|
;
|
|
|
|
serialize->Class<ExpressionTree>()
|
|
->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<ExpressionEvaluationSystemComponent>("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)
|
|
;
|
|
}
|
|
}
|
|
|
|
if (AZ::JsonRegistrationContext* jsonContext = azrtti_cast<AZ::JsonRegistrationContext*>(context))
|
|
{
|
|
jsonContext->Serializer<AZ::ExpressionTreeVariableDescriptorSerializer>()->HandlesType<ExpressionTree::VariableDescriptor>();
|
|
jsonContext->Serializer<AZ::ElementInformationSerializer>()->HandlesType<ElementInformation>();
|
|
}
|
|
}
|
|
|
|
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<ExpressionParserId>& availableParsers, AZStd::string_view expressionString) const
|
|
{
|
|
ExpressionTree expressionTree;
|
|
AZ::Outcome<void, ParsingError> result = ParseRestrictedExpressionInPlace(availableParsers, expressionString, expressionTree);
|
|
|
|
if (result)
|
|
{
|
|
return AZ::Success(expressionTree);
|
|
}
|
|
|
|
return AZ::Failure(result.GetError());
|
|
}
|
|
|
|
AZ::Outcome<void, ParsingError> ExpressionEvaluationSystemComponent::ParseRestrictedExpressionInPlace(const AZStd::unordered_set<ExpressionParserId>& 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<size_t> 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<VariableDescriptor>(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<size_t> 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(treeOutcome.GetError());
|
|
}
|
|
|
|
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<VariableDescriptor>(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<void, ParsingError> 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<void, ParsingError> 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<void, ParsingError> 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<void, ParsingError> 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<void, ParsingError> 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<void, ParsingError> 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);
|
|
}
|
|
}
|