mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00

* This object displays a field where the player can type a text. * The cursor can be moved and the text can be selected and entered like any other input field according to the platform. For example, you can use all the usual keyboard shortcuts or emoji pickers on desktop. On mobile, all the usual gestures are supported, emoji pickers, text selection, clipboard... * You can choose between different input field types: text, text area, email, password, phone number... This allows the keyboards to be adapted on mobile devices. The password field will also hide what is entered by the user. * Because the object is way more convenient to use (and actually works on mobile devices) than the Text Entry object, it is recommended you use it almost always instead of the Text Entry object. * This object has 2 limitations, because it's using the native capabilities of the platform to display a "native" input field: the Z order can't be changed (the object is *always* displayed on top of the game) and no effects can be displayed. Because it's handled by the operating system, it may also slightly differ on each platform. * It's recommended you avoid displaying the object outside of a menu or a static UI, as it may not always display exactly at a position synchronised with the rest of the rendering of the game, especially if used in a fast paced or moving scene.
1074 lines
42 KiB
C++
1074 lines
42 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/Extensions/Metadata/ObjectMetadata.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);
|
|
}
|
|
|
|
/**
|
|
* Given an object name (or empty if none) and a behavior name (or empty if
|
|
* none), return the index of the first parameter that is inside the
|
|
* parenthesis: 0, 1 or 2.
|
|
*
|
|
* For example, in an expression like `Object.MyBehavior::Method("hello")`,
|
|
* the parameter "hello" is the second parameter (the first being by
|
|
* convention Object, and the second MyBehavior, also by convention).
|
|
*/
|
|
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);
|
|
}
|
|
|
|
private:
|
|
/** \name Grammar
|
|
* Each method is a part of the grammar.
|
|
*/
|
|
///@{
|
|
std::unique_ptr<ExpressionNode> Start(const gd::String &type,
|
|
const gd::String &objectName = "") {
|
|
size_t expressionStartPosition = GetCurrentPosition();
|
|
auto expression = Expression(type, objectName);
|
|
|
|
const gd::String &inferredType = expression->type;
|
|
|
|
// Check for extra characters at the end of the expression
|
|
if (!IsEndReached()) {
|
|
auto op = gd::make_unique<OperatorNode>(inferredType, ' ');
|
|
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)."));
|
|
|
|
op->location = ExpressionParserLocation(expressionStartPosition,
|
|
GetCurrentPosition());
|
|
return std::move(op);
|
|
}
|
|
|
|
return expression;
|
|
}
|
|
|
|
std::unique_ptr<ExpressionNode> Expression(
|
|
const gd::String &type, const gd::String &objectName = "") {
|
|
SkipAllWhitespaces();
|
|
|
|
size_t expressionStartPosition = GetCurrentPosition();
|
|
std::unique_ptr<ExpressionNode> leftHandSide = Term(type, objectName);
|
|
|
|
const gd::String &inferredType = leftHandSide->type;
|
|
|
|
SkipAllWhitespaces();
|
|
|
|
if (IsEndReached()) return leftHandSide;
|
|
if (CheckIfChar(IsExpressionEndingChar)) return leftHandSide;
|
|
if (CheckIfChar(IsExpressionOperator)) {
|
|
auto op = gd::make_unique<OperatorNode>(inferredType, GetCurrentChar());
|
|
op->leftHandSide = std::move(leftHandSide);
|
|
op->diagnostic = ValidateOperator(inferredType, GetCurrentChar());
|
|
SkipChar();
|
|
op->rightHandSide = Expression(inferredType, objectName);
|
|
|
|
op->location = ExpressionParserLocation(expressionStartPosition,
|
|
GetCurrentPosition());
|
|
return std::move(op);
|
|
}
|
|
|
|
if (inferredType == "string") {
|
|
leftHandSide->diagnostic = RaiseSyntaxError(
|
|
"You must add the operator + between texts or expressions. For "
|
|
"example: \"Your name: \" + VariableString(PlayerName).");
|
|
} else if (inferredType == "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>(inferredType, ' ');
|
|
op->leftHandSide = std::move(leftHandSide);
|
|
op->rightHandSide = Expression(inferredType, objectName);
|
|
op->location =
|
|
ExpressionParserLocation(expressionStartPosition, GetCurrentPosition());
|
|
return std::move(op);
|
|
}
|
|
|
|
std::unique_ptr<ExpressionNode> Term(const gd::String &type,
|
|
const gd::String &objectName) {
|
|
SkipAllWhitespaces();
|
|
|
|
size_t expressionStartPosition = GetCurrentPosition();
|
|
std::unique_ptr<ExpressionNode> factor = Factor(type, objectName);
|
|
|
|
const gd::String &inferredType = factor->type;
|
|
|
|
SkipAllWhitespaces();
|
|
|
|
// 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 (CheckIfChar(IsTermOperator)) {
|
|
auto op = gd::make_unique<OperatorNode>(inferredType, GetCurrentChar());
|
|
op->leftHandSide = std::move(factor);
|
|
op->diagnostic = ValidateOperator(inferredType, GetCurrentChar());
|
|
SkipChar();
|
|
op->rightHandSide = Factor(inferredType, objectName);
|
|
op->location = ExpressionParserLocation(expressionStartPosition,
|
|
GetCurrentPosition());
|
|
SkipAllWhitespaces();
|
|
|
|
factor = std::move(op);
|
|
}
|
|
|
|
return factor;
|
|
};
|
|
|
|
std::unique_ptr<ExpressionNode> Factor(const gd::String &type,
|
|
const gd::String &objectName) {
|
|
SkipAllWhitespaces();
|
|
size_t expressionStartPosition = GetCurrentPosition();
|
|
|
|
if (CheckIfChar(IsQuote)) {
|
|
std::unique_ptr<ExpressionNode> factor = ReadText();
|
|
if (type == "number")
|
|
factor->diagnostic =
|
|
RaiseTypeError(_("You entered a text, but a number was expected."),
|
|
expressionStartPosition);
|
|
else if (type != "string" && type != "number|string")
|
|
factor->diagnostic = RaiseTypeError(
|
|
_("You entered a text, but this type was expected:") + type,
|
|
expressionStartPosition);
|
|
|
|
return factor;
|
|
} else if (CheckIfChar(IsUnaryOperator)) {
|
|
auto unaryOperatorCharacter = GetCurrentChar();
|
|
SkipChar();
|
|
|
|
auto operatorOperand = Factor(type, objectName);
|
|
const gd::String &inferredType = operatorOperand->type;
|
|
|
|
auto unaryOperator = gd::make_unique<UnaryOperatorNode>(
|
|
inferredType, unaryOperatorCharacter);
|
|
unaryOperator->diagnostic = ValidateUnaryOperator(
|
|
inferredType, unaryOperatorCharacter, expressionStartPosition);
|
|
unaryOperator->factor = std::move(operatorOperand);
|
|
unaryOperator->location = ExpressionParserLocation(
|
|
expressionStartPosition, GetCurrentPosition());
|
|
|
|
return std::move(unaryOperator);
|
|
} else if (CheckIfChar(IsNumberFirstChar)) {
|
|
std::unique_ptr<ExpressionNode> factor = ReadNumber();
|
|
if (type == "string")
|
|
factor->diagnostic = RaiseTypeError(
|
|
_("You entered a number, but a text was expected (in quotes)."),
|
|
expressionStartPosition);
|
|
else if (type != "number" && type != "number|string")
|
|
factor->diagnostic = RaiseTypeError(
|
|
_("You entered a number, but this type was expected:") + type,
|
|
expressionStartPosition);
|
|
|
|
return factor;
|
|
} else if (CheckIfChar(IsOpeningParenthesis)) {
|
|
SkipChar();
|
|
std::unique_ptr<ExpressionNode> factor = SubExpression(type, objectName);
|
|
|
|
if (!CheckIfChar(IsClosingParenthesis)) {
|
|
factor->diagnostic =
|
|
RaiseSyntaxError(_("Missing a closing parenthesis. Add a closing "
|
|
"parenthesis for each opening parenthesis."));
|
|
}
|
|
SkipIfChar(IsClosingParenthesis);
|
|
return factor;
|
|
} else if (IsIdentifierAllowedChar()) {
|
|
// This is a place where the grammar differs according to the
|
|
// type being expected.
|
|
if (gd::ParameterMetadata::IsExpression("variable", type)) {
|
|
return Variable(type, objectName);
|
|
} else {
|
|
return Identifier(type);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<ExpressionNode> factor = ReadUntilWhitespace(type);
|
|
factor->diagnostic = RaiseEmptyError(type, expressionStartPosition);
|
|
return factor;
|
|
}
|
|
|
|
std::unique_ptr<SubExpressionNode> SubExpression(
|
|
const gd::String &type, const gd::String &objectName) {
|
|
size_t expressionStartPosition = GetCurrentPosition();
|
|
|
|
auto expression = Expression(type, objectName);
|
|
const gd::String &inferredType = expression->type;
|
|
|
|
auto subExpression =
|
|
gd::make_unique<SubExpressionNode>(inferredType, std::move(expression));
|
|
subExpression->location =
|
|
ExpressionParserLocation(expressionStartPosition, GetCurrentPosition());
|
|
|
|
return std::move(subExpression);
|
|
};
|
|
|
|
std::unique_ptr<IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode>
|
|
Identifier(const gd::String &type) {
|
|
auto identifierAndLocation = ReadIdentifierName();
|
|
gd::String name = identifierAndLocation.name;
|
|
auto nameLocation = identifierAndLocation.location;
|
|
|
|
SkipAllWhitespaces();
|
|
|
|
// We consider a namespace separator to be allowed here and be part of the
|
|
// function name (or object name, but object names are not allowed to
|
|
// contain a ":"). This is because functions from extensions have their
|
|
// extension name prefix, and separated by the namespace separator. This
|
|
// could maybe be refactored to create different nodes in the future.
|
|
if (IsNamespaceSeparator()) {
|
|
SkipNamespaceSeparator();
|
|
SkipAllWhitespaces();
|
|
|
|
auto postNamespaceIdentifierAndLocation = ReadIdentifierName();
|
|
name += NAMESPACE_SEPARATOR;
|
|
name += postNamespaceIdentifierAndLocation.name;
|
|
ExpressionParserLocation completeNameLocation(
|
|
nameLocation.GetStartPosition(),
|
|
postNamespaceIdentifierAndLocation.location.GetEndPosition());
|
|
nameLocation = completeNameLocation;
|
|
}
|
|
|
|
if (CheckIfChar(IsOpeningParenthesis)) {
|
|
ExpressionParserLocation openingParenthesisLocation = SkipChar();
|
|
return FreeFunction(type, name, nameLocation, openingParenthesisLocation);
|
|
} else if (CheckIfChar(IsDot)) {
|
|
ExpressionParserLocation dotLocation = SkipChar();
|
|
SkipAllWhitespaces();
|
|
return ObjectFunctionOrBehaviorFunction(
|
|
type, name, nameLocation, dotLocation);
|
|
} 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\")."),
|
|
nameLocation.GetStartPosition());
|
|
} else if (type == "number") {
|
|
identifier->diagnostic = RaiseTypeError(
|
|
_("You must enter a number."), nameLocation.GetStartPosition());
|
|
} else if (type == "number|string") {
|
|
identifier->diagnostic = RaiseTypeError(
|
|
_("You must enter a number or a text, wrapped inside double quotes "
|
|
"(example: \"Hello world\")."),
|
|
nameLocation.GetStartPosition());
|
|
} else if (!gd::ParameterMetadata::IsObject(type)) {
|
|
identifier->diagnostic = RaiseTypeError(
|
|
_("You've entered a name, but this type was expected:") + type,
|
|
nameLocation.GetStartPosition());
|
|
}
|
|
|
|
identifier->location = ExpressionParserLocation(
|
|
nameLocation.GetStartPosition(), GetCurrentPosition());
|
|
return std::move(identifier);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<VariableNode> Variable(const gd::String &type,
|
|
const gd::String &objectName) {
|
|
auto identifierAndLocation = ReadIdentifierName();
|
|
const gd::String &name = identifierAndLocation.name;
|
|
const auto &nameLocation = identifierAndLocation.location;
|
|
|
|
auto variable = gd::make_unique<VariableNode>(type, name, objectName);
|
|
variable->child = VariableAccessorOrVariableBracketAccessor();
|
|
|
|
variable->location = ExpressionParserLocation(
|
|
nameLocation.GetStartPosition(), GetCurrentPosition());
|
|
variable->nameLocation = nameLocation;
|
|
return std::move(variable);
|
|
}
|
|
|
|
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode>
|
|
VariableAccessorOrVariableBracketAccessor() {
|
|
size_t childStartPosition = GetCurrentPosition();
|
|
|
|
SkipAllWhitespaces();
|
|
if (CheckIfChar(IsOpeningSquareBracket)) {
|
|
SkipChar();
|
|
auto child = gd::make_unique<VariableBracketAccessorNode>(
|
|
Expression("number|string"));
|
|
|
|
if (!CheckIfChar(IsClosingSquareBracket)) {
|
|
child->diagnostic =
|
|
RaiseSyntaxError(_("Missing a closing bracket. Add a closing "
|
|
"bracket for each opening bracket."));
|
|
}
|
|
SkipIfChar(IsClosingSquareBracket);
|
|
child->child = VariableAccessorOrVariableBracketAccessor();
|
|
child->location =
|
|
ExpressionParserLocation(childStartPosition, GetCurrentPosition());
|
|
|
|
return std::move(child);
|
|
} else if (CheckIfChar(IsDot)) {
|
|
auto dotLocation = SkipChar();
|
|
SkipAllWhitespaces();
|
|
|
|
auto identifierAndLocation = ReadIdentifierName();
|
|
auto child =
|
|
gd::make_unique<VariableAccessorNode>(identifierAndLocation.name);
|
|
child->child = VariableAccessorOrVariableBracketAccessor();
|
|
child->nameLocation = identifierAndLocation.location;
|
|
child->dotLocation = dotLocation;
|
|
child->location =
|
|
ExpressionParserLocation(childStartPosition, GetCurrentPosition());
|
|
|
|
return std::move(child);
|
|
}
|
|
|
|
return std::move(
|
|
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode>());
|
|
}
|
|
|
|
std::unique_ptr<FunctionCallNode> FreeFunction(
|
|
const gd::String &type,
|
|
const gd::String &functionFullName,
|
|
const ExpressionParserLocation &identifierLocation,
|
|
const ExpressionParserLocation &openingParenthesisLocation) {
|
|
// TODO: error if trying to use function for type != "number" && != "string"
|
|
// + Test for it
|
|
|
|
const gd::ExpressionMetadata &metadata =
|
|
MetadataProvider::GetAnyExpressionMetadata(platform, functionFullName);
|
|
|
|
// In case we can't find a valid expression, ensure the node has the type
|
|
// that is requested by the parent, so we avoid putting "unknown" (which
|
|
// would be also correct, but less precise and would prevent completions to
|
|
// be shown to the user)
|
|
const gd::String returnType =
|
|
gd::MetadataProvider::IsBadExpressionMetadata(metadata) == true
|
|
? type
|
|
: metadata.GetReturnType();
|
|
|
|
auto parametersNode = Parameters(metadata.parameters);
|
|
auto function =
|
|
gd::make_unique<FunctionCallNode>(returnType,
|
|
std::move(parametersNode.parameters),
|
|
metadata,
|
|
functionFullName);
|
|
function->diagnostic = std::move(parametersNode.diagnostic);
|
|
if (!function->diagnostic) // TODO: reverse the order of diagnostic?
|
|
function->diagnostic = ValidateFunction(
|
|
type, *function, identifierLocation.GetStartPosition());
|
|
|
|
function->location = ExpressionParserLocation(
|
|
identifierLocation.GetStartPosition(), GetCurrentPosition());
|
|
function->functionNameLocation = identifierLocation;
|
|
function->openingParenthesisLocation = openingParenthesisLocation;
|
|
function->closingParenthesisLocation =
|
|
parametersNode.closingParenthesisLocation;
|
|
|
|
return std::move(function);
|
|
}
|
|
|
|
std::unique_ptr<FunctionCallOrObjectFunctionNameOrEmptyNode>
|
|
ObjectFunctionOrBehaviorFunction(
|
|
const gd::String &type,
|
|
const gd::String &objectName,
|
|
const ExpressionParserLocation &objectNameLocation,
|
|
const ExpressionParserLocation &objectNameDotLocation) {
|
|
auto identifierAndLocation = ReadIdentifierName();
|
|
const gd::String &objectFunctionOrBehaviorName = identifierAndLocation.name;
|
|
const auto &objectFunctionOrBehaviorNameLocation =
|
|
identifierAndLocation.location;
|
|
|
|
SkipAllWhitespaces();
|
|
|
|
if (IsNamespaceSeparator()) {
|
|
ExpressionParserLocation namespaceSeparatorLocation =
|
|
SkipNamespaceSeparator();
|
|
SkipAllWhitespaces();
|
|
return BehaviorFunction(type,
|
|
objectName,
|
|
objectFunctionOrBehaviorName,
|
|
objectNameLocation,
|
|
objectNameDotLocation,
|
|
objectFunctionOrBehaviorNameLocation,
|
|
namespaceSeparatorLocation);
|
|
} else if (CheckIfChar(IsOpeningParenthesis)) {
|
|
ExpressionParserLocation openingParenthesisLocation = SkipChar();
|
|
|
|
gd::String objectType =
|
|
GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName);
|
|
|
|
const gd::ExpressionMetadata &metadata =
|
|
MetadataProvider::GetObjectAnyExpressionMetadata(
|
|
platform, objectType, objectFunctionOrBehaviorName);
|
|
|
|
// In case we can't find a valid expression, ensure the node has the type
|
|
// that is requested by the parent, so we avoid putting "unknown" (which
|
|
// would be also correct, but less precise and would prevent completions
|
|
// to be shown to the user)
|
|
const gd::String returnType =
|
|
gd::MetadataProvider::IsBadExpressionMetadata(metadata) == true
|
|
? type
|
|
: metadata.GetReturnType();
|
|
|
|
auto parametersNode = Parameters(metadata.parameters, objectName);
|
|
auto function = gd::make_unique<FunctionCallNode>(
|
|
returnType,
|
|
objectName,
|
|
std::move(parametersNode.parameters),
|
|
metadata,
|
|
objectFunctionOrBehaviorName);
|
|
function->diagnostic = std::move(parametersNode.diagnostic);
|
|
|
|
if (!function->diagnostic) // TODO: reverse the order of diagnostic?
|
|
function->diagnostic = ValidateFunction(
|
|
type, *function, objectNameLocation.GetStartPosition());
|
|
|
|
// If the function needs a capability on the object that may not be covered
|
|
// by all objects, check it now.
|
|
if (!metadata.GetRequiredBaseObjectCapability().empty()) {
|
|
const gd::ObjectMetadata &objectMetadata =
|
|
MetadataProvider::GetObjectMetadata(platform, objectType);
|
|
|
|
if (objectMetadata.IsUnsupportedBaseObjectCapability(
|
|
metadata.GetRequiredBaseObjectCapability())) {
|
|
function->diagnostic = RaiseTypeError(
|
|
_("This expression exists, but it can't be used on this object."),
|
|
objectNameLocation.GetStartPosition());
|
|
}
|
|
}
|
|
|
|
function->location = ExpressionParserLocation(
|
|
objectNameLocation.GetStartPosition(), GetCurrentPosition());
|
|
function->objectNameLocation = objectNameLocation;
|
|
function->objectNameDotLocation = objectNameDotLocation;
|
|
function->functionNameLocation = objectFunctionOrBehaviorNameLocation;
|
|
function->openingParenthesisLocation = openingParenthesisLocation;
|
|
function->closingParenthesisLocation =
|
|
parametersNode.closingParenthesisLocation;
|
|
return std::move(function);
|
|
}
|
|
|
|
auto node = gd::make_unique<ObjectFunctionNameNode>(
|
|
type, objectName, objectFunctionOrBehaviorName);
|
|
node->diagnostic = RaiseSyntaxError(
|
|
_("An opening parenthesis (for an object expression), or double colon "
|
|
"(::) was expected (for a behavior expression)."));
|
|
|
|
node->location = ExpressionParserLocation(
|
|
objectNameLocation.GetStartPosition(), GetCurrentPosition());
|
|
node->objectNameLocation = objectNameLocation;
|
|
node->objectNameDotLocation = objectNameDotLocation;
|
|
node->objectFunctionOrBehaviorNameLocation =
|
|
objectFunctionOrBehaviorNameLocation;
|
|
return std::move(node);
|
|
}
|
|
|
|
std::unique_ptr<FunctionCallOrObjectFunctionNameOrEmptyNode> BehaviorFunction(
|
|
const gd::String &type,
|
|
const gd::String &objectName,
|
|
const gd::String &behaviorName,
|
|
const ExpressionParserLocation &objectNameLocation,
|
|
const ExpressionParserLocation &objectNameDotLocation,
|
|
const ExpressionParserLocation &behaviorNameLocation,
|
|
const ExpressionParserLocation &behaviorNameNamespaceSeparatorLocation) {
|
|
auto identifierAndLocation = ReadIdentifierName();
|
|
const gd::String &functionName = identifierAndLocation.name;
|
|
const auto &functionNameLocation = identifierAndLocation.location;
|
|
|
|
SkipAllWhitespaces();
|
|
|
|
if (CheckIfChar(IsOpeningParenthesis)) {
|
|
ExpressionParserLocation openingParenthesisLocation = SkipChar();
|
|
|
|
gd::String behaviorType = GetTypeOfBehavior(
|
|
globalObjectsContainer, objectsContainer, behaviorName);
|
|
|
|
const gd::ExpressionMetadata &metadata =
|
|
MetadataProvider::GetBehaviorAnyExpressionMetadata(
|
|
platform, behaviorType, functionName);
|
|
|
|
// In case we can't find a valid expression, ensure the node has the type
|
|
// that is requested by the parent, so we avoid putting "unknown" (which
|
|
// would be also correct, but less precise and would prevent completions
|
|
// to be shown to the user)
|
|
const gd::String returnType =
|
|
gd::MetadataProvider::IsBadExpressionMetadata(metadata) == true
|
|
? type
|
|
: metadata.GetReturnType();
|
|
|
|
auto parametersNode =
|
|
Parameters(metadata.parameters, objectName, behaviorName);
|
|
auto function = gd::make_unique<FunctionCallNode>(
|
|
returnType,
|
|
objectName,
|
|
behaviorName,
|
|
std::move(parametersNode.parameters),
|
|
metadata,
|
|
functionName);
|
|
function->diagnostic = std::move(parametersNode.diagnostic);
|
|
if (!function->diagnostic) // TODO: reverse the order of diagnostic?
|
|
function->diagnostic = ValidateFunction(
|
|
type, *function, objectNameLocation.GetStartPosition());
|
|
|
|
function->location = ExpressionParserLocation(
|
|
objectNameLocation.GetStartPosition(), GetCurrentPosition());
|
|
function->objectNameLocation = objectNameLocation;
|
|
function->objectNameDotLocation = objectNameDotLocation;
|
|
function->behaviorNameLocation = behaviorNameLocation;
|
|
function->behaviorNameNamespaceSeparatorLocation =
|
|
behaviorNameNamespaceSeparatorLocation;
|
|
function->openingParenthesisLocation = openingParenthesisLocation;
|
|
function->closingParenthesisLocation =
|
|
parametersNode.closingParenthesisLocation;
|
|
function->functionNameLocation = functionNameLocation;
|
|
return std::move(function);
|
|
} else {
|
|
auto node = gd::make_unique<ObjectFunctionNameNode>(
|
|
type, objectName, behaviorName, functionName);
|
|
node->diagnostic = RaiseSyntaxError(
|
|
_("An opening parenthesis was expected here to call a function."));
|
|
|
|
node->location = ExpressionParserLocation(
|
|
objectNameLocation.GetStartPosition(), GetCurrentPosition());
|
|
node->objectNameLocation = objectNameLocation;
|
|
node->objectNameDotLocation = objectNameDotLocation;
|
|
node->objectFunctionOrBehaviorNameLocation = behaviorNameLocation;
|
|
node->behaviorNameNamespaceSeparatorLocation =
|
|
behaviorNameNamespaceSeparatorLocation;
|
|
node->behaviorFunctionNameLocation = functionNameLocation;
|
|
return std::move(node);
|
|
}
|
|
}
|
|
|
|
// A temporary node that will be integrated into function nodes.
|
|
struct ParametersNode {
|
|
std::vector<std::unique_ptr<ExpressionNode>> parameters;
|
|
std::unique_ptr<gd::ExpressionParserError> diagnostic;
|
|
ExpressionParserLocation closingParenthesisLocation;
|
|
};
|
|
|
|
ParametersNode Parameters(
|
|
std::vector<gd::ParameterMetadata> parameterMetadata,
|
|
const gd::String &objectName = "",
|
|
const gd::String &behaviorName = "") {
|
|
std::vector<std::unique_ptr<ExpressionNode>> parameters;
|
|
gd::String lastObjectName = "";
|
|
|
|
// By convention, object is always the first parameter, and behavior the
|
|
// second one.
|
|
size_t parameterIndex =
|
|
WrittenParametersFirstIndex(objectName, behaviorName);
|
|
|
|
while (!IsEndReached()) {
|
|
SkipAllWhitespaces();
|
|
|
|
if (CheckIfChar(IsClosingParenthesis)) {
|
|
auto closingParenthesisLocation = SkipChar();
|
|
return ParametersNode{
|
|
std::move(parameters), nullptr, closingParenthesisLocation};
|
|
} 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, lastObjectName.empty() ? objectName : lastObjectName));
|
|
} else if (gd::ParameterMetadata::IsObject(type)) {
|
|
size_t parameterStartPosition = GetCurrentPosition();
|
|
std::unique_ptr<ExpressionNode> objectExpression = Expression(type);
|
|
|
|
// Memorize the last object name. By convention, parameters that
|
|
// require an object (mainly, "objectvar" and "behavior") should be
|
|
// placed after the object in the list of parameters (if possible,
|
|
// just after). Search "lastObjectName" in the codebase for other
|
|
// place where this convention is enforced.
|
|
if (auto identifierNode =
|
|
dynamic_cast<IdentifierNode *>(objectExpression.get())) {
|
|
lastObjectName = identifierNode->identifierName;
|
|
} else {
|
|
objectExpression->diagnostic =
|
|
gd::make_unique<ExpressionParserError>(
|
|
"malformed_object_parameter",
|
|
_("An object name was expected but something else was "
|
|
"written. Enter just the name of the object for this "
|
|
"parameter."),
|
|
parameterStartPosition,
|
|
GetCurrentPosition());
|
|
}
|
|
|
|
parameters.push_back(std::move(objectExpression));
|
|
} 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());
|
|
}
|
|
|
|
SkipAllWhitespaces();
|
|
SkipIfChar(IsParameterSeparator);
|
|
parameterIndex++;
|
|
}
|
|
}
|
|
|
|
ExpressionParserLocation invalidClosingParenthesisLocation;
|
|
return ParametersNode{
|
|
std::move(parameters),
|
|
RaiseSyntaxError(_("The list of parameters is not terminated. Add a "
|
|
"closing parenthesis to end the parameters.")),
|
|
invalidClosingParenthesisLocation};
|
|
}
|
|
///@}
|
|
|
|
/** \name Validators
|
|
* Return a diagnostic if any error is found
|
|
*/
|
|
///@{
|
|
std::unique_ptr<ExpressionParserDiagnostic> ValidateFunction(
|
|
const gd::String &type,
|
|
const gd::FunctionCallNode &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,
|
|
size_t position) {
|
|
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 -."),
|
|
position);
|
|
} 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)."),
|
|
position);
|
|
} 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."),
|
|
position);
|
|
} 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."),
|
|
position);
|
|
}
|
|
|
|
return gd::make_unique<ExpressionParserDiagnostic>();
|
|
}
|
|
///@}
|
|
|
|
/** \name Parsing tokens
|
|
* Read tokens or characters
|
|
*/
|
|
///@{
|
|
ExpressionParserLocation SkipChar() {
|
|
size_t startPosition = currentPosition;
|
|
return ExpressionParserLocation(startPosition, ++currentPosition);
|
|
}
|
|
|
|
void SkipAllWhitespaces() {
|
|
while (currentPosition < expression.size() &&
|
|
IsWhitespace(expression[currentPosition])) {
|
|
currentPosition++;
|
|
}
|
|
}
|
|
|
|
void SkipIfChar(
|
|
const std::function<bool(gd::String::value_type)> &predicate) {
|
|
if (CheckIfChar(predicate)) {
|
|
currentPosition++;
|
|
}
|
|
}
|
|
|
|
ExpressionParserLocation SkipNamespaceSeparator() {
|
|
size_t startPosition = currentPosition;
|
|
// Namespace separator is a special kind of delimiter as it is 2 characters
|
|
// long
|
|
if (IsNamespaceSeparator()) {
|
|
currentPosition += NAMESPACE_SEPARATOR.size();
|
|
}
|
|
|
|
return ExpressionParserLocation(startPosition, currentPosition);
|
|
}
|
|
|
|
bool CheckIfChar(
|
|
const std::function<bool(gd::String::value_type)> &predicate) {
|
|
if (currentPosition >= expression.size()) return false;
|
|
gd::String::value_type character = expression[currentPosition];
|
|
|
|
return predicate(character);
|
|
}
|
|
|
|
bool IsIdentifierAllowedChar() {
|
|
if (currentPosition >= expression.size()) return false;
|
|
gd::String::value_type character = expression[currentPosition];
|
|
|
|
// Quickly compare if the character is a number or ASCII character.
|
|
if ((character >= '0' && character <= '9') ||
|
|
(character >= 'A' && character <= 'Z') ||
|
|
(character >= 'a' && character <= 'z'))
|
|
return true;
|
|
|
|
// Otherwise do the full check against separators forbidden in identifiers.
|
|
if (!IsParameterSeparator(character) && !IsDot(character) &&
|
|
!IsQuote(character) && !IsBracket(character) &&
|
|
!IsExpressionOperator(character) && !IsTermOperator(character)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool IsWhitespace(gd::String::value_type character) {
|
|
return character == ' ' || character == '\n' || character == '\r';
|
|
}
|
|
|
|
static bool IsParameterSeparator(gd::String::value_type character) {
|
|
return character == ',';
|
|
}
|
|
|
|
static bool IsDot(gd::String::value_type character) {
|
|
return character == '.';
|
|
}
|
|
|
|
static bool IsQuote(gd::String::value_type character) {
|
|
return character == '"';
|
|
}
|
|
|
|
static bool IsBracket(gd::String::value_type character) {
|
|
return character == '(' || character == ')' || character == '[' ||
|
|
character == ']' || character == '{' || character == '}';
|
|
}
|
|
|
|
static bool IsOpeningParenthesis(gd::String::value_type character) {
|
|
return character == '(';
|
|
}
|
|
|
|
static bool IsClosingParenthesis(gd::String::value_type character) {
|
|
return character == ')';
|
|
}
|
|
|
|
static bool IsOpeningSquareBracket(gd::String::value_type character) {
|
|
return character == '[';
|
|
}
|
|
|
|
static bool IsClosingSquareBracket(gd::String::value_type character) {
|
|
return character == ']';
|
|
}
|
|
|
|
static bool IsExpressionEndingChar(gd::String::value_type character) {
|
|
return character == ',' || IsClosingParenthesis(character) ||
|
|
IsClosingSquareBracket(character);
|
|
}
|
|
|
|
static bool IsExpressionOperator(gd::String::value_type character) {
|
|
return character == '+' || character == '-' || character == '<' ||
|
|
character == '>' || character == '?' || character == '^' ||
|
|
character == '=' || character == '\\' || character == ':' ||
|
|
character == '!';
|
|
}
|
|
|
|
static bool IsUnaryOperator(gd::String::value_type character) {
|
|
return character == '+' || character == '-';
|
|
}
|
|
|
|
static bool IsTermOperator(gd::String::value_type character) {
|
|
return character == '/' || character == '*';
|
|
}
|
|
|
|
static bool IsNumberFirstChar(gd::String::value_type character) {
|
|
return character == '.' || (character >= '0' && character <= '9');
|
|
}
|
|
|
|
static bool IsNonZeroDigit(gd::String::value_type character) {
|
|
return (character >= '1' && character <= '9');
|
|
}
|
|
|
|
static bool IsZeroDigit(gd::String::value_type character) {
|
|
return character == '0';
|
|
}
|
|
|
|
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(); }
|
|
|
|
// A temporary node used when reading an identifier
|
|
struct IdentifierAndLocation {
|
|
gd::String name;
|
|
ExpressionParserLocation location;
|
|
};
|
|
|
|
IdentifierAndLocation ReadIdentifierName() {
|
|
gd::String name;
|
|
size_t startPosition = currentPosition;
|
|
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).
|
|
if (!name.empty() && IsWhitespace(name[name.size() - 1])) {
|
|
size_t lastCharacterPos = name.size() - 1;
|
|
while (lastCharacterPos < name.size() &&
|
|
IsWhitespace(name[lastCharacterPos])) {
|
|
lastCharacterPos--;
|
|
}
|
|
if ((lastCharacterPos + 1) < name.size()) {
|
|
name.erase(lastCharacterPos + 1);
|
|
}
|
|
}
|
|
|
|
IdentifierAndLocation identifierAndLocation{
|
|
name,
|
|
// The location is ignoring the trailing whitespace (only whitespace
|
|
// inside the identifier are allowed for compatibility).
|
|
ExpressionParserLocation(startPosition, startPosition + name.size())};
|
|
return identifierAndLocation;
|
|
}
|
|
|
|
std::unique_ptr<TextNode> ReadText();
|
|
|
|
std::unique_ptr<NumberNode> ReadNumber();
|
|
|
|
std::unique_ptr<EmptyNode> ReadUntilWhitespace(gd::String type) {
|
|
size_t startPosition = GetCurrentPosition();
|
|
gd::String text;
|
|
while (currentPosition < expression.size() &&
|
|
!IsWhitespace(expression[currentPosition])) {
|
|
text += expression[currentPosition];
|
|
currentPosition++;
|
|
}
|
|
|
|
auto node = gd::make_unique<EmptyNode>(type, text);
|
|
node->location =
|
|
ExpressionParserLocation(startPosition, GetCurrentPosition());
|
|
return node;
|
|
}
|
|
|
|
std::unique_ptr<EmptyNode> ReadUntilEnd(gd::String type) {
|
|
size_t startPosition = GetCurrentPosition();
|
|
gd::String text;
|
|
while (currentPosition < expression.size()) {
|
|
text += expression[currentPosition];
|
|
currentPosition++;
|
|
}
|
|
|
|
auto node = gd::make_unique<EmptyNode>(type, text);
|
|
node->location =
|
|
ExpressionParserLocation(startPosition, GetCurrentPosition());
|
|
return node;
|
|
}
|
|
|
|
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));
|
|
}
|
|
///@}
|
|
|
|
gd::String expression;
|
|
std::size_t currentPosition;
|
|
|
|
const gd::Platform &platform;
|
|
const gd::ObjectsContainer &globalObjectsContainer;
|
|
const gd::ObjectsContainer &objectsContainer;
|
|
|
|
static gd::String NAMESPACE_SEPARATOR;
|
|
};
|
|
|
|
} // namespace gd
|
|
|
|
#endif // GDCORE_EXPRESSIONPARSER2_H
|