Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,54 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/any.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/string/string.h>
#include <ExpressionEvaluation/ExpressionEngine/ExpressionTypes.h>
#include <ExpressionEngine/InternalTypes.h>
namespace ExpressionEvaluation
{
// Interface for expanding the available grammar for parsing.
//
// Must handle both parsing and execution.
class ExpressionElementParser
{
public:
AZ_CLASS_ALLOCATOR(ExpressionElementParser, AZ::SystemAllocator, 0);
struct ParseResult
{
// Returns the number of characters that the parser wants to consume from the input text.
size_t m_charactersConsumed = 0;
ElementInformation m_element;
};
protected:
ExpressionElementParser() = default;
public:
virtual ~ExpressionElementParser() = default;
virtual ExpressionParserId GetParserId() const = 0;
// Attempt to parse the specified element in the text at the specified offset.
virtual ParseResult ParseElement(const AZStd::string& inputText, size_t offset) const = 0;
// Evaluate the given token using the current evaluation stack.
virtual void EvaluateToken(const ElementInformation& parseResult, ExpressionResultStack& evaluationStack) const = 0;
};
}
@@ -0,0 +1,99 @@
/*
* 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 <AzCore/std/string/regex.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <ExpressionEngine/ExpressionPrimitive.h>
#include <ExpressionEngine/InternalTypes.h>
namespace ExpressionEvaluation
{
////////////////////
// PrimitiveParser
////////////////////
void PrimitiveParser::EvaluateToken(const ElementInformation& parseResult, ExpressionResultStack& evaluationStack) const
{
evaluationStack.emplace(parseResult.m_extraStore);
}
///////////////////////////
// NumericPrimitiveParser
///////////////////////////
NumericPrimitiveParser::NumericPrimitiveParser()
: m_regex(R"(^(0|([1-9][0-9]*))(\.[0-9]+)?)")
{
}
ExpressionParserId NumericPrimitiveParser::GetParserId() const
{
return Interfaces::NumericPrimitives;
}
NumericPrimitiveParser::ParseResult NumericPrimitiveParser::ParseElement(const AZStd::string& inputText, size_t offset) const
{
AZStd::smatch match;
ParseResult result;
if (AZStd::regex_search(&inputText.at(offset), match, m_regex))
{
AZStd::string matchedCharacters = match[0].str();
result.m_charactersConsumed = static_cast<int>(matchedCharacters.length());
double numericValue = AzFramework::StringFunc::ToDouble(matchedCharacters.c_str());
result.m_element = Primitive::GetPrimitiveElement(numericValue);
}
return result;
}
///////////////////////////
// BooleanPrimitiveParser
///////////////////////////
BooleanPrimitiveParser::BooleanPrimitiveParser()
: m_regex(R"((true|false))", AZStd::regex::ECMAScript | AZStd::regex::icase)
{
}
ExpressionParserId BooleanPrimitiveParser::GetParserId() const
{
return Interfaces::BooleanPrimitives;
}
BooleanPrimitiveParser::ParseResult BooleanPrimitiveParser::ParseElement(const AZStd::string& inputText, size_t offset) const
{
AZStd::smatch match;
ParseResult result;
if (AZStd::regex_search(&inputText.at(offset), match, m_regex))
{
AZStd::string matchedCharacters = match[0].str();
AZStd::to_lower(matchedCharacters.begin(), matchedCharacters.end());
result.m_charactersConsumed = matchedCharacters.length();
bool booleanValue = AzFramework::StringFunc::ToBool(matchedCharacters.c_str());
result.m_element = Primitive::GetPrimitiveElement(booleanValue);
}
return result;
}
}
@@ -0,0 +1,84 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/string/regex.h>
#include <AzCore/RTTI/RTTI.h>
#include <ExpressionEvaluation/ExpressionEngine/ExpressionTree.h>
#include <ExpressionEngine/ExpressionElementParser.h>
#include <ExpressionEngine/InternalTypes.h>
namespace ExpressionEvaluation
{
// Shared interface for pushing Primitives onto the evaluation stack. Does not handle parsing.
class PrimitiveParser
: public ExpressionElementParser
{
public:
AZ_CLASS_ALLOCATOR(PrimitiveParser, AZ::SystemAllocator, 0);
PrimitiveParser() = default;
void EvaluateToken(const ElementInformation& parseResult, ExpressionResultStack& evaluationStack) const override;
};
namespace Primitive
{
template<typename T>
ElementInformation GetPrimitiveElement(const T& valueType)
{
ElementInformation primitiveInformation;
primitiveInformation.m_allowOnOperatorStack = false;
primitiveInformation.m_id = InternalTypes::Primitive;
primitiveInformation.m_extraStore = valueType;
return primitiveInformation;
}
}
// Parser for basic numeric types
class NumericPrimitiveParser
: public PrimitiveParser
{
public:
AZ_CLASS_ALLOCATOR(NumericPrimitiveParser, AZ::SystemAllocator, 0);
NumericPrimitiveParser();
ExpressionParserId GetParserId() const override;
ParseResult ParseElement(const AZStd::string& inputText, size_t offset) const override;
private:
AZStd::regex m_regex;
};
// Parser for basic boolean types
class BooleanPrimitiveParser
: public PrimitiveParser
{
public:
AZ_CLASS_ALLOCATOR(BooleanPrimitiveParser, AZ::SystemAllocator, 0);
BooleanPrimitiveParser();
ExpressionParserId GetParserId() const override;
ParseResult ParseElement(const AZStd::string& inputText, size_t offset) const override;
private:
AZStd::regex m_regex;
};
}
@@ -0,0 +1,69 @@
/*
* 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 <AzCore/std/string/regex.h>
#include <ExpressionEngine/ExpressionVariable.h>
#include <ExpressionEngine/InternalTypes.h>
namespace ExpressionEvaluation
{
///////////////////
// VariableParser
///////////////////
ElementInformation VariableParser::GetVariableInformation(const AZStd::string& displayName)
{
ElementInformation elementInformation;
elementInformation.m_allowOnOperatorStack = false;
elementInformation.m_id = InternalTypes::Variable;
elementInformation.m_extraStore = VariableDescriptor(displayName);
return elementInformation;
}
VariableParser::VariableParser()
: m_regex(R"(^\{[^\}]*\})")
{
}
VariableParser::ParseResult VariableParser::ParseElement(const AZStd::string& inputText, size_t offset) const
{
AZStd::smatch match;
ParseResult result;
if (AZStd::regex_search(&inputText.at(offset), match, m_regex))
{
AZStd::string matchedCharacters = match[0].str();
result.m_charactersConsumed = matchedCharacters.length();
AZStd::string variableName = matchedCharacters.substr(1, matchedCharacters.length() - 2);
result.m_element = GetVariableInformation(variableName);
}
return result;
}
void VariableParser::EvaluateToken(const ElementInformation& parseResult, ExpressionResultStack& evaluationStack) const
{
AZ_UNUSED(parseResult);
AZ_UNUSED(evaluationStack);
AZ_Error("ExpressionParser", false, "VariableInterface should never be used to evaluate Variable information.");
}
}
@@ -0,0 +1,68 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <ExpressionEngine/ExpressionElementParser.h>
#include <ExpressionEngine/InternalTypes.h>
namespace ExpressionEvaluation
{
struct VariableDescriptor
{
AZ_RTTI(VariableDescriptor, "{6D219DB1-3763-4408-A3E8-75E4AE66E9BD}");
VariableDescriptor()
{
}
VariableDescriptor(const AZStd::string& displayName)
: m_displayName(displayName)
, m_nameHash(AZ::Crc32(displayName))
{
}
virtual ~VariableDescriptor() = default;
AZStd::string m_displayName;
AZ::Crc32 m_nameHash;
};
// Interface that adds in support for Variables into the Expression grammar.
class VariableParser
: public ExpressionElementParser
{
public:
AZ_CLASS_ALLOCATOR(VariableParser, AZ::SystemAllocator, 0);
static int GetVariableOperatorId()
{
return 1;
}
static ElementInformation GetVariableInformation(const AZStd::string& displayName);
VariableParser();
ExpressionParserId GetParserId() const override
{
return InternalTypes::Interfaces::InternalParser;
}
ParseResult ParseElement(const AZStd::string& inputText, size_t offset) const override;
void EvaluateToken(const ElementInformation& parseResult, ExpressionResultStack& evaluationStack) const override;
private:
AZStd::regex m_regex;
};
}
@@ -0,0 +1,53 @@
/*
* 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.
*
*/
#pragma once
#include <ExpressionEvaluation/ExpressionEngine/ExpressionTypes.h>
namespace ExpressionEvaluation
{
namespace InternalTypes
{
// General operations of the expression tree that are handled internally.
enum InternalOperatorId
{
Primitive = 0,
Variable,
OpenParen,
CloseParen
};
namespace Interfaces
{
static const ExpressionParserId InternalParser = 0;
}
}
class ExpressionResultStack
: public AZStd::stack<ExpressionResult>
{
public:
AZ_CLASS_ALLOCATOR(ExpressionResultStack, AZ::SystemAllocator, 0);
ExpressionResult PopAndReturn()
{
if (empty())
{
return ExpressionResult();
}
auto result = top();
pop();
return result;
}
};
}
@@ -0,0 +1,209 @@
/*
* 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 <AzCore/Math/MathUtils.h>
#include <ExpressionEngine/MathOperators/MathExpressionOperators.h>
#include <ExpressionEngine/Utils.h>
#include <ExpressionEvaluation/ExpressionEngine/ExpressionTypes.h>
namespace ExpressionEvaluation
{
////////////////////////////
// MathExpressionOperators
////////////////////////////
ElementInformation MathExpressionOperators::AddOperator()
{
ElementInformation elementInfo;
elementInfo.m_id = MathExpressionOperators::Add;
elementInfo.m_priority = AddSubtract;
return elementInfo;
}
ElementInformation MathExpressionOperators::SubtractOperator()
{
ElementInformation elementInfo;
elementInfo.m_id = MathExpressionOperators::Subtract;
elementInfo.m_priority = AddSubtract;
return elementInfo;
}
ElementInformation MathExpressionOperators::MultiplyOperator()
{
ElementInformation elementInfo;
elementInfo.m_id = MathExpressionOperators::Multiply;
elementInfo.m_priority = MultiplyDivideModulo;
return elementInfo;
}
ElementInformation MathExpressionOperators::DivideOperator()
{
ElementInformation elementInfo;
elementInfo.m_id = MathExpressionOperators::Divide;
elementInfo.m_priority = MultiplyDivideModulo;
return elementInfo;
}
ElementInformation MathExpressionOperators::ModuloOperator()
{
ElementInformation elementInfo;
elementInfo.m_id = MathExpressionOperators::Modulo;
elementInfo.m_priority = MultiplyDivideModulo;
return elementInfo;
}
ExpressionParserId MathExpressionOperators::GetParserId() const
{
return Interfaces::MathOperators;
}
MathExpressionOperators::ParseResult MathExpressionOperators::ParseElement(const AZStd::string& inputText, size_t offset) const
{
ParseResult result;
char firstChar = inputText.at(offset);
if (firstChar == '+')
{
result.m_charactersConsumed = 1;
result.m_element = AddOperator();
}
else if (firstChar == '-')
{
result.m_charactersConsumed = 1;
result.m_element = SubtractOperator();
}
else if (firstChar == '*')
{
result.m_charactersConsumed = 1;
result.m_element = MultiplyOperator();
}
else if (firstChar == '/')
{
result.m_charactersConsumed = 1;
result.m_element = DivideOperator();
}
else if (firstChar == '%')
{
result.m_charactersConsumed = 1;
result.m_element = ModuloOperator();
}
return result;
}
void MathExpressionOperators::EvaluateToken(const ElementInformation& elementInformation, ExpressionResultStack& resultStack) const
{
if (resultStack.size() < 2)
{
return;
}
auto rightValue = resultStack.PopAndReturn();
auto leftValue = resultStack.PopAndReturn();
ExpressionResult result;
switch (elementInformation.m_id)
{
case Add:
result = OnAddOperator(leftValue, rightValue);
break;
case Subtract:
result = OnSubtractOperator(leftValue, rightValue);
break;
case Multiply:
result = OnMultiplyOperator(leftValue, rightValue);
break;
case Divide:
result = OnDivideOperator(leftValue, rightValue);
break;
case Modulo:
result = OnModuloOperator(leftValue, rightValue);
break;
default:
break;
}
if (!result.empty())
{
resultStack.emplace(AZStd::move(result));
}
}
ExpressionResult MathExpressionOperators::OnAddOperator(const AZStd::any& leftValue, const AZStd::any& rightValue) const
{
double lhsValue = Utils::GetAnyValue<double>(leftValue);
double rhsValue = Utils::GetAnyValue<double>(rightValue);
return ExpressionResult(lhsValue + rhsValue);
}
ExpressionResult MathExpressionOperators::OnSubtractOperator(const AZStd::any& leftValue, const AZStd::any& rightValue) const
{
double lhsValue = Utils::GetAnyValue<double>(leftValue);
double rhsValue = Utils::GetAnyValue<double>(rightValue);
return ExpressionResult(lhsValue - rhsValue);
}
ExpressionResult MathExpressionOperators::OnMultiplyOperator(const AZStd::any& leftValue, const AZStd::any& rightValue) const
{
double lhsValue = Utils::GetAnyValue<double>(leftValue);
double rhsValue = Utils::GetAnyValue<double>(rightValue);
return ExpressionResult(lhsValue * rhsValue);
}
ExpressionResult MathExpressionOperators::OnDivideOperator(const AZStd::any& leftValue, const AZStd::any& rightValue) const
{
double lhsValue = Utils::GetAnyValue<double>(leftValue);
double rhsValue = Utils::GetAnyValue<double>(rightValue);
if (!AZ::IsClose(rhsValue, 0.0, std::numeric_limits<double>::epsilon()))
{
return ExpressionResult(lhsValue / rhsValue);
}
return ExpressionResult();
}
ExpressionResult MathExpressionOperators::OnModuloOperator(const AZStd::any& leftValue, const AZStd::any& rightValue) const
{
double lhsValue = Utils::GetAnyValue<double>(leftValue);
double rhsValue = Utils::GetAnyValue<double>(rightValue);
if (!AZ::IsClose(rhsValue, 0.0, std::numeric_limits<double>().epsilon()))
{
return ExpressionResult(aznumeric_cast<double>(aznumeric_cast<int>(lhsValue) % aznumeric_cast<int>(rhsValue)));
}
return ExpressionResult();
}
}
@@ -0,0 +1,66 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <ExpressionEngine/ExpressionElementParser.h>
namespace ExpressionEvaluation
{
class MathExpressionOperators
: public ExpressionElementParser
{
public:
AZ_CLASS_ALLOCATOR(MathExpressionOperators, AZ::SystemAllocator, 0);
enum MathExpressionOperatorPriority
{
Unknown = -1,
AddSubtract = 0,
MultiplyDivideModulo = 1,
Power = 2,
Function = 3
};
enum MathOperatorId
{
Add = 0,
Subtract,
Multiply,
Divide,
Modulo
};
static ElementInformation AddOperator();
static ElementInformation SubtractOperator();
static ElementInformation MultiplyOperator();
static ElementInformation DivideOperator();
static ElementInformation ModuloOperator();
MathExpressionOperators() = default;
~MathExpressionOperators() override = default;
ExpressionParserId GetParserId() const override;
ParseResult ParseElement(const AZStd::string& inputText, size_t offset) const override;
void EvaluateToken(const ElementInformation& elementInformation, ExpressionResultStack& evaluationStack) const override;
private:
ExpressionResult OnAddOperator(const AZStd::any& leftValue, const AZStd::any& rightValue) const;
ExpressionResult OnSubtractOperator(const AZStd::any& leftValue, const AZStd::any& rightValue) const;
ExpressionResult OnMultiplyOperator(const AZStd::any& leftValue, const AZStd::any& rightValue) const;
ExpressionResult OnDivideOperator(const AZStd::any& leftValue, const AZStd::any& rightValue) const;
ExpressionResult OnModuloOperator(const AZStd::any& leftValue, const AZStd::any& rightValue) const;
};
}
@@ -0,0 +1,32 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/any.h>
namespace ExpressionEvaluation
{
class Utils
{
public:
template<typename ValueType>
static ValueType GetAnyValue(const AZStd::any& operand, ValueType defaultValue = ValueType{})
{
if (operand.is<ValueType>())
{
return AZStd::any_cast<ValueType>(operand);
}
return defaultValue;
}
};
}
@@ -0,0 +1,51 @@
/*
* 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 <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Module/Module.h>
#include <ExpressionEvaluationSystemComponent.h>
namespace ExpressionEvaluation
{
class ExpressionEvaluationModule
: public AZ::Module
{
public:
AZ_RTTI(ExpressionEvaluationModule, "{3183322D-3AE1-4B8B-86D7-870DA60DC175}", AZ::Module);
AZ_CLASS_ALLOCATOR(ExpressionEvaluationModule, AZ::SystemAllocator, 0);
ExpressionEvaluationModule()
: AZ::Module()
{
// Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
m_descriptors.insert(m_descriptors.end(), {
ExpressionEvaluationSystemComponent::CreateDescriptor(),
});
}
/**
* Add required SystemComponents to the SystemEntity.
*/
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList{
azrtti_typeid<ExpressionEvaluationSystemComponent>(),
};
}
};
}
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_ExpressionEvaluation, ExpressionEvaluation::ExpressionEvaluationModule)
@@ -0,0 +1,622 @@
/*
* 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 <ExpressionEvaluationSystemComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <ExpressionEngine/InternalTypes.h>
#include <ExpressionEngine/MathOperators/MathExpressionOperators.h>
#include <ExpressionEngine/Utils.h>
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)
;
}
}
}
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);
}
}
@@ -0,0 +1,72 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <ExpressionEvaluation/ExpressionEvaluationBus.h>
#include <ExpressionEngine/ExpressionPrimitive.h>
#include <ExpressionEngine/ExpressionVariable.h>
namespace ExpressionEvaluation
{
class ExpressionEvaluationSystemComponent
: public AZ::Component
, public ExpressionEvaluationRequestBus::Handler
{
public:
AZ_COMPONENT(ExpressionEvaluationSystemComponent, "{55C70DBA-9B11-4A23-83C5-CA90260C917A}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
~ExpressionEvaluationSystemComponent();
// AZ::Component
void Init() override;
void Activate() override;
void Deactivate() override;
////
void RegisterExpressionInterface(ExpressionElementParser* elementInterface);
void RemoveExpressionInterface(ExpressionParserId interfaceId);
// ExpressionEvaluationRequestBus
ParseOutcome ParseExpression(AZStd::string_view expressionString) const override;
ParseInPlaceOutcome ParseExpressionInPlace(AZStd::string_view expressionString, ExpressionTree& expressionTree) const override;
ParseOutcome ParseRestrictedExpression(const AZStd::unordered_set<ExpressionParserId>& availableParsers, AZStd::string_view expressionString) const override;
ParseInPlaceOutcome ParseRestrictedExpressionInPlace(const AZStd::unordered_set<ExpressionParserId>& availableParsers, AZStd::string_view expressionString, ExpressionTree& expressionTree) const override;
EvaluateStringOutcome EvaluateExpression(AZStd::string_view expression) const override;
ExpressionResult Evaluate(const ExpressionTree& expressionTree) const override;
////
private:
AZ::Outcome<void, ParsingError> ReportMissingValue(size_t offset) const;
AZ::Outcome<void, ParsingError> ReportUnexpectedOperator(const AZStd::string& parseString, size_t offset, size_t charactersConsumed) const;
AZ::Outcome<void, ParsingError> ReportUnexpectedValue(const AZStd::string& parseString, size_t offset, size_t charactersConsumed) const;
AZ::Outcome<void, ParsingError> ReportUnexpectedSymbol(const AZStd::string& parseString, size_t offset, size_t charactersConsumed) const;
AZ::Outcome<void, ParsingError> ReportUnknownCharacter(const AZStd::string& parseString, size_t offset) const;
AZ::Outcome<void, ParsingError> ReportUnbalancedParen(size_t offset, const AZStd::string& offsetsString) const;
AZStd::vector<ExpressionElementParser*> m_internalParsers;
AZStd::unordered_map<ExpressionParserId, ExpressionElementParser*> m_elementInterfaces;
};
}