GDevelop Core
Core library for developing platforms and tools compatible with GDevelop.
ExpressionValidator.h
1 /*
2  * GDevelop Core
3  * Copyright 2008-present Florian Rival ([email protected]). All rights
4  * reserved. This project is released under the MIT License.
5  */
6 #pragma once
7 
8 #include <memory>
9 #include <vector>
10 #include "GDCore/Events/Parsers/ExpressionParser2Node.h"
11 #include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
12 #include "GDCore/Tools/MakeUnique.h"
14 #include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
15 #include "GDCore/Project/ProjectScopedContainers.h"
16 #include "GDCore/Project/VariablesContainersList.h"
17 #include "GDCore/Project/VariablesContainer.h"
18 
19 namespace gd {
20 class Expression;
21 class ObjectsContainer;
22 class VariablesContainer;
23 class Platform;
24 class ParameterMetadata;
25 class ExpressionMetadata;
26 class VariablesContainersList;
27 class ProjectScopedContainers;
28 } // namespace gd
29 
30 namespace gd {
31 
39  public:
40  ExpressionValidator(const gd::Platform &platform_,
41  const gd::ProjectScopedContainers & projectScopedContainers_,
42  const gd::String &rootType_,
43  const gd::String &extraInfo_ = "")
44  : platform(platform_),
45  projectScopedContainers(projectScopedContainers_),
46  parentType(StringToType(gd::ValueTypeMetadata::GetExpressionPrimitiveValueType(rootType_))),
47  childType(Type::Unknown),
48  forbidsUsageOfBracketsBecauseParentIsObject(false),
49  currentParameterExtraInfo(&extraInfo_),
50  variableObjectName(),
51  variableObjectNameLocation() {};
52  virtual ~ExpressionValidator(){};
53 
58  static bool HasNoErrors(const gd::Platform &platform,
59  const gd::ProjectScopedContainers & projectScopedContainers,
60  const gd::String &rootType,
61  gd::ExpressionNode& node) {
62  gd::ExpressionValidator validator(platform, projectScopedContainers, rootType);
63  node.Visit(validator);
64  return validator.GetAllErrors().empty();
65  }
66 
72  const std::vector<ExpressionParserError*>& GetFatalErrors() {
73  return fatalErrors;
74  };
75 
81  const std::vector<ExpressionParserError*>& GetAllErrors() {
82  return allErrors;
83  };
84 
85  protected:
86  void OnVisitSubExpressionNode(SubExpressionNode& node) override {
87  ReportAnyError(node);
88  node.expression->Visit(*this);
89  }
90  void OnVisitOperatorNode(OperatorNode& node) override {
91  ReportAnyError(node);
92 
93  // The "required" type ("parentType") will be used when visiting the first operand.
94  // Note that it may be refined thanks to this first operand (see later).
95  node.leftHandSide->Visit(*this);
96  const Type leftType = childType; // Store the type of the first operand.
97 
98  if (leftType == Type::Number) {
99  if (node.op == ' ') {
100  RaiseError(gd::ExpressionParserError::ErrorType::SyntaxError,
101  "No operator found. Did you forget to enter an operator (like +, -, "
102  "* or /) between numbers or expressions?", node.rightHandSide->location);
103  }
104  }
105  else if (leftType == Type::String) {
106  if (node.op == ' ') {
107  RaiseError(gd::ExpressionParserError::ErrorType::SyntaxError,
108  "You must add the operator + between texts or expressions. For "
109  "example: \"Your name: \" + VariableString(PlayerName).", node.rightHandSide->location);
110  }
111  else if (node.op != '+') {
112  RaiseOperatorError(
113  _("You've used an operator that is not supported. Only + can be used "
114  "to concatenate texts."),
115  ExpressionParserLocation(node.leftHandSide->location.GetEndPosition() + 1, node.location.GetEndPosition()));
116  }
117  } else if (leftType == Type::Object) {
118  RaiseOperatorError(
119  _("Operators (+, -, /, *) can't be used with an object name. Remove "
120  "the operator."),
121  node.rightHandSide->location);
122  } else if (leftType == Type::Variable || leftType == Type::LegacyVariable) {
123  RaiseOperatorError(
124  _("Operators (+, -, /, *) can't be used in variable names. Remove "
125  "the operator from the variable name."),
126  node.rightHandSide->location);
127  }
128 
129  // The "required" type ("parentType") of the second operator is decided by:
130  // - the parent type. Unless it can (`number|string`) or should (`unknown`) be refined, then:
131  // - the first operand.
132  parentType = ShouldTypeBeRefined(parentType) ? leftType : parentType;
133  node.rightHandSide->Visit(*this);
134  const Type rightType = childType;
135 
136  // The type of the overall operator ("childType") is decided by:
137  // - the parent type. Unless it can (`number|string`) or should (`unknown`) be refined, then:
138  // - the first operand. Unless it can (`number|string`) or should (`unknown`) be refined, then:
139  // - the right operand (which got visited knowing the type of the first operand, so it's
140  // equal or strictly more precise than the left operand).
141  childType = ShouldTypeBeRefined(parentType) ? (ShouldTypeBeRefined(leftType) ? leftType : rightType) : parentType;
142  }
143  void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
144  ReportAnyError(node);
145  node.factor->Visit(*this);
146  const Type rightType = childType;
147 
148  if (rightType == Type::Number) {
149  if (node.op != '+' && node.op != '-') {
150  // This is actually a dead code because the parser takes them as
151  // binary operations with an empty left side which makes as much sense.
152  RaiseTypeError(
153  _("You've used an \"unary\" operator that is not supported. Operator "
154  "should be "
155  "either + or -."),
156  node.location);
157  }
158  } else if (rightType == Type::String) {
159  RaiseTypeError(
160  _("You've used an operator that is not supported. Only + can be used "
161  "to concatenate texts, and must be placed between two texts (or "
162  "expressions)."),
163  node.location);
164  } else if (rightType == Type::Object) {
165  RaiseTypeError(
166  _("Operators (+, -) can't be used with an object name. Remove the "
167  "operator."),
168  node.location);
169  } else if (rightType == Type::Variable || rightType == Type::LegacyVariable) {
170  RaiseTypeError(
171  _("Operators (+, -) can't be used in variable names. Remove "
172  "the operator from the variable name."),
173  node.location);
174  }
175  }
176  void OnVisitNumberNode(NumberNode& node) override {
177  ReportAnyError(node);
178  childType = Type::Number;
179  if (parentType == Type::String) {
180  RaiseTypeError(
181  _("You entered a number, but a text was expected (in quotes)."),
182  node.location);
183  } else if (parentType != Type::Number &&
184  parentType != Type::NumberOrString) {
185  RaiseTypeError(_("You entered a number, but this type was expected:") +
186  " " + TypeToString(parentType),
187  node.location);
188  }
189  }
190  void OnVisitTextNode(TextNode& node) override {
191  ReportAnyError(node);
192  childType = Type::String;
193  if (parentType == Type::Number) {
194  RaiseTypeError(_("You entered a text, but a number was expected."),
195  node.location);
196  } else if (parentType != Type::String &&
197  parentType != Type::NumberOrString) {
198  RaiseTypeError(_("You entered a text, but this type was expected:") +
199  " " + TypeToString(parentType),
200  node.location);
201  }
202  }
203  void OnVisitVariableNode(VariableNode& node) override {
204  ReportAnyError(node);
205 
206  if (parentType == Type::Variable) {
207  childType = parentType;
208 
209  CheckVariableExistence(node.location, node.name);
210  if (node.child) {
211  node.child->Visit(*this);
212  }
213  } else if (parentType == Type::LegacyVariable) {
214  childType = parentType;
215 
216  if (node.child) {
217  node.child->Visit(*this);
218  }
219  } else if (parentType == Type::String || parentType == Type::Number || parentType == Type::NumberOrString) {
220  // The node represents a variable or an object variable in an expression waiting for its *value* to be returned.
221  childType = parentType;
222 
223  const auto& variablesContainersList = projectScopedContainers.GetVariablesContainersList();
224  const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
225  const auto& propertiesContainerList = projectScopedContainers.GetPropertiesContainersList();
226 
227  forbidsUsageOfBracketsBecauseParentIsObject = false;
228  projectScopedContainers.MatchIdentifierWithName<void>(node.name,
229  [&]() {
230  // This represents an object.
231  variableObjectName = node.name;
232  variableObjectNameLocation = node.nameLocation;
233  // While understood by the parser, it's forbidden to use the bracket notation just after
234  // an object name (`MyObject["MyVariable"]`).
235  forbidsUsageOfBracketsBecauseParentIsObject = true;
236  }, [&]() {
237  // This is a variable.
238  }, [&]() {
239  // This is a property.
240  // Being in this node implies that there is at least a child - which is not supported for properties.
241  RaiseTypeError(_("Accessing a child variable of a property is not possible - just write the property name."),
242  node.location);
243  }, [&]() {
244  // This is a parameter.
245  // Being in this node implies that there is at least a child - which is not supported for parameters.
246  RaiseTypeError(_("Accessing a child variable of a parameter is not possible - just write the parameter name."),
247  node.location);
248  }, [&]() {
249  // This is something else.
250  RaiseTypeError(_("No object, variable or property with this name found."),
251  node.location);
252  });
253 
254  if (node.child) {
255  node.child->Visit(*this);
256  }
257 
258  forbidsUsageOfBracketsBecauseParentIsObject = false;
259  } else {
260  RaiseTypeError(_("You entered a variable, but this type was expected:") +
261  " " + TypeToString(parentType),
262  node.location);
263 
264  if (node.child) {
265  node.child->Visit(*this);
266  }
267  }
268  }
269  void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
270  ReportAnyError(node);
271  // TODO Also check child-variables existence on a path with only VariableAccessor to raise non-fatal errors.
272  if (!variableObjectName.empty()) {
273  ValidateObjectVariableOrVariableOrProperty(variableObjectName,
274  variableObjectNameLocation,
275  node.name, node.nameLocation);
276  variableObjectName = "";
277  }
278  // In the case we accessed an object variable (`MyObject.MyVariable`),
279  // brackets can now be used (`MyObject.MyVariable["MyChildVariable"]` is now valid).
280  forbidsUsageOfBracketsBecauseParentIsObject = false;
281 
282  if (node.child) {
283  node.child->Visit(*this);
284  }
285  }
286  void OnVisitVariableBracketAccessorNode(
287  VariableBracketAccessorNode& node) override {
288  ReportAnyError(node);
289 
290  variableObjectName = "";
291  if (forbidsUsageOfBracketsBecauseParentIsObject) {
292  RaiseError(gd::ExpressionParserError::ErrorType::BracketsNotAllowedForObjects,
293  _("You can't use the brackets to access an object variable. "
294  "Use a dot followed by the variable name, like this: "
295  "`MyObject.MyVariable`."),
296  node.location);
297  }
298  forbidsUsageOfBracketsBecauseParentIsObject = false;
299 
300  Type currentParentType = parentType;
301  Type currentChildType = childType;
302  parentType = Type::NumberOrString;
303  auto parentParameterExtraInfo = currentParameterExtraInfo;
304  currentParameterExtraInfo = nullptr;
305  node.expression->Visit(*this);
306  currentParameterExtraInfo = parentParameterExtraInfo;
307  parentType = currentParentType;
308  childType = currentChildType;
309 
310  if (node.child) {
311  node.child->Visit(*this);
312  }
313  }
314  void OnVisitIdentifierNode(IdentifierNode& node) override {
315  ReportAnyError(node);
316  if (parentType == Type::String) {
317  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
318  // The identifier is not a variable, so either the variable is not properly declared
319  // or it's a text without quotes.
320  RaiseUnknownIdentifierError(_("You must wrap your text inside double quotes "
321  "(example: \"Hello world\")."),
322  node.location);
323  }
324  }
325  else if (parentType == Type::Number) {
326  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
327  // The identifier is not a variable, so the variable is not properly declared.
328  RaiseUnknownIdentifierError(_("You must enter a number."), node.location);
329  }
330  }
331  else if (parentType == Type::NumberOrString) {
332  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
333  // The identifier is not a variable, so either the variable is not properly declared
334  // or it's a text without quotes.
335  RaiseUnknownIdentifierError(
336  _("You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name."),
337  node.location);
338  }
339  }
340  else if (parentType == Type::Variable) {
341  CheckVariableExistence(node.location, node.identifierName);
342  }
343  else if (parentType != Type::Object && parentType != Type::LegacyVariable) {
344  // It can't happen.
345  RaiseTypeError(
346  _("You've entered a name, but this type was expected:") + " " + TypeToString(parentType),
347  node.location);
348  childType = parentType;
349  } else {
350  childType = parentType;
351  }
352  }
353  void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
354  ReportAnyError(node);
355  }
356  void OnVisitFunctionCallNode(FunctionCallNode& node) override {
357  childType = ValidateFunction(node);
358  }
359  void OnVisitEmptyNode(EmptyNode& node) override {
360  ReportAnyError(node);
361  gd::String message;
362  if (parentType == Type::Number) {
363  message = _("You must enter a number or a valid expression call.");
364  } else if (parentType == Type::String) {
365  message = _(
366  "You must enter a text (between quotes) or a valid expression call.");
367  } else if (parentType == Type::Variable || parentType == Type::LegacyVariable) {
368  message = _("You must enter a variable name.");
369  } else if (parentType == Type::Object) {
370  message = _("You must enter a valid object name.");
371  } else {
372  // It can't happen.
373  message = _("You must enter a valid expression.");
374  }
375  RaiseTypeError(message, node.location);
376  childType = Type::Empty;
377  }
378 
379  private:
380  enum Type {Unknown = 0, Number, String, NumberOrString, Variable, LegacyVariable, Object, Empty};
381  Type ValidateFunction(const gd::FunctionCallNode& function);
382  bool ValidateObjectVariableOrVariableOrProperty(const gd::IdentifierNode& identifier);
383  bool ValidateObjectVariableOrVariableOrProperty(
384  const gd::String &identifierName,
385  const gd::ExpressionParserLocation identifierNameLocation,
386  const gd::String &childIdentifierName,
387  const gd::ExpressionParserLocation childIdentifierNameLocation);
388 
389  void CheckVariableExistence(const ExpressionParserLocation &location, const gd::String& name) {
390  if (!currentParameterExtraInfo || *currentParameterExtraInfo != "AllowUndeclaredVariable") {
391  projectScopedContainers.MatchIdentifierWithName<void>(
392  name,
393  [&]() {
394  // This represents an object.
395  RaiseVariableNameCollisionError(
396  _("This variable has the same name as an object. Consider "
397  "renaming one or the other."),
398  location, name);
399  },
400  [&]() {
401  // This is a variable.
402  },
403  [&]() {
404  // This is a property.
405  // This error won't happen unless the priority is changed.
406  RaiseVariableNameCollisionError(
407  _("This variable has the same name as a property. Consider "
408  "renaming one or the other."),
409  location, name);
410  },
411  [&]() {
412  // This is a parameter.
413  // This error won't happen unless the priority is changed.
414  RaiseVariableNameCollisionError(
415  _("This variable has the same name as a parameter. Consider "
416  "renaming one or the other."),
417  location, name);
418  },
419  [&]() {
420  // This is something else.
421  RaiseUndeclaredVariableError(
422  _("No variable with this name found."), location,
423  name);
424  });
425  }
426  }
427 
428  void ReportAnyError(const ExpressionNode& node, bool isFatal = true) {
429  if (node.diagnostic) {
430  // Syntax errors are holden by the AST nodes.
431  // It's fine to give pointers on them as the AST live longer than errors
432  // handling.
433  allErrors.push_back(node.diagnostic.get());
434  if (isFatal) {
435  fatalErrors.push_back(node.diagnostic.get());
436  }
437  }
438  }
439 
440  void RaiseError(gd::ExpressionParserError::ErrorType type,
441  const gd::String &message,
442  const ExpressionParserLocation &location, bool isFatal = true,
443  const gd::String &actualValue = "",
444  const gd::String &objectName = "") {
445  auto diagnostic = gd::make_unique<ExpressionParserError>(
446  type, message, location, actualValue, objectName);
447  allErrors.push_back(diagnostic.get());
448  if (isFatal) {
449  fatalErrors.push_back(diagnostic.get());
450  }
451  // Errors found by the validator are not holden by the AST nodes.
452  // They must be owned by the validator to keep living while errors are
453  // handled by the caller.
454  supplementalErrors.push_back(std::move(diagnostic));
455  }
456 
457  void RaiseUnknownIdentifierError(const gd::String &message,
458  const ExpressionParserLocation &location) {
459  RaiseError(gd::ExpressionParserError::ErrorType::UnknownIdentifier, message,
460  location);
461  }
462 
463  void RaiseUndeclaredVariableError(const gd::String &message,
464  const ExpressionParserLocation &location,
465  const gd::String &variableName,
466  const gd::String &objectName = "") {
467  RaiseError(gd::ExpressionParserError::ErrorType::UndeclaredVariable,
468  message, location, true, variableName, objectName);
469  }
470 
471  void RaiseVariableNameCollisionError(const gd::String &message,
472  const ExpressionParserLocation &location,
473  const gd::String &variableName,
474  const gd::String &objectName = "") {
475  RaiseError(gd::ExpressionParserError::ErrorType::VariableNameCollision,
476  message, location, false, variableName, objectName);
477  }
478 
479  void RaiseTypeError(const gd::String &message,
480  const ExpressionParserLocation &location,
481  bool isFatal = true) {
482  RaiseError(gd::ExpressionParserError::ErrorType::MismatchedType, message,
483  location, isFatal);
484  }
485 
486  void RaiseOperatorError(const gd::String &message,
487  const ExpressionParserLocation &location) {
488  RaiseError(gd::ExpressionParserError::ErrorType::InvalidOperator, message,
489  location);
490  }
491 
492  void ReadChildTypeFromVariable(gd::Variable::Type variableType) {
493  if (variableType == gd::Variable::Number) {
494  childType = Type::Number;
495  } else if (variableType == gd::Variable::String) {
496  childType = Type::String;
497  } else {
498  // Nothing - we don't know the precise type (this could be used as a string or as a number).
499  }
500  }
501 
502  static bool ShouldTypeBeRefined(Type type) {
503  return (type == Type::Unknown || type == Type::NumberOrString);
504  }
505 
506  static Type StringToType(const gd::String &type);
507  static const gd::String &TypeToString(Type type);
508  static const gd::String unknownTypeString;
509  static const gd::String numberTypeString;
510  static const gd::String stringTypeString;
511  static const gd::String numberOrStringTypeString;
512  static const gd::String variableTypeString;
513  static const gd::String legacyVariableTypeString;
514  static const gd::String objectTypeString;
515  static const gd::String identifierTypeString;
516  static const gd::String emptyTypeString;
517 
518  std::vector<ExpressionParserError*> fatalErrors;
519  std::vector<ExpressionParserError*> allErrors;
520  std::vector<std::unique_ptr<ExpressionParserError>> supplementalErrors;
521  Type childType;
522  Type parentType;
523  bool forbidsUsageOfBracketsBecauseParentIsObject;
524  gd::String variableObjectName;
525  gd::ExpressionParserLocation variableObjectNameLocation;
526  const gd::String *currentParameterExtraInfo;
527  const gd::Platform &platform;
528  const gd::ProjectScopedContainers &projectScopedContainers;
529 };
530 
531 } // namespace gd
532 
The interface for any worker class ("visitor" pattern) that want to interact with the nodes of a pars...
Definition: ExpressionParser2NodeWorker.h:36
Validate that an expression is properly written by returning any error attached to the nodes during p...
Definition: ExpressionValidator.h:38
const std::vector< ExpressionParserError * > & GetFatalErrors()
Get only the fatal errors.
Definition: ExpressionValidator.h:72
const std::vector< ExpressionParserError * > & GetAllErrors()
Get all the errors.
Definition: ExpressionValidator.h:81
static bool HasNoErrors(const gd::Platform &platform, const gd::ProjectScopedContainers &projectScopedContainers, const gd::String &rootType, gd::ExpressionNode &node)
Helper function to check if a given node does not contain any error including non-fatal ones.
Definition: ExpressionValidator.h:58
Base class for implementing a platform.
Definition: Platform.h:42
Holds references to variables, objects, properties and other containers.
Definition: ProjectScopedContainers.h:34
String represents an UTF8 encoded string.
Definition: String.h:33
static const gd::String & GetExpressionPrimitiveValueType(const gd::String &parameterType)
Return the expression type from the parameter type. Declinations of "number" and "string" types (like...
Definition: ValueTypeMetadata.cpp:41
Type
Definition: Variable.h:32
Definition: CommonTools.h:24
Type
Type of JSON value.
Definition: rapidjson.h:603
The base node, from which all nodes in the tree of an expression inherits from.
Definition: ExpressionParser2Node.h:100
Definition: ExpressionParser2Node.h:25
A function call node (either free function, object function or object behavior function)....
Definition: ExpressionParser2Node.h:371
An identifier node, usually representing an object or a variable with an optional function name or ch...
Definition: ExpressionParser2Node.h:204
Definition: ExpressionParser2Node.h:115