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  parentType == Type::VariableOrProperty ||
208  parentType == Type::VariableOrPropertyOrParameter) {
209  childType = parentType;
210 
211  CheckVariableExistence(node.location, node.name, node.child != nullptr);
212  if (node.child) {
213  node.child->Visit(*this);
214  }
215  } else if (parentType == Type::LegacyVariable) {
216  childType = parentType;
217 
218  if (node.child) {
219  node.child->Visit(*this);
220  }
221  } else if (parentType == Type::String || parentType == Type::Number ||
222  parentType == Type::NumberOrString) {
223  // The node represents a variable or an object variable in an expression waiting for its *value* to be returned.
224  childType = parentType;
225 
226  const auto& variablesContainersList = projectScopedContainers.GetVariablesContainersList();
227  const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
228  const auto& propertiesContainerList = projectScopedContainers.GetPropertiesContainersList();
229 
230  forbidsUsageOfBracketsBecauseParentIsObject = false;
231  projectScopedContainers.MatchIdentifierWithName<void>(node.name,
232  [&]() {
233  // This represents an object.
234  variableObjectName = node.name;
235  variableObjectNameLocation = node.nameLocation;
236  // While understood by the parser, it's forbidden to use the bracket notation just after
237  // an object name (`MyObject["MyVariable"]`).
238  forbidsUsageOfBracketsBecauseParentIsObject = true;
239  }, [&]() {
240  // This is a variable.
241  }, [&]() {
242  // This is a property.
243  // Being in this node implies that there is at least a child - which is not supported for properties.
244  RaiseTypeError(_("Accessing a child variable of a property is not possible - just write the property name."),
245  node.location);
246  }, [&]() {
247  // This is a parameter.
248  // Being in this node implies that there is at least a child - which is not supported for parameters.
249  RaiseTypeError(_("Accessing a child variable of a parameter is not possible - just write the parameter name."),
250  node.location);
251  }, [&]() {
252  // This is something else.
253  RaiseTypeError(_("No object, variable or property with this name found."),
254  node.location);
255  });
256 
257  if (node.child) {
258  node.child->Visit(*this);
259  }
260 
261  forbidsUsageOfBracketsBecauseParentIsObject = false;
262  } else {
263  RaiseTypeError(_("You entered a variable, but this type was expected:") +
264  " " + TypeToString(parentType),
265  node.location);
266 
267  if (node.child) {
268  node.child->Visit(*this);
269  }
270  }
271  }
272  void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
273  ReportAnyError(node);
274  // TODO Also check child-variables existence on a path with only VariableAccessor to raise non-fatal errors.
275  if (!variableObjectName.empty()) {
276  ValidateObjectVariableOrVariableOrProperty(variableObjectName,
277  variableObjectNameLocation,
278  node.name, node.nameLocation);
279  variableObjectName = "";
280  }
281  // In the case we accessed an object variable (`MyObject.MyVariable`),
282  // brackets can now be used (`MyObject.MyVariable["MyChildVariable"]` is now valid).
283  forbidsUsageOfBracketsBecauseParentIsObject = false;
284 
285  if (node.child) {
286  node.child->Visit(*this);
287  }
288  }
289  void OnVisitVariableBracketAccessorNode(
290  VariableBracketAccessorNode& node) override {
291  ReportAnyError(node);
292 
293  variableObjectName = "";
294  if (forbidsUsageOfBracketsBecauseParentIsObject) {
295  RaiseError(gd::ExpressionParserError::ErrorType::BracketsNotAllowedForObjects,
296  _("You can't use the brackets to access an object variable. "
297  "Use a dot followed by the variable name, like this: "
298  "`MyObject.MyVariable`."),
299  node.location);
300  }
301  forbidsUsageOfBracketsBecauseParentIsObject = false;
302 
303  Type currentParentType = parentType;
304  Type currentChildType = childType;
305  parentType = Type::NumberOrString;
306  auto parentParameterExtraInfo = currentParameterExtraInfo;
307  currentParameterExtraInfo = nullptr;
308  node.expression->Visit(*this);
309  currentParameterExtraInfo = parentParameterExtraInfo;
310  parentType = currentParentType;
311  childType = currentChildType;
312 
313  if (node.child) {
314  node.child->Visit(*this);
315  }
316  }
317  void OnVisitIdentifierNode(IdentifierNode& node) override {
318  ReportAnyError(node);
319  if (parentType == Type::String) {
320  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
321  // The identifier is not a variable, so either the variable is not properly declared
322  // or it's a text without quotes.
323  RaiseUnknownIdentifierError(_("You must wrap your text inside double quotes "
324  "(example: \"Hello world\")."),
325  node.location);
326  }
327  }
328  else if (parentType == Type::Number) {
329  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
330  // The identifier is not a variable, so the variable is not properly declared.
331  RaiseUnknownIdentifierError(_("You must enter a number."), node.location);
332  }
333  }
334  else if (parentType == Type::NumberOrString) {
335  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
336  // The identifier is not a variable, so either the variable is not properly declared
337  // or it's a text without quotes.
338  RaiseUnknownIdentifierError(
339  _("You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name."),
340  node.location);
341  }
342  } else if (parentType == Type::Variable ||
343  parentType == Type::VariableOrProperty ||
344  parentType == Type::VariableOrPropertyOrParameter) {
345  CheckVariableExistence(node.location, node.identifierName, !node.childIdentifierName.empty());
346  } else if (parentType != Type::Object &&
347  parentType != Type::LegacyVariable) {
348  // It can't happen.
349  RaiseTypeError(
350  _("You've entered a name, but this type was expected:") + " " + TypeToString(parentType),
351  node.location);
352  childType = parentType;
353  } else {
354  childType = parentType;
355  }
356  }
357  void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
358  ReportAnyError(node);
359  }
360  void OnVisitFunctionCallNode(FunctionCallNode& node) override {
361  childType = ValidateFunction(node);
362  }
363  void OnVisitEmptyNode(EmptyNode& node) override {
364  ReportAnyError(node);
365  gd::String message;
366  if (parentType == Type::Number) {
367  message = _("You must enter a number or a valid expression call.");
368  } else if (parentType == Type::String) {
369  message = _(
370  "You must enter a text (between quotes) or a valid expression call.");
371  } else if (parentType == Type::Variable || parentType == Type::LegacyVariable) {
372  message = _("You must enter a variable name.");
373  } else if (parentType == Type::Object) {
374  message = _("You must enter a valid object name.");
375  } else {
376  // It can't happen.
377  message = _("You must enter a valid expression.");
378  }
379  RaiseTypeError(message, node.location);
380  childType = Type::Empty;
381  }
382 
383 private:
384  enum Type {
385  Unknown = 0,
386  Number,
387  String,
388  NumberOrString,
389  Variable,
390  LegacyVariable,
391  Object,
392  Empty,
393  VariableOrProperty,
394  VariableOrPropertyOrParameter
395  };
396  Type ValidateFunction(const gd::FunctionCallNode& function);
397  bool ValidateObjectVariableOrVariableOrProperty(const gd::IdentifierNode& identifier);
398  bool ValidateObjectVariableOrVariableOrProperty(
399  const gd::String &identifierName,
400  const gd::ExpressionParserLocation identifierNameLocation,
401  const gd::String &childIdentifierName,
402  const gd::ExpressionParserLocation childIdentifierNameLocation);
403 
404  void CheckVariableExistence(const ExpressionParserLocation &location,
405  const gd::String &name, bool hasChild) {
406  if (!currentParameterExtraInfo ||
407  *currentParameterExtraInfo != "AllowUndeclaredVariable") {
408  projectScopedContainers.MatchIdentifierWithName<void>(
409  name,
410  [&]() {
411  // This represents an object.
412  RaiseVariableNameCollisionError(
413  _("This variable has the same name as an object. Consider "
414  "renaming one or the other."),
415  location, name);
416  },
417  [&]() {
418  // This is a variable.
419  },
420  [&]() {
421  // This is a property.
422  if (parentType != Type::VariableOrProperty &&
423  parentType != Type::VariableOrPropertyOrParameter) {
424  RaiseVariableNameCollisionError(
425  _("This variable has the same name as a property. Consider "
426  "renaming one or the other."),
427  location, name);
428  } else if (hasChild) {
429  RaiseMalformedVariableParameter(
430  _("Properties can't have children."), location, name);
431  }
432  },
433  [&]() {
434  // This is a parameter.
435  if (parentType != Type::VariableOrPropertyOrParameter) {
436  RaiseVariableNameCollisionError(
437  _("This variable has the same name as a parameter. Consider "
438  "renaming one or the other."),
439  location, name);
440  } else if (hasChild) {
441  RaiseMalformedVariableParameter(
442  _("Properties can't have children."), location, name);
443  }
444  },
445  [&]() {
446  // This is something else.
447  RaiseUndeclaredVariableError(
448  _("No variable with this name found."), location,
449  name);
450  });
451  }
452  }
453 
454  void ReportAnyError(const ExpressionNode& node, bool isFatal = true) {
455  if (node.diagnostic) {
456  // Syntax errors are holden by the AST nodes.
457  // It's fine to give pointers on them as the AST live longer than errors
458  // handling.
459  allErrors.push_back(node.diagnostic.get());
460  if (isFatal) {
461  fatalErrors.push_back(node.diagnostic.get());
462  }
463  }
464  }
465 
466  void RaiseError(gd::ExpressionParserError::ErrorType type,
467  const gd::String &message,
468  const ExpressionParserLocation &location, bool isFatal = true,
469  const gd::String &actualValue = "",
470  const gd::String &objectName = "") {
471  auto diagnostic = gd::make_unique<ExpressionParserError>(
472  type, message, location, actualValue, objectName);
473  allErrors.push_back(diagnostic.get());
474  if (isFatal) {
475  fatalErrors.push_back(diagnostic.get());
476  }
477  // Errors found by the validator are not holden by the AST nodes.
478  // They must be owned by the validator to keep living while errors are
479  // handled by the caller.
480  supplementalErrors.push_back(std::move(diagnostic));
481  }
482 
483  void RaiseUnknownIdentifierError(const gd::String &message,
484  const ExpressionParserLocation &location) {
485  RaiseError(gd::ExpressionParserError::ErrorType::UnknownIdentifier, message,
486  location);
487  }
488 
489  void RaiseUndeclaredVariableError(const gd::String &message,
490  const ExpressionParserLocation &location,
491  const gd::String &variableName,
492  const gd::String &objectName = "") {
493  RaiseError(gd::ExpressionParserError::ErrorType::UndeclaredVariable,
494  message, location, true, variableName, objectName);
495  }
496 
497  void RaiseVariableNameCollisionError(const gd::String &message,
498  const ExpressionParserLocation &location,
499  const gd::String &variableName,
500  const gd::String &objectName = "") {
501  RaiseError(gd::ExpressionParserError::ErrorType::VariableNameCollision,
502  message, location, false, variableName, objectName);
503  }
504 
505  void RaiseMalformedVariableParameter(const gd::String &message,
506  const ExpressionParserLocation &location,
507  const gd::String &variableName) {
508  RaiseError(gd::ExpressionParserError::ErrorType::MalformedVariableParameter,
509  message, location, true, variableName, "");
510  }
511 
512  void RaiseTypeError(const gd::String &message,
513  const ExpressionParserLocation &location,
514  bool isFatal = true) {
515  RaiseError(gd::ExpressionParserError::ErrorType::MismatchedType, message,
516  location, isFatal);
517  }
518 
519  void RaiseOperatorError(const gd::String &message,
520  const ExpressionParserLocation &location) {
521  RaiseError(gd::ExpressionParserError::ErrorType::InvalidOperator, message,
522  location);
523  }
524 
525  void ReadChildTypeFromVariable(gd::Variable::Type variableType) {
526  if (variableType == gd::Variable::Number) {
527  childType = Type::Number;
528  } else if (variableType == gd::Variable::String) {
529  childType = Type::String;
530  } else {
531  // Nothing - we don't know the precise type (this could be used as a string or as a number).
532  }
533  }
534 
535  static bool ShouldTypeBeRefined(Type type) {
536  return (type == Type::Unknown || type == Type::NumberOrString);
537  }
538 
539  static Type StringToType(const gd::String &type);
540  static const gd::String &TypeToString(Type type);
541  static const gd::String unknownTypeString;
542  static const gd::String numberTypeString;
543  static const gd::String stringTypeString;
544  static const gd::String numberOrStringTypeString;
545  static const gd::String variableTypeString;
546  static const gd::String legacyVariableTypeString;
547  static const gd::String objectTypeString;
548  static const gd::String identifierTypeString;
549  static const gd::String emptyTypeString;
550 
551  std::vector<ExpressionParserError*> fatalErrors;
552  std::vector<ExpressionParserError*> allErrors;
553  std::vector<std::unique_ptr<ExpressionParserError>> supplementalErrors;
554  Type childType;
555  Type parentType;
556  bool forbidsUsageOfBracketsBecauseParentIsObject;
557  gd::String variableObjectName;
558  gd::ExpressionParserLocation variableObjectNameLocation;
559  const gd::String *currentParameterExtraInfo;
560  const gd::Platform &platform;
561  const gd::ProjectScopedContainers &projectScopedContainers;
562 };
563 
564 } // namespace gd
565 
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