mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
764 lines
28 KiB
C++
764 lines
28 KiB
C++
/*
|
|
* GDevelop Core
|
|
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
|
|
* reserved. This project is released under the MIT License.
|
|
*/
|
|
#ifndef GDCORE_EXPRESSIONPARSER2_H
|
|
#define GDCORE_EXPRESSIONPARSER2_H
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include "ExpressionParser2Node.h"
|
|
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
|
|
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
|
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
|
|
#include "GDCore/String.h"
|
|
#include "GDCore/Tools/Localization.h"
|
|
#include "GDCore/Tools/MakeUnique.h"
|
|
namespace gd {
|
|
class Expression;
|
|
class ObjectsContainer;
|
|
class Platform;
|
|
class ParameterMetadata;
|
|
class ExpressionMetadata;
|
|
} // namespace gd
|
|
|
|
namespace gd {
|
|
|
|
/** \brief Parse an expression, returning a tree of node corresponding
|
|
* to the parsed expression.
|
|
*
|
|
* This is a LL(1) parser. This could be extracted to a generic/reusable
|
|
* parser by refactoring out the dependency on gd::MetadataProvider (injecting
|
|
* instead functions to be called to query supported functions).
|
|
*
|
|
* \see gd::ExpressionParserDiagnostic
|
|
* \see gd::ExpressionNode
|
|
*/
|
|
class GD_CORE_API ExpressionParser2 {
|
|
public:
|
|
ExpressionParser2(const gd::Platform &platform_,
|
|
const gd::ObjectsContainer &globalObjectsContainer_,
|
|
const gd::ObjectsContainer &objectsContainer_);
|
|
virtual ~ExpressionParser2(){};
|
|
|
|
/**
|
|
* Parse the given expression with the specified type.
|
|
*
|
|
* \param type Type of the expression: "string", "number",
|
|
* type supported by gd::ParameterMetadata::IsObject, types supported by
|
|
* gd::ParameterMetadata::IsExpression or "unknown". \param expression The
|
|
* expression to parse \param objectName Specify the object name, only for the
|
|
* case of "objectvar" type.
|
|
*
|
|
* \return The node representing the expression as a parsed tree.
|
|
*/
|
|
std::unique_ptr<ExpressionNode> ParseExpression(
|
|
const gd::String &type,
|
|
const gd::String &expression_,
|
|
const gd::String &objectName = "") {
|
|
expression = expression_;
|
|
|
|
currentPosition = 0;
|
|
return Start(type, objectName);
|
|
}
|
|
|
|
private:
|
|
/** \name Grammar
|
|
* Each method is a part of the grammar.
|
|
*/
|
|
///@{
|
|
std::unique_ptr<ExpressionNode> Start(const gd::String &type,
|
|
const gd::String &objectName = "") {
|
|
auto expression = Expression(type, objectName);
|
|
|
|
// Check for extra characters at the end of the expression
|
|
if (!IsEndReached()) {
|
|
auto op = gd::make_unique<OperatorNode>();
|
|
op->op = ' ';
|
|
op->leftHandSide = std::move(expression);
|
|
op->rightHandSide = ReadUntilEnd("unknown");
|
|
|
|
op->rightHandSide->diagnostic = RaiseSyntaxError(
|
|
_("The expression has extra character at the end that should be "
|
|
"removed (or completed if your expression is not finished)."));
|
|
return std::move(op);
|
|
}
|
|
|
|
return expression;
|
|
}
|
|
|
|
std::unique_ptr<ExpressionNode> Expression(
|
|
const gd::String &type, const gd::String &objectName = "") {
|
|
SkipWhitespace();
|
|
|
|
size_t expressionStartPosition = GetCurrentPosition();
|
|
std::unique_ptr<ExpressionNode> leftHandSide = Term(type, objectName);
|
|
|
|
SkipWhitespace();
|
|
|
|
if (IsEndReached()) return leftHandSide;
|
|
if (IsAnyChar(",)]")) return leftHandSide;
|
|
if (IsAnyChar(EXPRESSION_OPERATORS)) {
|
|
auto op = gd::make_unique<OperatorNode>();
|
|
op->op = GetCurrentChar();
|
|
op->leftHandSide = std::move(leftHandSide);
|
|
op->diagnostic = ValidateOperator(type, GetCurrentChar());
|
|
SkipChar();
|
|
op->rightHandSide = Expression(type, objectName);
|
|
return std::move(op);
|
|
}
|
|
|
|
if (type == "string") {
|
|
leftHandSide->diagnostic = RaiseSyntaxError(
|
|
"You must add the operator + between texts or expressions. For "
|
|
"example: \"Your name: \" + VariableString(PlayerName).");
|
|
} else if (type == "number") {
|
|
leftHandSide->diagnostic = RaiseSyntaxError(
|
|
"No operator found. Did you forget to enter an operator (like +, -, "
|
|
"* or /) between numbers or expressions?");
|
|
} else {
|
|
leftHandSide->diagnostic = RaiseSyntaxError(
|
|
"More than one term was found. Verify that your expression is "
|
|
"properly written.");
|
|
}
|
|
|
|
auto op = gd::make_unique<OperatorNode>();
|
|
op->op = ' ';
|
|
op->leftHandSide = std::move(leftHandSide);
|
|
op->rightHandSide = Expression(type, objectName);
|
|
return std::move(op);
|
|
}
|
|
|
|
std::unique_ptr<ExpressionNode> Term(const gd::String &type,
|
|
const gd::String &objectName) {
|
|
SkipWhitespace();
|
|
|
|
std::unique_ptr<ExpressionNode> factor = Factor(type, objectName);
|
|
SkipWhitespace();
|
|
|
|
// This while loop is used instead of a recursion (like in Expression)
|
|
// to guarantee the proper operator precedence. (Expression could also
|
|
// be reworked to use a while loop).
|
|
while (IsAnyChar(TERM_OPERATORS)) {
|
|
auto op = gd::make_unique<OperatorNode>();
|
|
op->op = GetCurrentChar();
|
|
op->leftHandSide = std::move(factor);
|
|
op->diagnostic = ValidateOperator(type, GetCurrentChar());
|
|
SkipChar();
|
|
op->rightHandSide = Factor(type, objectName);
|
|
SkipWhitespace();
|
|
|
|
factor = std::move(op);
|
|
}
|
|
|
|
return factor;
|
|
};
|
|
|
|
std::unique_ptr<ExpressionNode> Factor(const gd::String &type,
|
|
const gd::String &objectName) {
|
|
SkipWhitespace();
|
|
|
|
size_t expressionStartPosition = GetCurrentPosition();
|
|
std::unique_ptr<ExpressionNode> factor;
|
|
|
|
if (IsAnyChar(QUOTE)) {
|
|
factor = ReadText();
|
|
if (type == "number")
|
|
factor->diagnostic =
|
|
RaiseTypeError(_("You entered a text, but a number was expected."),
|
|
expressionStartPosition);
|
|
else if (type != "string")
|
|
factor->diagnostic = RaiseTypeError(
|
|
_("You entered a text, but this type was expected:") + type,
|
|
expressionStartPosition);
|
|
} else if (IsAnyChar(UNARY_OPERATORS)) {
|
|
auto unaryOperator = gd::make_unique<UnaryOperatorNode>(GetCurrentChar());
|
|
unaryOperator->diagnostic = ValidateUnaryOperator(type, GetCurrentChar());
|
|
SkipChar();
|
|
unaryOperator->factor = Factor(type, objectName);
|
|
|
|
factor = std::move(unaryOperator);
|
|
} else if (IsAnyChar(NUMBER_FIRST_CHAR)) {
|
|
factor = ReadNumber();
|
|
if (type == "string")
|
|
factor->diagnostic = RaiseTypeError(
|
|
_("You entered a number, but a text was expected (in quotes)."),
|
|
expressionStartPosition);
|
|
else if (type != "number")
|
|
factor->diagnostic = RaiseTypeError(
|
|
_("You entered a number, but this type was expected:") + type,
|
|
expressionStartPosition);
|
|
} else if (IsAnyChar("(")) {
|
|
SkipChar();
|
|
factor = SubExpression(type, objectName);
|
|
|
|
if (!IsAnyChar(")")) {
|
|
factor->diagnostic =
|
|
RaiseSyntaxError(_("Missing a closing parenthesis. Add a closing "
|
|
"parenthesis for each opening parenthesis."));
|
|
}
|
|
SkipIfIsAnyChar(")");
|
|
} else if (IsIdentifierAllowedChar()) {
|
|
// This is a place where the grammar differs according to the
|
|
// type being expected.
|
|
if (gd::ParameterMetadata::IsExpression("variable", type)) {
|
|
factor = Variable(type, objectName);
|
|
} else {
|
|
factor = Identifier(type);
|
|
}
|
|
} else {
|
|
factor = ReadUntilWhitespace(type);
|
|
factor->diagnostic = RaiseEmptyError(type, expressionStartPosition);
|
|
}
|
|
|
|
return factor;
|
|
}
|
|
|
|
std::unique_ptr<SubExpressionNode> SubExpression(
|
|
const gd::String &type, const gd::String &objectName) {
|
|
return std::move(
|
|
gd::make_unique<SubExpressionNode>(Expression(type, objectName)));
|
|
};
|
|
|
|
std::unique_ptr<IdentifierOrFunctionOrEmptyNode> Identifier(
|
|
const gd::String &type) {
|
|
size_t identifierStartPosition = GetCurrentPosition();
|
|
gd::String name = ReadIdentifierName();
|
|
|
|
SkipWhitespace();
|
|
|
|
if (IsNamespaceSeparator()) {
|
|
SkipNamespaceSeparator();
|
|
|
|
name += NAMESPACE_SEPARATOR;
|
|
name += ReadIdentifierName();
|
|
}
|
|
|
|
if (IsAnyChar("(")) {
|
|
SkipChar();
|
|
return FreeFunction(type, name, identifierStartPosition);
|
|
} else if (IsAnyChar(DOT)) {
|
|
SkipChar();
|
|
return ObjectFunctionOrBehaviorFunction(
|
|
type, name, identifierStartPosition);
|
|
} else {
|
|
auto identifier = gd::make_unique<IdentifierNode>(name, type);
|
|
if (type == "string") {
|
|
identifier->diagnostic =
|
|
RaiseTypeError(_("You must wrap your text inside double quotes "
|
|
"(example: \"Hello world\")."),
|
|
identifierStartPosition);
|
|
} else if (type == "number") {
|
|
identifier->diagnostic = RaiseTypeError(_("You must enter a number."),
|
|
identifierStartPosition);
|
|
} else if (!gd::ParameterMetadata::IsObject(type)) {
|
|
identifier->diagnostic = RaiseTypeError(
|
|
_("You've entered a name, but this type was expected:") + type,
|
|
identifierStartPosition);
|
|
}
|
|
|
|
return std::move(identifier);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<VariableNode> Variable(const gd::String &type,
|
|
const gd::String &objectName) {
|
|
size_t identifierStartPosition = GetCurrentPosition();
|
|
|
|
gd::String name = ReadIdentifierName();
|
|
auto variable = gd::make_unique<VariableNode>(type, name, objectName);
|
|
variable->child = VariableAccessorOrVariableBracketAccessor();
|
|
|
|
return std::move(variable);
|
|
}
|
|
|
|
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode>
|
|
VariableAccessorOrVariableBracketAccessor() {
|
|
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode> child;
|
|
SkipWhitespace();
|
|
if (IsAnyChar("[")) {
|
|
SkipChar();
|
|
child =
|
|
gd::make_unique<VariableBracketAccessorNode>(Expression("string"));
|
|
|
|
if (!IsAnyChar("]")) {
|
|
child->diagnostic =
|
|
RaiseSyntaxError(_("Missing a closing bracket. Add a closing "
|
|
"bracket for each opening bracket."));
|
|
}
|
|
SkipIfIsAnyChar("]");
|
|
child->child = VariableAccessorOrVariableBracketAccessor();
|
|
} else if (IsAnyChar(DOT)) {
|
|
SkipChar();
|
|
SkipWhitespace();
|
|
|
|
child = gd::make_unique<VariableAccessorNode>(ReadIdentifierName());
|
|
child->child = VariableAccessorOrVariableBracketAccessor();
|
|
}
|
|
|
|
return child;
|
|
}
|
|
|
|
std::unique_ptr<FunctionNode> FreeFunction(const gd::String &type,
|
|
const gd::String &functionFullName,
|
|
size_t functionStartPosition) {
|
|
// TODO: error if trying to use function for type != "number" && != "string"
|
|
// + Test for it
|
|
|
|
// This could be improved to have the type passed to a single
|
|
// GetExpressionMetadata function.
|
|
const gd::ExpressionMetadata &metadata =
|
|
type == "number" ? MetadataProvider::GetExpressionMetadata(
|
|
platform, functionFullName)
|
|
: MetadataProvider::GetStrExpressionMetadata(
|
|
platform, functionFullName);
|
|
|
|
auto parametersAndError = Parameters(metadata.parameters);
|
|
auto function = gd::make_unique<FunctionNode>(
|
|
type, std::move(parametersAndError.first), metadata, functionFullName);
|
|
function->diagnostic = std::move(parametersAndError.second);
|
|
if (!function->diagnostic)
|
|
function->diagnostic = ValidateFunction(*function, functionStartPosition);
|
|
|
|
return std::move(function);
|
|
}
|
|
|
|
std::unique_ptr<FunctionOrEmptyNode> ObjectFunctionOrBehaviorFunction(
|
|
const gd::String &type,
|
|
const gd::String &objectName,
|
|
size_t functionStartPosition) {
|
|
gd::String objectFunctionOrBehaviorName = ReadIdentifierName();
|
|
|
|
SkipWhitespace();
|
|
|
|
if (IsNamespaceSeparator()) {
|
|
SkipNamespaceSeparator();
|
|
return BehaviorFunction(type,
|
|
objectName,
|
|
objectFunctionOrBehaviorName,
|
|
functionStartPosition);
|
|
} else if (IsAnyChar("(")) {
|
|
SkipChar();
|
|
|
|
gd::String objectType =
|
|
GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName);
|
|
|
|
// This could be improved to have the type passed to a single
|
|
// GetExpressionMetadata function.
|
|
const gd::ExpressionMetadata &metadata =
|
|
type == "number"
|
|
? MetadataProvider::GetObjectExpressionMetadata(
|
|
platform, objectType, objectFunctionOrBehaviorName)
|
|
: MetadataProvider::GetObjectStrExpressionMetadata(
|
|
platform, objectType, objectFunctionOrBehaviorName);
|
|
|
|
auto parametersAndError = Parameters(metadata.parameters, objectName);
|
|
auto function =
|
|
gd::make_unique<FunctionNode>(type,
|
|
objectName,
|
|
std::move(parametersAndError.first),
|
|
metadata,
|
|
objectFunctionOrBehaviorName);
|
|
function->diagnostic = std::move(parametersAndError.second);
|
|
if (!function->diagnostic)
|
|
function->diagnostic =
|
|
ValidateFunction(*function, functionStartPosition);
|
|
|
|
return std::move(function);
|
|
}
|
|
|
|
auto node = gd::make_unique<EmptyNode>(type);
|
|
node->diagnostic = RaiseSyntaxError(
|
|
_("An opening parenthesis (for an object expression), or double colon "
|
|
"(::) was expected (for a behavior expression)."));
|
|
|
|
return std::move(node);
|
|
}
|
|
|
|
std::unique_ptr<FunctionOrEmptyNode> BehaviorFunction(
|
|
const gd::String &type,
|
|
const gd::String &objectName,
|
|
const gd::String &behaviorName,
|
|
size_t functionStartPosition) {
|
|
gd::String functionName = ReadIdentifierName();
|
|
|
|
SkipWhitespace();
|
|
|
|
if (IsAnyChar("(")) {
|
|
SkipChar();
|
|
|
|
gd::String behaviorType = GetTypeOfBehavior(
|
|
globalObjectsContainer, objectsContainer, behaviorName);
|
|
|
|
// This could be improved to have the type passed to a single
|
|
// GetExpressionMetadata function.
|
|
const gd::ExpressionMetadata &metadata =
|
|
type == "number" ? MetadataProvider::GetBehaviorExpressionMetadata(
|
|
platform, behaviorType, functionName)
|
|
: MetadataProvider::GetBehaviorStrExpressionMetadata(
|
|
platform, behaviorType, functionName);
|
|
|
|
auto parametersAndError =
|
|
Parameters(metadata.parameters, objectName, behaviorName);
|
|
auto function =
|
|
gd::make_unique<FunctionNode>(type,
|
|
objectName,
|
|
behaviorName,
|
|
std::move(parametersAndError.first),
|
|
metadata,
|
|
functionName);
|
|
function->diagnostic = std::move(parametersAndError.second);
|
|
if (!function->diagnostic)
|
|
function->diagnostic =
|
|
ValidateFunction(*function, functionStartPosition);
|
|
|
|
return std::move(function);
|
|
} else {
|
|
auto node = gd::make_unique<EmptyNode>(type);
|
|
node->diagnostic = RaiseSyntaxError(
|
|
_("An opening parenthesis was expected here to call a function."));
|
|
|
|
return std::move(node);
|
|
}
|
|
}
|
|
|
|
std::pair<std::vector<std::unique_ptr<ExpressionNode>>,
|
|
std::unique_ptr<gd::ExpressionParserError>>
|
|
Parameters(std::vector<gd::ParameterMetadata> parameterMetadata,
|
|
const gd::String &objectName = "",
|
|
const gd::String &behaviorName = "") {
|
|
std::vector<std::unique_ptr<ExpressionNode>> parameters;
|
|
|
|
// By convention, object is always the first parameter, and behavior the
|
|
// second one.
|
|
size_t parameterIndex =
|
|
WrittenParametersFirstIndex(objectName, behaviorName);
|
|
|
|
while (!IsEndReached()) {
|
|
SkipWhitespace();
|
|
|
|
if (IsAnyChar(")")) {
|
|
SkipChar();
|
|
return std::make_pair(std::move(parameters), nullptr);
|
|
} else {
|
|
if (parameterIndex < parameterMetadata.size()) {
|
|
const gd::String &type = parameterMetadata[parameterIndex].GetType();
|
|
if (parameterMetadata[parameterIndex].IsCodeOnly()) {
|
|
// Do nothing, code only parameters are not written in expressions.
|
|
} else if (gd::ParameterMetadata::IsExpression("number", type)) {
|
|
parameters.push_back(Expression("number"));
|
|
} else if (gd::ParameterMetadata::IsExpression("string", type)) {
|
|
parameters.push_back(Expression("string"));
|
|
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
|
|
parameters.push_back(Expression(type, objectName));
|
|
} else if (gd::ParameterMetadata::IsObject(type)) {
|
|
parameters.push_back(Expression(type));
|
|
} else {
|
|
size_t parameterStartPosition = GetCurrentPosition();
|
|
parameters.push_back(Expression("unknown"));
|
|
parameters.back()->diagnostic =
|
|
gd::make_unique<ExpressionParserError>(
|
|
"unknown_parameter_type",
|
|
_("This function is improperly set up. Reach out to the "
|
|
"extension developer or a GDevelop maintainer to fix "
|
|
"this issue"),
|
|
parameterStartPosition,
|
|
GetCurrentPosition());
|
|
}
|
|
} else {
|
|
size_t parameterStartPosition = GetCurrentPosition();
|
|
parameters.push_back(Expression("unknown"));
|
|
parameters.back()
|
|
->diagnostic = gd::make_unique<ExpressionParserError>(
|
|
"extra_parameter",
|
|
_("This parameter was not expected by this expression. Remove it "
|
|
"or verify that you've entered the proper expression name."),
|
|
parameterStartPosition,
|
|
GetCurrentPosition());
|
|
}
|
|
|
|
SkipWhitespace();
|
|
SkipIfIsAnyChar(PARAMETERS_SEPARATOR);
|
|
parameterIndex++;
|
|
}
|
|
}
|
|
|
|
return std::make_pair(
|
|
std::move(parameters),
|
|
RaiseSyntaxError(_("The list of parameters is not terminated. Add a "
|
|
"closing parenthesis to end the parameters.")));
|
|
}
|
|
///@}
|
|
|
|
/** \name Validators
|
|
* Return a diagnostic if any error is found
|
|
*/
|
|
///@{
|
|
std::unique_ptr<ExpressionParserDiagnostic> ValidateFunction(
|
|
const gd::FunctionNode &function, size_t functionStartPosition);
|
|
|
|
std::unique_ptr<ExpressionParserDiagnostic> ValidateOperator(
|
|
const gd::String &type, gd::String::value_type operatorChar) {
|
|
if (type == "number") {
|
|
if (operatorChar == '+' || operatorChar == '-' || operatorChar == '/' ||
|
|
operatorChar == '*') {
|
|
return gd::make_unique<ExpressionParserDiagnostic>();
|
|
}
|
|
|
|
return gd::make_unique<ExpressionParserError>(
|
|
"invalid_operator",
|
|
_("You've used an operator that is not supported. Operator should be "
|
|
"either +, -, / or *."),
|
|
GetCurrentPosition());
|
|
} else if (type == "string") {
|
|
if (operatorChar == '+') {
|
|
return gd::make_unique<ExpressionParserDiagnostic>();
|
|
}
|
|
|
|
return gd::make_unique<ExpressionParserError>(
|
|
"invalid_operator",
|
|
_("You've used an operator that is not supported. Only + can be used "
|
|
"to concatenate texts."),
|
|
GetCurrentPosition());
|
|
} else if (gd::ParameterMetadata::IsObject(type)) {
|
|
return gd::make_unique<ExpressionParserError>(
|
|
"invalid_operator",
|
|
_("Operators (+, -, /, *) can't be used with an object name. Remove the operator."),
|
|
GetCurrentPosition());
|
|
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
|
|
return gd::make_unique<ExpressionParserError>(
|
|
"invalid_operator",
|
|
_("Operators (+, -, /, *) can't be used in variable names. Remove "
|
|
"the operator from the variable name."),
|
|
GetCurrentPosition());
|
|
}
|
|
|
|
return gd::make_unique<ExpressionParserDiagnostic>();
|
|
}
|
|
|
|
std::unique_ptr<ExpressionParserDiagnostic> ValidateUnaryOperator(
|
|
const gd::String &type, gd::String::value_type operatorChar) {
|
|
if (type == "number") {
|
|
if (operatorChar == '+' || operatorChar == '-') {
|
|
return gd::make_unique<ExpressionParserDiagnostic>();
|
|
}
|
|
|
|
return gd::make_unique<ExpressionParserError>(
|
|
"invalid_operator",
|
|
_("You've used an \"unary\" operator that is not supported. Operator "
|
|
"should be "
|
|
"either + or -."),
|
|
GetCurrentPosition());
|
|
} else if (type == "string") {
|
|
return gd::make_unique<ExpressionParserError>(
|
|
"invalid_operator",
|
|
_("You've used an operator that is not supported. Only + can be used "
|
|
"to concatenate texts, and must be placed between two texts (or "
|
|
"expressions)."),
|
|
GetCurrentPosition());
|
|
} else if (gd::ParameterMetadata::IsObject(type)) {
|
|
return gd::make_unique<ExpressionParserError>(
|
|
"invalid_operator",
|
|
_("Operators (+, -) can't be used with an object name. Remove the operator."),
|
|
GetCurrentPosition());
|
|
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
|
|
return gd::make_unique<ExpressionParserError>(
|
|
"invalid_operator",
|
|
_("Operators (+, -) can't be used in variable names. Remove "
|
|
"the operator from the variable name."),
|
|
GetCurrentPosition());
|
|
}
|
|
|
|
return gd::make_unique<ExpressionParserDiagnostic>();
|
|
}
|
|
///@}
|
|
|
|
/** \name Parsing tokens
|
|
* Read tokens or characters
|
|
*/
|
|
///@{
|
|
void SkipChar() { currentPosition++; }
|
|
|
|
void SkipWhitespace() {
|
|
while (currentPosition < expression.size() &&
|
|
WHITESPACES.find(expression[currentPosition]) != gd::String::npos) {
|
|
currentPosition++;
|
|
}
|
|
}
|
|
|
|
void SkipIfIsAnyChar(const gd::String &allowedCharacters) {
|
|
if (IsAnyChar(allowedCharacters)) {
|
|
currentPosition++;
|
|
}
|
|
}
|
|
|
|
void SkipNamespaceSeparator() {
|
|
// Namespace separator is a special kind of delimiter as it is 2 characters
|
|
// long
|
|
if (IsNamespaceSeparator()) {
|
|
currentPosition += NAMESPACE_SEPARATOR.size();
|
|
}
|
|
}
|
|
|
|
bool IsAnyChar(const gd::String &allowedCharacters) {
|
|
if (currentPosition < expression.size() &&
|
|
allowedCharacters.find(expression[currentPosition]) !=
|
|
gd::String::npos) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsIdentifierAllowedChar() {
|
|
if (currentPosition < expression.size() &&
|
|
PARAMETERS_SEPARATOR.find(expression[currentPosition]) ==
|
|
gd::String::npos &&
|
|
DOT.find(expression[currentPosition]) == gd::String::npos &&
|
|
QUOTE.find(expression[currentPosition]) == gd::String::npos &&
|
|
BRACKETS.find(expression[currentPosition]) == gd::String::npos &&
|
|
EXPRESSION_OPERATORS.find(expression[currentPosition]) ==
|
|
gd::String::npos &&
|
|
TERM_OPERATORS.find(expression[currentPosition]) == gd::String::npos) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsNamespaceSeparator() {
|
|
// Namespace separator is a special kind of delimiter as it is 2 characters
|
|
// long
|
|
return (currentPosition + NAMESPACE_SEPARATOR.size() <= expression.size() &&
|
|
expression.substr(currentPosition, NAMESPACE_SEPARATOR.size()) ==
|
|
NAMESPACE_SEPARATOR);
|
|
}
|
|
|
|
bool IsEndReached() { return currentPosition >= expression.size(); }
|
|
|
|
gd::String ReadIdentifierName() {
|
|
gd::String name;
|
|
while (currentPosition < expression.size() &&
|
|
(IsIdentifierAllowedChar()
|
|
// Allow whitespace in identifier name for compatibility
|
|
|| expression[currentPosition] == ' ')) {
|
|
name += expression[currentPosition];
|
|
currentPosition++;
|
|
}
|
|
|
|
// Trim whitespace at the end (we allow them for compatibility inside
|
|
// the name, but after the last character that is not whitespace, they
|
|
// should be ignore again).
|
|
size_t lastCharacterPos = name.find_last_not_of(WHITESPACES);
|
|
if (!name.empty() && (lastCharacterPos + 1) < name.size()) {
|
|
name.erase(lastCharacterPos + 1);
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
std::unique_ptr<TextNode> ReadText();
|
|
|
|
std::unique_ptr<NumberNode> ReadNumber();
|
|
|
|
std::unique_ptr<EmptyNode> ReadUntilWhitespace(gd::String type) {
|
|
gd::String text;
|
|
while (currentPosition < expression.size() &&
|
|
WHITESPACES.find(expression[currentPosition]) == gd::String::npos) {
|
|
text += expression[currentPosition];
|
|
currentPosition++;
|
|
}
|
|
|
|
return gd::make_unique<EmptyNode>(type, text);
|
|
}
|
|
|
|
std::unique_ptr<EmptyNode> ReadUntilEnd(gd::String type) {
|
|
gd::String text;
|
|
while (currentPosition < expression.size()) {
|
|
text += expression[currentPosition];
|
|
currentPosition++;
|
|
}
|
|
|
|
return gd::make_unique<EmptyNode>(type, text);
|
|
}
|
|
|
|
size_t GetCurrentPosition() { return currentPosition; }
|
|
|
|
gd::String::value_type GetCurrentChar() {
|
|
if (currentPosition < expression.size()) {
|
|
return expression[currentPosition];
|
|
}
|
|
|
|
return '\n'; // Should not arise, unless GetCurrentChar was called when
|
|
// IsEndReached() is true (which is a logical error).
|
|
}
|
|
///@}
|
|
|
|
/** \name Raising errors
|
|
* Helpers to attach errors to nodes
|
|
*/
|
|
///@{
|
|
std::unique_ptr<ExpressionParserError> RaiseSyntaxError(
|
|
const gd::String &message) {
|
|
return std::move(gd::make_unique<ExpressionParserError>(
|
|
"syntax_error", message, GetCurrentPosition()));
|
|
}
|
|
|
|
std::unique_ptr<ExpressionParserError> RaiseTypeError(
|
|
const gd::String &message, size_t beginningPosition) {
|
|
return std::move(gd::make_unique<ExpressionParserError>(
|
|
"type_error", message, beginningPosition, GetCurrentPosition()));
|
|
}
|
|
|
|
std::unique_ptr<ExpressionParserError> RaiseEmptyError(
|
|
const gd::String &type, size_t beginningPosition) {
|
|
gd::String message;
|
|
if (type == "number") {
|
|
message = _("You must enter a number or a valid expression call.");
|
|
} else if (type == "string") {
|
|
message = _(
|
|
"You must enter a text (between quotes) or a valid expression call.");
|
|
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
|
|
message = _("You must enter a variable name.");
|
|
} else if (gd::ParameterMetadata::IsObject(type)) {
|
|
message = _("You must enter a valid object name.");
|
|
} else {
|
|
message = _("You must enter a valid expression.");
|
|
}
|
|
|
|
return std::move(RaiseTypeError(message, beginningPosition));
|
|
}
|
|
///@}
|
|
|
|
static size_t WrittenParametersFirstIndex(const gd::String &objectName,
|
|
const gd::String &behaviorName) {
|
|
// By convention, object is always the first parameter, and behavior the
|
|
// second one.
|
|
return !behaviorName.empty() ? 2 : (!objectName.empty() ? 1 : 0);
|
|
}
|
|
|
|
gd::String expression;
|
|
std::size_t currentPosition;
|
|
|
|
const gd::Platform &platform;
|
|
const gd::ObjectsContainer &globalObjectsContainer;
|
|
const gd::ObjectsContainer &objectsContainer;
|
|
|
|
static gd::String NUMBER_FIRST_CHAR;
|
|
static gd::String DOT;
|
|
static gd::String PARAMETERS_SEPARATOR;
|
|
static gd::String QUOTE;
|
|
static gd::String BRACKETS;
|
|
static gd::String EXPRESSION_OPERATORS;
|
|
static gd::String TERM_OPERATORS;
|
|
static gd::String UNARY_OPERATORS;
|
|
static gd::String WHITESPACES;
|
|
static gd::String NAMESPACE_SEPARATOR;
|
|
};
|
|
|
|
} // namespace gd
|
|
|
|
#endif // GDCORE_EXPRESSIONPARSER2_H
|