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 
88  const std::vector<ExpressionParserError*> &
90  return deprecationWarnings;
91  };
92 
93  protected:
94  void OnVisitSubExpressionNode(SubExpressionNode& node) override {
95  ReportAnyError(node);
96  node.expression->Visit(*this);
97  }
98  void OnVisitOperatorNode(OperatorNode& node) override {
99  ReportAnyError(node);
100 
101  // The "required" type ("parentType") will be used when visiting the first operand.
102  // Note that it may be refined thanks to this first operand (see later).
103  node.leftHandSide->Visit(*this);
104  const Type leftType = childType; // Store the type of the first operand.
105 
106  if (leftType == Type::Number) {
107  if (node.op == ' ') {
108  RaiseError(gd::ExpressionParserError::ErrorType::SyntaxError,
109  "No operator found. Did you forget to enter an operator (like +, -, "
110  "* or /) between numbers or expressions?", node.rightHandSide->location);
111  }
112  }
113  else if (leftType == Type::String) {
114  if (node.op == ' ') {
115  RaiseError(gd::ExpressionParserError::ErrorType::SyntaxError,
116  "You must add the operator + between texts or expressions. For "
117  "example: \"Your name: \" + VariableString(PlayerName).", node.rightHandSide->location);
118  }
119  else if (node.op != '+') {
120  RaiseOperatorError(
121  _("You've used an operator that is not supported. Only + can be used "
122  "to concatenate texts."),
123  ExpressionParserLocation(node.leftHandSide->location.GetEndPosition() + 1, node.location.GetEndPosition()));
124  }
125  } else if (leftType == Type::Object) {
126  RaiseOperatorError(
127  _("Operators (+, -, /, *) can't be used with an object name. Remove "
128  "the operator."),
129  node.rightHandSide->location);
130  } else if (leftType == Type::Variable || leftType == Type::LegacyVariable) {
131  RaiseOperatorError(
132  _("Operators (+, -, /, *) can't be used in variable names. Remove "
133  "the operator from the variable name."),
134  node.rightHandSide->location);
135  }
136 
137  // The "required" type ("parentType") of the second operator is decided by:
138  // - the parent type. Unless it can (`number|string`) or should (`unknown`) be refined, then:
139  // - the first operand.
140  parentType = ShouldTypeBeRefined(parentType) ? leftType : parentType;
141  node.rightHandSide->Visit(*this);
142  const Type rightType = childType;
143 
144  // The type of the overall operator ("childType") is decided by:
145  // - the parent type. Unless it can (`number|string`) or should (`unknown`) be refined, then:
146  // - the first operand. Unless it can (`number|string`) or should (`unknown`) be refined, then:
147  // - the right operand (which got visited knowing the type of the first operand, so it's
148  // equal or strictly more precise than the left operand).
149  childType = ShouldTypeBeRefined(parentType) ? (ShouldTypeBeRefined(leftType) ? leftType : rightType) : parentType;
150  }
151  void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
152  ReportAnyError(node);
153  node.factor->Visit(*this);
154  const Type rightType = childType;
155 
156  if (rightType == Type::Number) {
157  if (node.op != '+' && node.op != '-') {
158  // This is actually a dead code because the parser takes them as
159  // binary operations with an empty left side which makes as much sense.
160  RaiseTypeError(
161  _("You've used an \"unary\" operator that is not supported. Operator "
162  "should be "
163  "either + or -."),
164  node.location);
165  }
166  } else if (rightType == Type::String) {
167  RaiseTypeError(
168  _("You've used an operator that is not supported. Only + can be used "
169  "to concatenate texts, and must be placed between two texts (or "
170  "expressions)."),
171  node.location);
172  } else if (rightType == Type::Object) {
173  RaiseTypeError(
174  _("Operators (+, -) can't be used with an object name. Remove the "
175  "operator."),
176  node.location);
177  } else if (rightType == Type::Variable || rightType == Type::LegacyVariable) {
178  RaiseTypeError(
179  _("Operators (+, -) can't be used in variable names. Remove "
180  "the operator from the variable name."),
181  node.location);
182  }
183  }
184  void OnVisitNumberNode(NumberNode& node) override {
185  ReportAnyError(node);
186  childType = Type::Number;
187  if (parentType == Type::String) {
188  RaiseTypeError(
189  _("You entered a number, but a text was expected (in quotes)."),
190  node.location);
191  } else if (parentType != Type::Number &&
192  parentType != Type::NumberOrString) {
193  RaiseTypeError(_("You entered a number, but this type was expected:") +
194  " " + TypeToString(parentType),
195  node.location);
196  }
197  }
198  void OnVisitTextNode(TextNode& node) override {
199  ReportAnyError(node);
200  childType = Type::String;
201  if (parentType == Type::Number) {
202  RaiseTypeError(_("You entered a text, but a number was expected."),
203  node.location);
204  } else if (parentType != Type::String &&
205  parentType != Type::NumberOrString) {
206  RaiseTypeError(_("You entered a text, but this type was expected:") +
207  " " + TypeToString(parentType),
208  node.location);
209  }
210  }
211  void OnVisitVariableNode(VariableNode& node) override {
212  ReportAnyError(node);
213 
214  if (parentType == Type::Variable ||
215  parentType == Type::VariableOrProperty ||
216  parentType == Type::VariableOrPropertyOrParameter) {
217  childType = parentType;
218 
219  CheckVariableExistence(node.location, node.name, node.child != nullptr);
220  if (node.child) {
221  node.child->Visit(*this);
222  }
223  } else if (parentType == Type::LegacyVariable) {
224  childType = parentType;
225 
226  if (node.child) {
227  node.child->Visit(*this);
228  }
229  } else if (parentType == Type::String || parentType == Type::Number ||
230  parentType == Type::NumberOrString) {
231  // The node represents a variable or an object variable in an expression waiting for its *value* to be returned.
232  childType = parentType;
233 
234  const auto& variablesContainersList = projectScopedContainers.GetVariablesContainersList();
235  const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
236  const auto& propertiesContainerList = projectScopedContainers.GetPropertiesContainersList();
237 
238  forbidsUsageOfBracketsBecauseParentIsObject = false;
239  projectScopedContainers.MatchIdentifierWithName<void>(node.name,
240  [&]() {
241  // This represents an object.
242  variableObjectName = node.name;
243  variableObjectNameLocation = node.nameLocation;
244  // While understood by the parser, it's forbidden to use the bracket notation just after
245  // an object name (`MyObject["MyVariable"]`).
246  forbidsUsageOfBracketsBecauseParentIsObject = true;
247  }, [&]() {
248  // This is a variable.
249  }, [&]() {
250  // This is a property.
251  // Being in this node implies that there is at least a child - which is not supported for properties.
252  RaiseTypeError(_("Accessing a child variable of a property is not possible - just write the property name."),
253  node.location);
254  }, [&]() {
255  // This is a parameter.
256  // Being in this node implies that there is at least a child - which is not supported for parameters.
257  RaiseTypeError(_("Accessing a child variable of a parameter is not possible - just write the parameter name."),
258  node.location);
259  }, [&]() {
260  // This is something else.
261  RaiseTypeError(_("No object, variable or property with this name found."),
262  node.location);
263  });
264 
265  if (node.child) {
266  node.child->Visit(*this);
267  }
268 
269  forbidsUsageOfBracketsBecauseParentIsObject = false;
270  } else {
271  RaiseTypeError(_("You entered a variable, but this type was expected:") +
272  " " + TypeToString(parentType),
273  node.location);
274 
275  if (node.child) {
276  node.child->Visit(*this);
277  }
278  }
279  }
280  void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
281  ReportAnyError(node);
282  // TODO Also check child-variables existence on a path with only VariableAccessor to raise non-fatal errors.
283  if (!variableObjectName.empty()) {
284  ValidateObjectVariableOrVariableOrProperty(variableObjectName,
285  variableObjectNameLocation,
286  node.name, node.nameLocation);
287  variableObjectName = "";
288  }
289  // In the case we accessed an object variable (`MyObject.MyVariable`),
290  // brackets can now be used (`MyObject.MyVariable["MyChildVariable"]` is now valid).
291  forbidsUsageOfBracketsBecauseParentIsObject = false;
292 
293  if (node.child) {
294  node.child->Visit(*this);
295  }
296  }
297  void OnVisitVariableBracketAccessorNode(
298  VariableBracketAccessorNode& node) override {
299  ReportAnyError(node);
300 
301  variableObjectName = "";
302  if (forbidsUsageOfBracketsBecauseParentIsObject) {
303  RaiseError(gd::ExpressionParserError::ErrorType::BracketsNotAllowedForObjects,
304  _("You can't use the brackets to access an object variable. "
305  "Use a dot followed by the variable name, like this: "
306  "`MyObject.MyVariable`."),
307  node.location);
308  }
309  forbidsUsageOfBracketsBecauseParentIsObject = false;
310 
311  Type currentParentType = parentType;
312  Type currentChildType = childType;
313  parentType = Type::NumberOrString;
314  auto parentParameterExtraInfo = currentParameterExtraInfo;
315  currentParameterExtraInfo = nullptr;
316  node.expression->Visit(*this);
317  currentParameterExtraInfo = parentParameterExtraInfo;
318  parentType = currentParentType;
319  childType = currentChildType;
320 
321  if (node.child) {
322  node.child->Visit(*this);
323  }
324  }
325  void OnVisitIdentifierNode(IdentifierNode& node) override {
326  ReportAnyError(node);
327  if (parentType == Type::String) {
328  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
329  // The identifier is not a variable, so either the variable is not properly declared
330  // or it's a text without quotes.
331  RaiseUnknownIdentifierError(_("You must wrap your text inside double quotes "
332  "(example: \"Hello world\")."),
333  node.location);
334  }
335  }
336  else if (parentType == Type::Number) {
337  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
338  // The identifier is not a variable, so the variable is not properly declared.
339  RaiseUnknownIdentifierError(_("You must enter a number."), node.location);
340  }
341  }
342  else if (parentType == Type::NumberOrString) {
343  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
344  // The identifier is not a variable, so either the variable is not properly declared
345  // or it's a text without quotes.
346  RaiseUnknownIdentifierError(
347  _("You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name."),
348  node.location);
349  }
350  } else if (parentType == Type::Variable ||
351  parentType == Type::VariableOrProperty ||
352  parentType == Type::VariableOrPropertyOrParameter) {
353  CheckVariableExistence(node.location, node.identifierName, !node.childIdentifierName.empty());
354  } else if (parentType != Type::Object &&
355  parentType != Type::LegacyVariable) {
356  // It can't happen.
357  RaiseTypeError(
358  _("You've entered a name, but this type was expected:") + " " + TypeToString(parentType),
359  node.location);
360  childType = parentType;
361  } else {
362  childType = parentType;
363  }
364  }
365  void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
366  ReportAnyError(node);
367  }
368  void OnVisitFunctionCallNode(FunctionCallNode& node) override {
369  childType = ValidateFunction(node);
370  }
371  void OnVisitEmptyNode(EmptyNode& node) override {
372  ReportAnyError(node);
373  gd::String message;
374  if (parentType == Type::Number) {
375  message = _("You must enter a number or a valid expression call.");
376  } else if (parentType == Type::String) {
377  message = _(
378  "You must enter a text (between quotes) or a valid expression call.");
379  } else if (parentType == Type::Variable || parentType == Type::LegacyVariable) {
380  message = _("You must enter a variable name.");
381  } else if (parentType == Type::Object) {
382  message = _("You must enter a valid object name.");
383  } else {
384  // It can't happen.
385  message = _("You must enter a valid expression.");
386  }
387  RaiseTypeError(message, node.location);
388  childType = Type::Empty;
389  }
390 
391 private:
392  enum Type {
393  Unknown = 0,
394  Number,
395  String,
396  NumberOrString,
397  Variable,
398  LegacyVariable,
399  Object,
400  Empty,
401  VariableOrProperty,
402  VariableOrPropertyOrParameter
403  };
404  Type ValidateFunction(const gd::FunctionCallNode& function);
405  bool ValidateObjectVariableOrVariableOrProperty(const gd::IdentifierNode& identifier);
406  bool ValidateObjectVariableOrVariableOrProperty(
407  const gd::String &identifierName,
408  const gd::ExpressionParserLocation identifierNameLocation,
409  const gd::String &childIdentifierName,
410  const gd::ExpressionParserLocation childIdentifierNameLocation);
411 
412  void CheckVariableExistence(const ExpressionParserLocation &location,
413  const gd::String &name, bool hasChild) {
414  if (!currentParameterExtraInfo ||
415  *currentParameterExtraInfo != "AllowUndeclaredVariable") {
416  projectScopedContainers.MatchIdentifierWithName<void>(
417  name,
418  [&]() {
419  // This represents an object.
420  RaiseVariableNameCollisionError(
421  _("This variable has the same name as an object. Consider "
422  "renaming one or the other."),
423  location, name);
424  },
425  [&]() {
426  // This is a variable.
427  },
428  [&]() {
429  // This is a property.
430  if (parentType != Type::VariableOrProperty &&
431  parentType != Type::VariableOrPropertyOrParameter) {
432  RaiseVariableNameCollisionError(
433  _("This variable has the same name as a property. Consider "
434  "renaming one or the other."),
435  location, name);
436  } else if (hasChild) {
437  RaiseMalformedVariableParameter(
438  _("Properties can't have children."), location, name);
439  }
440  },
441  [&]() {
442  // This is a parameter.
443  if (parentType != Type::VariableOrPropertyOrParameter) {
444  RaiseVariableNameCollisionError(
445  _("This variable has the same name as a parameter. Consider "
446  "renaming one or the other."),
447  location, name);
448  } else if (hasChild) {
449  RaiseMalformedVariableParameter(
450  _("Properties can't have children."), location, name);
451  }
452  },
453  [&]() {
454  // This is something else.
455  RaiseUndeclaredVariableError(
456  _("No variable with this name found."), location,
457  name);
458  });
459  }
460  }
461 
462  void ReportAnyError(const ExpressionNode& node, bool isFatal = true) {
463  if (node.diagnostic) {
464  // Syntax errors are holden by the AST nodes.
465  // It's fine to give pointers on them as the AST live longer than errors
466  // handling.
467  allErrors.push_back(node.diagnostic.get());
468  if (isFatal) {
469  fatalErrors.push_back(node.diagnostic.get());
470  }
471  }
472  }
473 
474  void RaiseError(gd::ExpressionParserError::ErrorType type,
475  const gd::String &message,
476  const ExpressionParserLocation &location, bool isFatal = true,
477  const gd::String &actualValue = "",
478  const gd::String &objectName = "") {
479  auto diagnostic = gd::make_unique<ExpressionParserError>(
480  type, message, location, actualValue, objectName);
481  allErrors.push_back(diagnostic.get());
482  if (isFatal) {
483  fatalErrors.push_back(diagnostic.get());
484  }
485  // Errors found by the validator are not holden by the AST nodes.
486  // They must be owned by the validator to keep living while errors are
487  // handled by the caller.
488  supplementalErrors.push_back(std::move(diagnostic));
489  }
490 
491  void RaiseUnknownIdentifierError(const gd::String &message,
492  const ExpressionParserLocation &location) {
493  RaiseError(gd::ExpressionParserError::ErrorType::UnknownIdentifier, message,
494  location);
495  }
496 
497  void RaiseUndeclaredVariableError(const gd::String &message,
498  const ExpressionParserLocation &location,
499  const gd::String &variableName,
500  const gd::String &objectName = "") {
501  RaiseError(gd::ExpressionParserError::ErrorType::UndeclaredVariable,
502  message, location, true, variableName, objectName);
503  }
504 
505  void RaiseVariableNameCollisionError(const gd::String &message,
506  const ExpressionParserLocation &location,
507  const gd::String &variableName,
508  const gd::String &objectName = "") {
509  RaiseError(gd::ExpressionParserError::ErrorType::VariableNameCollision,
510  message, location, false, variableName, objectName);
511  }
512 
513  void RaiseMalformedVariableParameter(const gd::String &message,
514  const ExpressionParserLocation &location,
515  const gd::String &variableName) {
516  RaiseError(gd::ExpressionParserError::ErrorType::MalformedVariableParameter,
517  message, location, true, variableName, "");
518  }
519 
520  void RaiseTypeError(const gd::String &message,
521  const ExpressionParserLocation &location,
522  bool isFatal = true) {
523  RaiseError(gd::ExpressionParserError::ErrorType::MismatchedType, message,
524  location, isFatal);
525  }
526 
527  void RaiseOperatorError(const gd::String &message,
528  const ExpressionParserLocation &location) {
529  RaiseError(gd::ExpressionParserError::ErrorType::InvalidOperator, message,
530  location);
531  }
532 
533  void ReadChildTypeFromVariable(gd::Variable::Type variableType) {
534  if (variableType == gd::Variable::Number) {
535  childType = Type::Number;
536  } else if (variableType == gd::Variable::String) {
537  childType = Type::String;
538  } else {
539  // Nothing - we don't know the precise type (this could be used as a string or as a number).
540  }
541  }
542 
543  static bool ShouldTypeBeRefined(Type type) {
544  return (type == Type::Unknown || type == Type::NumberOrString);
545  }
546 
547  static Type StringToType(const gd::String &type);
548  static const gd::String &TypeToString(Type type);
549  static const gd::String unknownTypeString;
550  static const gd::String numberTypeString;
551  static const gd::String stringTypeString;
552  static const gd::String numberOrStringTypeString;
553  static const gd::String variableTypeString;
554  static const gd::String legacyVariableTypeString;
555  static const gd::String objectTypeString;
556  static const gd::String identifierTypeString;
557  static const gd::String emptyTypeString;
558 
559  std::vector<ExpressionParserError*> fatalErrors;
560  std::vector<ExpressionParserError*> allErrors;
561  std::vector<ExpressionParserError*> deprecationWarnings;
562  std::vector<std::unique_ptr<ExpressionParserError>> supplementalErrors;
563  Type childType;
564  Type parentType;
565  bool forbidsUsageOfBracketsBecauseParentIsObject;
566  gd::String variableObjectName;
567  gd::ExpressionParserLocation variableObjectNameLocation;
568  const gd::String *currentParameterExtraInfo;
569  const gd::Platform &platform;
570  const gd::ProjectScopedContainers &projectScopedContainers;
571 };
572 
573 } // namespace gd
574 
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 * > & GetDeprecationWarnings()
Get all deprecation warnings.
Definition: ExpressionValidator.h:89
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:36
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:42
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:101
Definition: ExpressionParser2Node.h:25
A function call node (either free function, object function or object behavior function)....
Definition: ExpressionParser2Node.h:372
An identifier node, usually representing an object or a variable with an optional function name or ch...
Definition: ExpressionParser2Node.h:205
Definition: ExpressionParser2Node.h:116