// Copyright Epic Games, Inc. All Rights Reserved. #include "EditConditionParser.h" #include "Containers/Set.h" #include "EditConditionContext.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "Math/BasicMathExpressionEvaluator.h" #include "Misc/AssertionMacros.h" #include "Misc/ExpressionParser.h" #include "Misc/Optional.h" #include "Trace/Detail/Channel.h" #include "UObject/NameTypes.h" class UObject; #define LOCTEXT_NAMESPACE "EditConditionParser" namespace EditConditionParserTokens { const TCHAR* const FEqual::Moniker = TEXT("=="); const TCHAR* const FNotEqual::Moniker = TEXT("!="); const TCHAR* const FGreater::Moniker = TEXT(">"); const TCHAR* const FGreaterEqual::Moniker = TEXT(">="); const TCHAR* const FLess::Moniker = TEXT("<"); const TCHAR* const FLessEqual::Moniker = TEXT("<="); const TCHAR* const FNot::Moniker = TEXT("!"); const TCHAR* const FAnd::Moniker = TEXT("&&"); const TCHAR* const FOr::Moniker = TEXT("||"); const TCHAR* const FAdd::Moniker = TEXT("+"); const TCHAR* const FSubtract::Moniker = TEXT("-"); const TCHAR* const FMultiply::Moniker = TEXT("*"); const TCHAR* const FDivide::Moniker = TEXT("/"); const TCHAR* const FBitwiseAnd::Moniker = TEXT("&"); const TCHAR* const FSubExpressionStart::Moniker = TEXT("("); const TCHAR* const FSubExpressionEnd::Moniker = TEXT(")"); } static const TCHAR PropertyBreakingChars[] = { '|', '=', '&', '>', '<', '!', '+', '-', '*', '/', ' ', '\t', '(', ')' }; static TOptional ConsumeBool(FExpressionTokenConsumer& Consumer) { TOptional TrueToken = Consumer.GetStream().ParseTokenIgnoreCase(TEXT("true")); if (TrueToken.IsSet()) { Consumer.Add(TrueToken.GetValue(), true); } TOptional FalseToken = Consumer.GetStream().ParseTokenIgnoreCase(TEXT("false")); if (FalseToken.IsSet()) { Consumer.Add(FalseToken.GetValue(), false); } return TOptional(); } static TOptional ConsumeNullPtr(FExpressionTokenConsumer& Consumer) { TOptional NullToken = Consumer.GetStream().ParseToken(TEXT("nullptr")); if (NullToken.IsSet()) { Consumer.Add(NullToken.GetValue(), EditConditionParserTokens::FNullPtrToken()); } return TOptional(); } static TOptional ConsumeIndexNone(FExpressionTokenConsumer& Consumer) { TOptional IndexNoneToken = Consumer.GetStream().ParseToken(TEXT("INDEX_NONE")); if (IndexNoneToken.IsSet()) { Consumer.Add(IndexNoneToken.GetValue(), EditConditionParserTokens::FIndexNoneToken()); } return TOptional(); } static TOptional ConsumePropertyName(FExpressionTokenConsumer& Consumer) { enum class EParsedStringType : uint8 { Unknown, Unquoted, Quoted, }; FString PropertyName; bool bShouldBeEnum = false; EParsedStringType ParsedStringType = EParsedStringType::Unknown; TCHAR OpeningQuoteChar = TEXT('\0'); int32 NumConsecutiveSlashes = 0; TOptional StringToken = Consumer.GetStream().ParseToken([&PropertyName, &bShouldBeEnum, &ParsedStringType, &OpeningQuoteChar, &NumConsecutiveSlashes](TCHAR InC) { if (ParsedStringType == EParsedStringType::Unknown) { if (InC == '"' || InC == '\'') { ParsedStringType = EParsedStringType::Quoted; OpeningQuoteChar = InC; NumConsecutiveSlashes = 0; return EParseState::Continue; } ParsedStringType = EParsedStringType::Unquoted; } check(ParsedStringType != EParsedStringType::Unknown); if (InC == ':') { bShouldBeEnum = true; } if (ParsedStringType == EParsedStringType::Unquoted) { for (const TCHAR BreakingChar : PropertyBreakingChars) { if (InC == BreakingChar) { return EParseState::StopBefore; } } PropertyName.AppendChar(InC); } else { check(ParsedStringType == EParsedStringType::Quoted); if (InC == OpeningQuoteChar && NumConsecutiveSlashes % 2 == 0) { return EParseState::StopAfter; } PropertyName.AppendChar(InC); if (InC == '\\') { NumConsecutiveSlashes++; } else { NumConsecutiveSlashes = 0; } } return EParseState::Continue; }); if (ParsedStringType == EParsedStringType::Quoted) { PropertyName.ReplaceEscapedCharWithCharInline(); } if (StringToken.IsSet()) { if (bShouldBeEnum) { int32 DoubleColonIndex = PropertyName.Find("::"); if (DoubleColonIndex == INDEX_NONE) { return FExpressionError(FText::Format(LOCTEXT("PropertyContainsSingleColon", "EditCondition contains single colon in property name \"{0}\", expected double colons."), FText::FromString(PropertyName))); } if (DoubleColonIndex == 0) { return FExpressionError(FText::Format(LOCTEXT("PropertyDoubleColonAtStart", "EditCondition contained double colon at start of property name \"{0}\", expected enum type."), FText::FromString(PropertyName))); } FString EnumType = PropertyName.Left(DoubleColonIndex); FString EnumValue = PropertyName.RightChop(DoubleColonIndex + 2); if (EnumValue.Len() == 0) { return FExpressionError(FText::Format(LOCTEXT("PropertyDoubleColonAtEnd", "EditCondition contained double colon at end of property name \"{0}\", expected enum value."), FText::FromString(PropertyName))); } Consumer.Add(StringToken.GetValue(), EditConditionParserTokens::FEnumToken(MoveTemp(EnumType), MoveTemp(EnumValue))); } else { Consumer.Add(StringToken.GetValue(), EditConditionParserTokens::FPropertyToken(MoveTemp(PropertyName))); } } return TOptional(); } template static void LogEditConditionError(const TValueOrError& Error, const IEditConditionContext* Context = nullptr) { if (!Error.HasError()) { return; } const FString Message = Error.GetError().Text.ToString(); FString Formatted = Message; if (Context != nullptr) { Formatted = FString::Printf(TEXT("%s - %s"), *Context->GetContextName().ToString(), *Message); } static TSet ErrorsAlreadyLogged; if (!ErrorsAlreadyLogged.Find(Formatted)) { ErrorsAlreadyLogged.Add(Formatted); UE_LOG(LogEditCondition, Error, TEXT("%s"), *Formatted); } } template TOptional GetValueInternal(const IEditConditionContext& Context, const FString& PropertyName) { return TOptional(); } template<> TOptional GetValueInternal(const IEditConditionContext& Context, const FString& PropertyName) { return Context.GetBoolValue(PropertyName, Context.GetFunction(PropertyName)); } template<> TOptional GetValueInternal(const IEditConditionContext& Context, const FString& PropertyName) { return Context.GetNumericValue(PropertyName, Context.GetFunction(PropertyName)); } template struct TOperand { TOperand(T InValue) : Value(InValue), Property(nullptr), Context(nullptr) {} TOperand(const EditConditionParserTokens::FPropertyToken& InProperty, const IEditConditionContext& InContext) : Property(&InProperty), Context(&InContext) {} bool IsProperty() const { return Property != nullptr; } TOptional GetValue() const { if (IsProperty()) { return GetValueInternal(*Context, Property->PropertyName); } return TOptional(Value); } const FString& GetName() const { check(IsProperty()); return Property->PropertyName; } private: T Value; const EditConditionParserTokens::FPropertyToken* Property; const IEditConditionContext* Context; }; static FExpressionResult ApplyNot(TOperand A) { TOptional Value = A.GetValue(); if (Value.IsSet()) { return MakeValue(!Value.GetValue()); } return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(A.GetName()))); } template FExpressionResult ApplyBinary(TOperand A, TOperand B, Function Apply) { TOptional ValueA = A.GetValue(); if (!ValueA.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(A.GetName()))); } TOptional ValueB = B.GetValue(); if (!ValueB.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(B.GetName()))); } return MakeValue(Apply(ValueA.GetValue(), ValueB.GetValue())); } static FExpressionResult ApplyBitwiseAnd(const EditConditionParserTokens::FPropertyToken& Property, const EditConditionParserTokens::FEnumToken& Enum, const IEditConditionContext& Context) { TOptional EnumValue = Context.GetIntegerValueOfEnum(Enum.Type, Enum.Value); if (!EnumValue.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidEnumValue", "EditCondition attempted to use an invalid enum value \"{0}::{1}\"."), FText::FromString(Enum.Type), FText::FromString(Enum.Value))); } TOptional PropertyValue = Context.GetIntegerValue(Property.PropertyName, Context.GetFunction(Property.PropertyName)); if (!PropertyValue.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(Property.PropertyName))); } return MakeValue((PropertyValue.Get(0) & EnumValue.Get(0)) != 0); } static FExpressionResult ApplyPropertyIsNull(const EditConditionParserTokens::FPropertyToken& Property, const IEditConditionContext& Context, bool bNegate) { TWeakObjectPtr CachedFunction = Context.GetFunction(Property.PropertyName); TOptional TypeName = Context.GetTypeName(Property.PropertyName, CachedFunction); if (!TypeName.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(Property.PropertyName))); } TOptional Ptr = Context.GetPointerValue(Property.PropertyName, CachedFunction); if (!Ptr.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(Property.PropertyName))); } bool bIsNull = Ptr.GetValue() == nullptr; if (bNegate) { bIsNull = !bIsNull; } return MakeValue(bIsNull); } static FExpressionResult ApplyPropertyIsIndexNone(const EditConditionParserTokens::FPropertyToken& Property, const IEditConditionContext& Context, bool bNegate) { TWeakObjectPtr CachedFunction = Context.GetFunction(Property.PropertyName); TOptional TypeName = Context.GetTypeName(Property.PropertyName, CachedFunction); if (!TypeName.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(Property.PropertyName))); } TOptional Value = Context.GetIntegerValue(Property.PropertyName, CachedFunction); if (!Value.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(Property.PropertyName))); } bool bIsIndexNone = Value.GetValue() == (int64)INDEX_NONE; if (bNegate) { bIsIndexNone = !bIsIndexNone; } return MakeValue(bIsIndexNone); } static FExpressionResult ApplyPropertiesEqual(const EditConditionParserTokens::FPropertyToken& A, const EditConditionParserTokens::FPropertyToken& B, const IEditConditionContext& Context, bool bNegate) { TWeakObjectPtr CachedFunctionA = Context.GetFunction(A.PropertyName); TWeakObjectPtr CachedFunctionB = Context.GetFunction(A.PropertyName); TOptional PtrA = Context.GetPointerValue(A.PropertyName, CachedFunctionA); TOptional PtrB = Context.GetPointerValue(B.PropertyName, CachedFunctionB); if (PtrA.IsSet() && PtrB.IsSet()) { const bool bAreEqual = PtrA.GetValue() == PtrB.GetValue(); return MakeValue(bNegate ? !bAreEqual : bAreEqual); } TOptional TypeNameA = Context.GetTypeName(A.PropertyName, CachedFunctionA); TOptional TypeNameB = Context.GetTypeName(B.PropertyName, CachedFunctionB); if (!TypeNameA.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(A.PropertyName))); } if (!TypeNameB.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(B.PropertyName))); } if (TypeNameA.GetValue() != TypeNameB.GetValue()) { return MakeError(FText::Format(LOCTEXT("OperandTypeMismatch", "EditCondition attempted to compare operands of different types: \"{0}\" and \"{1}\"."), FText::FromString(A.PropertyName), FText::FromString(B.PropertyName))); } TOptional BoolA = Context.GetBoolValue(A.PropertyName, CachedFunctionA); TOptional BoolB = Context.GetBoolValue(B.PropertyName, CachedFunctionB); if (BoolA.IsSet() && BoolB.IsSet()) { const bool bAreEqual = BoolA.GetValue() == BoolB.GetValue(); return MakeValue(bNegate ? !bAreEqual : bAreEqual); } TOptional DoubleA = Context.GetNumericValue(A.PropertyName, CachedFunctionA); TOptional DoubleB = Context.GetNumericValue(B.PropertyName, CachedFunctionB); if (DoubleA.IsSet() && DoubleB.IsSet()) { const bool bAreEqual = DoubleA.GetValue() == DoubleB.GetValue(); return MakeValue(bNegate ? !bAreEqual : bAreEqual); } TOptional EnumA = Context.GetEnumValue(A.PropertyName, CachedFunctionA); TOptional EnumB = Context.GetEnumValue(B.PropertyName, CachedFunctionB); if (EnumA.IsSet() && EnumB.IsSet()) { const bool bAreEqual = EnumA.GetValue() == EnumB.GetValue(); return MakeValue(bNegate ? !bAreEqual : bAreEqual); } return MakeError(FText::Format(LOCTEXT("OperandTypeMismatch", "EditCondition attempted to compare operands of different types: \"{0}\" and \"{1}\"."), FText::FromString(A.PropertyName), FText::FromString(B.PropertyName))); } static void CreateBooleanOperators(TOperatorJumpTable& OperatorJumpTable) { using namespace EditConditionParserTokens; OperatorJumpTable.MapPreUnary([](bool A) { return !A; }); OperatorJumpTable.MapPreUnary([](const FPropertyToken& A, const IEditConditionContext* Context) { return ApplyNot(TOperand(A, *Context)); }); // AND { auto ApplyAnd = [](bool First, bool Second) { return First && Second; }; OperatorJumpTable.MapBinary(ApplyAnd); OperatorJumpTable.MapBinary([ApplyAnd](const FPropertyToken& A, bool B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyAnd); }); OperatorJumpTable.MapBinary([ApplyAnd](bool A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyAnd); }); OperatorJumpTable.MapBinary([ApplyAnd](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyAnd); }); } // OR { auto ApplyOr = [](bool First, bool Second) { return First || Second; }; OperatorJumpTable.MapBinary(ApplyOr); OperatorJumpTable.MapBinary([ApplyOr](const FPropertyToken& A, bool B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyOr); }); OperatorJumpTable.MapBinary([ApplyOr](bool A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyOr); }); OperatorJumpTable.MapBinary([ApplyOr](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyOr); }); } // EQUALS { auto ApplyEqual = [](bool First, bool Second) { return First == Second; }; OperatorJumpTable.MapBinary(ApplyEqual); OperatorJumpTable.MapBinary([ApplyEqual](const FPropertyToken& A, bool B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyEqual); }); OperatorJumpTable.MapBinary([ApplyEqual](bool A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyEqual); }); } // NOT-EQUALS { auto ApplyNotEqual = [](bool First, bool Second) { return First != Second; }; OperatorJumpTable.MapBinary(ApplyNotEqual); OperatorJumpTable.MapBinary([ApplyNotEqual](const FPropertyToken& A, bool B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyNotEqual); }); OperatorJumpTable.MapBinary([ApplyNotEqual](bool A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyNotEqual); }); } } template void CreateNumberOperators(TOperatorJumpTable& OperatorJumpTable) { using namespace EditConditionParserTokens; // EQUAL { auto ApplyEqual = [](T First, T Second) { return First == Second; }; OperatorJumpTable.MapBinary(ApplyEqual); OperatorJumpTable.MapBinary([ApplyEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyEqual); }); OperatorJumpTable.MapBinary([ApplyEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyEqual); }); } // NOT-EQUAL { auto ApplyNotEqual = [](T First, T Second) { return First != Second; }; OperatorJumpTable.MapBinary(ApplyNotEqual); OperatorJumpTable.MapBinary([ApplyNotEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyNotEqual); }); OperatorJumpTable.MapBinary([ApplyNotEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyNotEqual); }); } // GREATER { auto ApplyGreater = [](T First, T Second) { return First > Second; }; OperatorJumpTable.MapBinary(ApplyGreater); OperatorJumpTable.MapBinary([ApplyGreater](const FPropertyToken& A, T B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyGreater); }); OperatorJumpTable.MapBinary([ApplyGreater](T A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyGreater); }); OperatorJumpTable.MapBinary([ApplyGreater](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyGreater); }); } // GREATER-EQUAL { auto ApplyGreaterEqual = [](T First, T Second) { return First >= Second; }; OperatorJumpTable.MapBinary(ApplyGreaterEqual); OperatorJumpTable.MapBinary([ApplyGreaterEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyGreaterEqual); }); OperatorJumpTable.MapBinary([ApplyGreaterEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyGreaterEqual); }); OperatorJumpTable.MapBinary([ApplyGreaterEqual](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyGreaterEqual); }); } // LESS { auto ApplyLess = [](T First, T Second) { return First < Second; }; OperatorJumpTable.MapBinary(ApplyLess); OperatorJumpTable.MapBinary([ApplyLess](const FPropertyToken& A, T B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyLess); }); OperatorJumpTable.MapBinary([ApplyLess](T A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyLess); }); OperatorJumpTable.MapBinary([ApplyLess](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyLess); }); } // LESS-EQUAL { auto ApplyLessEqual = [](T First, T Second) { return First <= Second; }; OperatorJumpTable.MapBinary(ApplyLessEqual); OperatorJumpTable.MapBinary([ApplyLessEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyLessEqual); }); OperatorJumpTable.MapBinary([ApplyLessEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyLessEqual); }); OperatorJumpTable.MapBinary([ApplyLessEqual](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyLessEqual); }); } // ADD { auto ApplyAdd = [](T First, T Second) { return First + Second; }; OperatorJumpTable.MapBinary(ApplyAdd); OperatorJumpTable.MapBinary([ApplyAdd](const FPropertyToken& A, T B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyAdd); }); OperatorJumpTable.MapBinary([ApplyAdd](T A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyAdd); }); OperatorJumpTable.MapBinary([ApplyAdd](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyAdd); }); } // SUBTRACT { auto ApplySubtract = [](T First, T Second) { return First - Second; }; OperatorJumpTable.MapBinary(ApplySubtract); OperatorJumpTable.MapBinary([ApplySubtract](const FPropertyToken& A, T B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplySubtract); }); OperatorJumpTable.MapBinary([ApplySubtract](T A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplySubtract); }); OperatorJumpTable.MapBinary([ApplySubtract](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplySubtract); }); } // MULTIPLY { auto ApplyMultiply = [](T First, T Second) { return First * Second; }; OperatorJumpTable.MapBinary(ApplyMultiply); OperatorJumpTable.MapBinary([ApplyMultiply](const FPropertyToken& A, T B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyMultiply); }); OperatorJumpTable.MapBinary([ApplyMultiply](T A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyMultiply); }); OperatorJumpTable.MapBinary([ApplyMultiply](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyMultiply); }); } // DIVIDE { auto ApplyDivide = [](T First, T Second) { return First / Second; }; OperatorJumpTable.MapBinary(ApplyDivide); OperatorJumpTable.MapBinary([ApplyDivide](const FPropertyToken& A, T B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyDivide); }); OperatorJumpTable.MapBinary([ApplyDivide](T A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyDivide); }); OperatorJumpTable.MapBinary([ApplyDivide](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) { return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyDivide); }); } } static FExpressionResult EnumPropertyEquals(const EditConditionParserTokens::FEnumToken& Enum, const EditConditionParserTokens::FPropertyToken& Property, const IEditConditionContext& Context, bool bNegate) { TWeakObjectPtr CachedFunction = Context.GetFunction(Property.PropertyName); TOptional TypeName = Context.GetTypeName(Property.PropertyName, CachedFunction); if (!TypeName.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidOperand_Type", "EditCondition attempted to use an invalid operand \"{0}\" (type error)."), FText::FromString(Property.PropertyName))); } if (TypeName.GetValue() != Enum.Type) { return MakeError(FText::Format(LOCTEXT("OperandTypeMismatch", "EditCondition attempted to compare operands of different types: \"{0}\" and \"{1}\"."), FText::FromString(Property.PropertyName), FText::FromString(Enum.Type + TEXT("::") + Enum.Value))); } TOptional ValueProp = Context.GetEnumValue(Property.PropertyName, CachedFunction); if (!ValueProp.IsSet()) { return MakeError(FText::Format(LOCTEXT("InvalidOperand_Value", "EditCondition attempted to use an invalid operand \"{0}\" (value error)."), FText::FromString(Property.PropertyName))); } bool bEqual = ValueProp.GetValue() == Enum.Value; return MakeValue(bNegate ? !bEqual : bEqual); } static void CreateEnumOperators(TOperatorJumpTable& OperatorJumpTable) { using namespace EditConditionParserTokens; // EQUALS { OperatorJumpTable.MapBinary([](const FEnumToken& A, const FEnumToken& B, const IEditConditionContext* Context) { return A.Type == B.Type && A.Value == B.Value; }); OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FEnumToken& B, const IEditConditionContext* Context) { return EnumPropertyEquals(B, A, *Context, false); }); OperatorJumpTable.MapBinary([](const FEnumToken& A, const FPropertyToken& B, const IEditConditionContext* Context) { return EnumPropertyEquals(A, B, *Context, false); }); } // NOT-EQUALS { OperatorJumpTable.MapBinary([](const FEnumToken& A, const FEnumToken& B, const IEditConditionContext* Context) { return A.Type != B.Type || A.Value != B.Value; }); OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FEnumToken& B, const IEditConditionContext* Context) -> FExpressionResult { return EnumPropertyEquals(B, A, *Context, true); }); OperatorJumpTable.MapBinary([](const FEnumToken& A, const FPropertyToken& B, const IEditConditionContext* Context) -> FExpressionResult { return EnumPropertyEquals(A, B, *Context, true); }); } } FEditConditionParser::FEditConditionParser() { using namespace EditConditionParserTokens; TokenDefinitions.IgnoreWhitespace(); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); TokenDefinitions.DefineToken(&ExpressionParser::ConsumeNumber); TokenDefinitions.DefineToken(&ConsumeNullPtr); TokenDefinitions.DefineToken(&ConsumeIndexNone); TokenDefinitions.DefineToken(&ConsumeBool); TokenDefinitions.DefineToken(&ConsumePropertyName); ExpressionGrammar.DefineBinaryOperator(4); ExpressionGrammar.DefineBinaryOperator(4); ExpressionGrammar.DefineBinaryOperator(3); ExpressionGrammar.DefineBinaryOperator(3); ExpressionGrammar.DefineBinaryOperator(3); ExpressionGrammar.DefineBinaryOperator(3); ExpressionGrammar.DefineBinaryOperator(3); ExpressionGrammar.DefineBinaryOperator(3); ExpressionGrammar.DefineBinaryOperator(2); ExpressionGrammar.DefineBinaryOperator(2); ExpressionGrammar.DefineBinaryOperator(2); ExpressionGrammar.DefineBinaryOperator(1); ExpressionGrammar.DefineBinaryOperator(1); ExpressionGrammar.DefinePreUnaryOperator(); ExpressionGrammar.DefineGrouping(); // POINTER EQUALITY OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) -> FExpressionResult { return ApplyPropertiesEqual(A, B, *Context, false); }); OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) -> FExpressionResult { return ApplyPropertiesEqual(A, B, *Context, true); }); // POINTER NULL OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FNullPtrToken& B, const IEditConditionContext* Context) -> FExpressionResult { return ApplyPropertyIsNull(A, *Context, false); }); OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FNullPtrToken& B, const IEditConditionContext* Context) -> FExpressionResult { return ApplyPropertyIsNull(A, *Context, true); }); // INDEX_NONE OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FIndexNoneToken& B, const IEditConditionContext* Context) -> FExpressionResult { return ApplyPropertyIsIndexNone(A, *Context, false); }); OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FIndexNoneToken& B, const IEditConditionContext* Context) -> FExpressionResult { return ApplyPropertyIsIndexNone(A, *Context, true); }); // BITWISE AND OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FEnumToken& B, const IEditConditionContext* Context) -> FExpressionResult { return ApplyBitwiseAnd(A, B, *Context); }); CreateBooleanOperators(OperatorJumpTable); CreateNumberOperators(OperatorJumpTable); CreateEnumOperators(OperatorJumpTable); } TValueOrError FEditConditionParser::Evaluate(const FEditConditionExpression& Expression, const IEditConditionContext& Context) const { using namespace EditConditionParserTokens; FExpressionResult Result = ExpressionParser::Evaluate(Expression.Tokens, OperatorJumpTable, &Context); if (Result.HasValue()) { const bool* BoolResult = Result.GetValue().Cast(); if (BoolResult != nullptr) { return MakeValue(*BoolResult); } const FPropertyToken* PropertyResult = Result.GetValue().Cast(); if (PropertyResult != nullptr) { TOptional PropertyValue = Context.GetBoolValue(PropertyResult->PropertyName, Context.GetFunction(PropertyResult->PropertyName)); if (PropertyValue.IsSet()) { return MakeValue(PropertyValue.GetValue()); } } } else { LogEditConditionError(Result, &Context); } const FText ErrorText = Result.HasError() ? Result.StealError().Text : FText::GetEmpty(); return MakeError(ErrorText); } TSharedPtr FEditConditionParser::Parse(const FString& ExpressionString) const { using namespace ExpressionParser; LexResultType LexResult = ExpressionParser::Lex(*ExpressionString, TokenDefinitions); if (LexResult.IsValid()) { CompileResultType CompileResult = ExpressionParser::Compile(LexResult.StealValue(), ExpressionGrammar); if (CompileResult.IsValid()) { return MakeShared(CompileResult.StealValue()); } else { LogEditConditionError(CompileResult); } } else { LogEditConditionError(LexResult); } return TSharedPtr(); } #undef LOCTEXT_NAMESPACE