TOptional<FExpressionError> FTokenDefinitions::ConsumeToken(FExpressionTokenConsumer& Consumer) const { auto& Stream = Consumer.GetStream(); // Skip over whitespace if (bIgnoreWhitespace) { TOptional<FStringToken> Whitespace = Stream.ParseWhitespace(); if (Whitespace.IsSet()) { Stream.SetReadPos(Whitespace.GetValue()); } } if (Stream.IsEmpty()) { // Trailing whitespace in the expression. return TOptional<FExpressionError>(); } const auto* Pos = Stream.GetRead(); // Try each token in turn. First come first served. for (const auto& Def : Definitions) { // Call the token definition auto Error = Def(Consumer); if (Error.IsSet()) { return Error; } // If the stream has moved on, the definition added one or more tokens, so else if (Stream.GetRead() != Pos) { return TOptional<FExpressionError>(); } } // No token definition matched the stream at its current position - fatal error FFormatOrderedArguments Args; Args.Add(FText::FromString(Consumer.GetStream().GetErrorContext())); Args.Add(Consumer.GetStream().GetPosition()); return FExpressionError(FText::Format(LOCTEXT("LexicalError", "Unrecognized token '{0}' at character {1}"), Args)); }
FText FAutoReimportManager::GetProgressText() const { FFormatOrderedArguments Args; { const int32 Progress = Utils::Reduce(DirectoryMonitors, [](const FContentDirectoryMonitor& Monitor, int32 Total){ return Total + Monitor.GetWorkProgress(); }, 0); Args.Add(Progress); } { const int32 Total = Utils::Reduce(DirectoryMonitors, [](const FContentDirectoryMonitor& Monitor, int32 InTotal){ return InTotal + Monitor.GetTotalWork(); }, 0); Args.Add(Total); } return FText::Format(LOCTEXT("ProcessingChanges", "Processing outstanding content changes ({0} of {1})..."), Args); }
/** Parse an escaped character */ TOptional<FExpressionError> ParseEscapedChar(FExpressionTokenConsumer& Consumer, bool bEmitErrors) { static const TCHAR* ValidEscapeChars = TEXT("{`"); TOptional<FStringToken> Token = Consumer.GetStream().ParseSymbol(EscapeChar); if (!Token.IsSet()) { return TOptional<FExpressionError>(); } // Accumulate the next character into the token TOptional<FStringToken> EscapedChar = Consumer.GetStream().ParseSymbol(&Token.GetValue()); if (!EscapedChar.IsSet()) { return TOptional<FExpressionError>(); } // Check for a valid escape character const TCHAR Character = *EscapedChar->GetTokenStartPos(); if (FCString::Strchr(ValidEscapeChars, Character)) { // Add the token to the consumer. This moves the read position in the stream to the end of the token. Consumer.Add(Token.GetValue(), FEscapedCharacter(Character)); return TOptional<FExpressionError>(); } else if (bEmitErrors) { FString CharStr; CharStr += Character; FFormatOrderedArguments Args; Args.Add(FText::FromString(CharStr)); return FExpressionError(FText::Format(LOCTEXT("InvalidEscapeCharacter", "Invalid escape character '{0}'"), Args)); } else { return TOptional<FExpressionError>(); } }
FExpressionResult Evaluate(const TArray<FCompiledToken>& CompiledTokens, const IOperatorEvaluationEnvironment& InEnvironment) { // Evaluation strategy: the supplied compiled tokens are const. To avoid copying the whole array, we store a separate array of // any tokens that are generated at runtime by the evaluator. The operand stack will consist of indices into either the CompiledTokens // array, or the RuntimeGeneratedTokens (where Index >= CompiledTokens.Num()) TArray<FExpressionToken> RuntimeGeneratedTokens; TArray<int32> OperandStack; /** Get the token pertaining to the specified operand index */ auto GetToken = [&](int32 Index) -> const FExpressionToken& { if (Index < CompiledTokens.Num()) { return CompiledTokens[Index]; } return RuntimeGeneratedTokens[Index - CompiledTokens.Num()]; }; /** Add a new token to the runtime generated array */ auto AddToken = [&](FExpressionToken&& In) -> int32 { auto Index = CompiledTokens.Num() + RuntimeGeneratedTokens.Num(); RuntimeGeneratedTokens.Emplace(MoveTemp(In)); return Index; }; for (int32 Index = 0; Index < CompiledTokens.Num(); ++Index) { const auto& Token = CompiledTokens[Index]; switch(Token.Type) { case FCompiledToken::Benign: continue; case FCompiledToken::Operand: OperandStack.Push(Index); continue; case FCompiledToken::BinaryOperator: if (OperandStack.Num() >= 2) { // Binary const auto& R = GetToken(OperandStack.Pop()); const auto& L = GetToken(OperandStack.Pop()); auto OpResult = InEnvironment.ExecBinary(Token, L, R); if (OpResult.IsValid()) { // Inherit the LHS context OperandStack.Push(AddToken(FExpressionToken(L.Context, MoveTemp(OpResult.GetValue())))); } else { return MakeError(OpResult.GetError()); } } else { FFormatOrderedArguments Args; Args.Add(FText::FromString(Token.Context.GetString())); return MakeError(FText::Format(LOCTEXT("SyntaxError_NotEnoughOperandsBinary", "Not enough operands for binary operator {0}"), Args)); } break; case FCompiledToken::PostUnaryOperator: case FCompiledToken::PreUnaryOperator: if (OperandStack.Num() >= 1) { const auto& Operand = GetToken(OperandStack.Pop()); FExpressionResult OpResult = (Token.Type == FCompiledToken::PreUnaryOperator) ? InEnvironment.ExecPreUnary(Token, Operand) : InEnvironment.ExecPostUnary(Token, Operand); if (OpResult.IsValid()) { // Inherit the LHS context OperandStack.Push(AddToken(FExpressionToken(Operand.Context, MoveTemp(OpResult.GetValue())))); } else { return MakeError(OpResult.GetError()); } } else { FFormatOrderedArguments Args; Args.Add(FText::FromString(Token.Context.GetString())); return MakeError(FText::Format(LOCTEXT("SyntaxError_NoUnaryOperand", "No operand for unary operator {0}"), Args)); } break; } } if (OperandStack.Num() == 1) { return MakeValue(GetToken(OperandStack[0]).Node.Copy()); } return MakeError(LOCTEXT("SyntaxError_InvalidExpression", "Could not evaluate expression")); }
FExpressionError GenerateErrorMsg(const FStringToken& Token) { FFormatOrderedArguments Args; Args.Add(FText::FromString(FString(Token.GetTokenEndPos()).Left(10) + TEXT("..."))); return FExpressionError(FText::Format(LOCTEXT("InvalidTokenDefinition", "Invalid token definition at '{0}'"), Args)); }