mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
1058 lines
41 KiB
C++
1058 lines
41 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);
|
|
}
|
|
|
|
/**
|
|
* 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());
|
|
|
|
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
|