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 #ifndef GDCORE_EXPRESSIONVALIDATOR_H
7 #define GDCORE_EXPRESSIONVALIDATOR_H
8 
9 #include <memory>
10 #include <vector>
11 #include "GDCore/Events/Parsers/ExpressionParser2Node.h"
12 #include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
13 #include "GDCore/Tools/MakeUnique.h"
15 #include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
16 #include "GDCore/Project/ProjectScopedContainers.h"
17 #include "GDCore/Project/VariablesContainersList.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  : platform(platform_),
44  projectScopedContainers(projectScopedContainers_),
45  parentType(StringToType(gd::ParameterMetadata::GetExpressionValueType(rootType_))),
46  childType(Type::Unknown),
47  forbidsUsageOfBracketsBecauseParentIsObject(false) {};
48  virtual ~ExpressionValidator(){};
49 
54  static bool HasNoErrors(const gd::Platform &platform,
55  const gd::ProjectScopedContainers & projectScopedContainers,
56  const gd::String &rootType,
57  gd::ExpressionNode& node) {
58  gd::ExpressionValidator validator(platform, projectScopedContainers, rootType);
59  node.Visit(validator);
60  return validator.GetAllErrors().empty();
61  }
62 
68  const std::vector<ExpressionParserDiagnostic*>& GetFatalErrors() {
69  return fatalErrors;
70  };
71 
77  const std::vector<ExpressionParserDiagnostic*>& GetAllErrors() {
78  return allErrors;
79  };
80 
81  protected:
82  void OnVisitSubExpressionNode(SubExpressionNode& node) override {
83  ReportAnyError(node);
84  node.expression->Visit(*this);
85  }
86  void OnVisitOperatorNode(OperatorNode& node) override {
87  ReportAnyError(node);
88 
89  // The "required" type ("parentType") will be used when visiting the first operand.
90  // Note that it may be refined thanks to this first operand (see later).
91  node.leftHandSide->Visit(*this);
92  const Type leftType = childType; // Store the type of the first operand.
93 
94  if (leftType == Type::Number) {
95  if (node.op == ' ') {
96  RaiseError("syntax_error",
97  "No operator found. Did you forget to enter an operator (like +, -, "
98  "* or /) between numbers or expressions?", node.rightHandSide->location);
99  }
100  }
101  else if (leftType == Type::String) {
102  if (node.op == ' ') {
103  RaiseError("syntax_error",
104  "You must add the operator + between texts or expressions. For "
105  "example: \"Your name: \" + VariableString(PlayerName).", node.rightHandSide->location);
106  }
107  else if (node.op != '+') {
108  RaiseOperatorError(
109  _("You've used an operator that is not supported. Only + can be used "
110  "to concatenate texts."),
111  ExpressionParserLocation(node.leftHandSide->location.GetEndPosition() + 1, node.location.GetEndPosition()));
112  }
113  } else if (leftType == Type::Object) {
114  RaiseOperatorError(
115  _("Operators (+, -, /, *) can't be used with an object name. Remove "
116  "the operator."),
117  node.rightHandSide->location);
118  } else if (leftType == Type::Variable) {
119  RaiseOperatorError(
120  _("Operators (+, -, /, *) can't be used in variable names. Remove "
121  "the operator from the variable name."),
122  node.rightHandSide->location);
123  }
124 
125  // The "required" type ("parentType") of the second operator is decided by:
126  // - the parent type. Unless it can (`number|string`) or should (`unknown`) be refined, then:
127  // - the first operand.
128  parentType = ShouldTypeBeRefined(parentType) ? leftType : parentType;
129  node.rightHandSide->Visit(*this);
130  const Type rightType = childType;
131 
132  // The type of the overall operator ("childType") is decided by:
133  // - the parent type. Unless it can (`number|string`) or should (`unknown`) be refined, then:
134  // - the first operand. Unless it can (`number|string`) or should (`unknown`) be refined, then:
135  // - the right operand (which got visited knowing the type of the first operand, so it's
136  // equal or strictly more precise than the left operand).
137  childType = ShouldTypeBeRefined(parentType) ? (ShouldTypeBeRefined(leftType) ? leftType : rightType) : parentType;
138  }
139  void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
140  ReportAnyError(node);
141  node.factor->Visit(*this);
142  const Type rightType = childType;
143 
144  if (rightType == Type::Number) {
145  if (node.op != '+' && node.op != '-') {
146  // This is actually a dead code because the parser takes them as
147  // binary operations with an empty left side which makes as much sense.
148  RaiseTypeError(
149  _("You've used an \"unary\" operator that is not supported. Operator "
150  "should be "
151  "either + or -."),
152  node.location);
153  }
154  } else if (rightType == Type::String) {
155  RaiseTypeError(
156  _("You've used an operator that is not supported. Only + can be used "
157  "to concatenate texts, and must be placed between two texts (or "
158  "expressions)."),
159  node.location);
160  } else if (rightType == Type::Object) {
161  RaiseTypeError(
162  _("Operators (+, -) can't be used with an object name. Remove the "
163  "operator."),
164  node.location);
165  } else if (rightType == Type::Variable) {
166  RaiseTypeError(
167  _("Operators (+, -) can't be used in variable names. Remove "
168  "the operator from the variable name."),
169  node.location);
170  }
171  }
172  void OnVisitNumberNode(NumberNode& node) override {
173  ReportAnyError(node);
174  childType = Type::Number;
175  if (parentType == Type::String) {
176  RaiseTypeError(
177  _("You entered a number, but a text was expected (in quotes)."),
178  node.location);
179  } else if (parentType != Type::Number &&
180  parentType != Type::NumberOrString) {
181  RaiseTypeError(_("You entered a number, but this type was expected:") +
182  " " + TypeToString(parentType),
183  node.location);
184  }
185  }
186  void OnVisitTextNode(TextNode& node) override {
187  ReportAnyError(node);
188  childType = Type::String;
189  if (parentType == Type::Number) {
190  RaiseTypeError(_("You entered a text, but a number was expected."),
191  node.location);
192  } else if (parentType != Type::String &&
193  parentType != Type::NumberOrString) {
194  RaiseTypeError(_("You entered a text, but this type was expected:") +
195  " " + TypeToString(parentType),
196  node.location);
197  }
198  }
199  void OnVisitVariableNode(VariableNode& node) override {
200  ReportAnyError(node);
201 
202  if (parentType == Type::Variable) {
203  childType = Type::Variable;
204 
205  if (node.child) {
206  node.child->Visit(*this);
207  }
208  } else if (parentType == Type::String || parentType == Type::Number || parentType == Type::NumberOrString) {
209  // The node represents a variable or an object variable in an expression waiting for its *value* to be returned.
210  childType = parentType;
211 
212  const auto& variablesContainersList = projectScopedContainers.GetVariablesContainersList();
213  const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
214  const auto& propertiesContainerList = projectScopedContainers.GetPropertiesContainersList();
215 
216  forbidsUsageOfBracketsBecauseParentIsObject = false;
217  projectScopedContainers.MatchIdentifierWithName<void>(node.name,
218  [&]() {
219  // This represents an object.
220 
221  // While understood by the parser, it's forbidden to use the bracket notation just after
222  // an object name (`MyObject["MyVariable"]`).
223  forbidsUsageOfBracketsBecauseParentIsObject = true;
224  }, [&]() {
225  // This is a variable.
226  }, [&]() {
227  // This is a property.
228  // Being in this node implies that there is at least a child - which is not supported for properties.
229  RaiseTypeError(_("Accessing a child variable of a property is not possible - just write the property name."),
230  node.location);
231  }, [&]() {
232  // This is a parameter.
233  // Being in this node implies that there is at least a child - which is not supported for parameters.
234  RaiseTypeError(_("Accessing a child variable of a parameter is not possible - just write the parameter name."),
235  node.location);
236  }, [&]() {
237  // This is something else.
238  RaiseTypeError(_("No object, variable or property with this name found."),
239  node.location);
240  });
241 
242  if (node.child) {
243  node.child->Visit(*this);
244  }
245 
246  forbidsUsageOfBracketsBecauseParentIsObject = false;
247  } else {
248  RaiseTypeError(_("You entered a variable, but this type was expected:") +
249  " " + TypeToString(parentType),
250  node.location);
251 
252  if (node.child) {
253  node.child->Visit(*this);
254  }
255  }
256  }
257  void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
258  ReportAnyError(node);
259 
260  // In the case we accessed an object variable (`MyObject.MyVariable`),
261  // brackets can now be used (`MyObject.MyVariable["MyChildVariable"]` is now valid).
262  forbidsUsageOfBracketsBecauseParentIsObject = false;
263 
264  if (node.child) {
265  node.child->Visit(*this);
266  }
267  }
268  void OnVisitVariableBracketAccessorNode(
269  VariableBracketAccessorNode& node) override {
270  ReportAnyError(node);
271 
272  if (forbidsUsageOfBracketsBecauseParentIsObject) {
273  RaiseError("brackets_not_allowed_for_objects",
274  _("You can't use the brackets to access an object variable. "
275  "Use a dot followed by the variable name, like this: "
276  "`MyObject.MyVariable`."),
277  node.location);
278  }
279  forbidsUsageOfBracketsBecauseParentIsObject = false;
280 
281  Type currentParentType = parentType;
282  parentType = Type::NumberOrString;
283  node.expression->Visit(*this);
284  parentType = currentParentType;
285 
286  if (node.child) {
287  node.child->Visit(*this);
288  }
289  }
290  void OnVisitIdentifierNode(IdentifierNode& node) override {
291  ReportAnyError(node);
292  if (parentType == Type::String) {
293  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
294  // The identifier is not a variable, so either the variable is not properly declared
295  // or it's a text without quotes.
296  RaiseTypeError(_("You must wrap your text inside double quotes "
297  "(example: \"Hello world\")."),
298  node.location);
299  }
300  }
301  else if (parentType == Type::Number) {
302  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
303  // The identifier is not a variable, so the variable is not properly declared.
304  RaiseTypeError(
305  _("You must enter a number."), node.location);
306  }
307  }
308  else if (parentType == Type::NumberOrString) {
309  if (!ValidateObjectVariableOrVariableOrProperty(node)) {
310  // The identifier is not a variable, so either the variable is not properly declared
311  // or it's a text without quotes.
312  RaiseTypeError(
313  _("You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name."),
314  node.location);
315  }
316  }
317  else if (parentType != Type::Object && parentType != Type::Variable) {
318  // It can't happen.
319  RaiseTypeError(
320  _("You've entered a name, but this type was expected:") + " " + TypeToString(parentType),
321  node.location);
322  childType = parentType;
323  } else {
324  childType = parentType;
325  }
326  }
327  void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
328  ReportAnyError(node);
329  }
330  void OnVisitFunctionCallNode(FunctionCallNode& node) override {
331  childType = ValidateFunction(node);
332  }
333  void OnVisitEmptyNode(EmptyNode& node) override {
334  ReportAnyError(node);
335  gd::String message;
336  if (parentType == Type::Number) {
337  message = _("You must enter a number or a valid expression call.");
338  } else if (parentType == Type::String) {
339  message = _(
340  "You must enter a text (between quotes) or a valid expression call.");
341  } else if (parentType == Type::Variable) {
342  message = _("You must enter a variable name.");
343  } else if (parentType == Type::Object) {
344  message = _("You must enter a valid object name.");
345  } else {
346  // It can't happen.
347  message = _("You must enter a valid expression.");
348  }
349  RaiseTypeError(message, node.location);
350  childType = Type::Empty;
351  }
352 
353  private:
354  enum Type {Unknown = 0, Number, String, NumberOrString, Variable, Object, Empty};
355  Type ValidateFunction(const gd::FunctionCallNode& function);
356  bool ValidateObjectVariableOrVariableOrProperty(const gd::IdentifierNode& identifier);
357 
358  void ReportAnyError(const ExpressionNode& node, bool isFatal = true) {
359  if (node.diagnostic && node.diagnostic->IsError()) {
360  // Syntax errors are holden by the AST nodes.
361  // It's fine to give pointers on them as the AST live longer than errors
362  // handling.
363  allErrors.push_back(node.diagnostic.get());
364  if (isFatal) {
365  fatalErrors.push_back(node.diagnostic.get());
366  }
367  }
368  }
369 
370  void RaiseError(const gd::String &type,
371  const gd::String &message, const ExpressionParserLocation &location, bool isFatal = true) {
372  auto diagnostic = gd::make_unique<ExpressionParserError>(
373  type, message, location);
374  allErrors.push_back(diagnostic.get());
375  if (isFatal) {
376  fatalErrors.push_back(diagnostic.get());
377  }
378  // Errors found by the validator are not holden by the AST nodes.
379  // They must be owned by the validator to keep living while errors are
380  // handled by the caller.
381  supplementalErrors.push_back(std::move(diagnostic));
382  }
383 
384  void RaiseTypeError(
385  const gd::String &message, const ExpressionParserLocation &location, bool isFatal = true) {
386  RaiseError("type_error", message, location, isFatal);
387  }
388 
389  void RaiseOperatorError(
390  const gd::String &message, const ExpressionParserLocation &location) {
391  RaiseError("invalid_operator", message, location);
392  }
393 
394  void ReadChildTypeFromVariable(gd::Variable::Type variableType) {
395  if (variableType == gd::Variable::Number) {
396  childType = Type::Number;
397  } else if (variableType == gd::Variable::String) {
398  childType = Type::String;
399  } else {
400  // Nothing - we don't know the precise type (this could be used as a string or as a number).
401  }
402  }
403 
404  static bool ShouldTypeBeRefined(Type type) {
405  return (type == Type::Unknown || type == Type::NumberOrString);
406  }
407 
408  static Type StringToType(const gd::String &type);
409  static const gd::String &TypeToString(Type type);
410  static const gd::String unknownTypeString;
411  static const gd::String numberTypeString;
412  static const gd::String stringTypeString;
413  static const gd::String numberOrStringTypeString;
414  static const gd::String variableTypeString;
415  static const gd::String objectTypeString;
416  static const gd::String identifierTypeString;
417  static const gd::String emptyTypeString;
418 
419  std::vector<ExpressionParserDiagnostic*> fatalErrors;
420  std::vector<ExpressionParserDiagnostic*> allErrors;
421  std::vector<std::unique_ptr<ExpressionParserDiagnostic>> supplementalErrors;
422  Type childType;
423  Type parentType;
424  bool forbidsUsageOfBracketsBecauseParentIsObject;
425  const gd::Platform &platform;
426  const gd::ProjectScopedContainers &projectScopedContainers;
427 };
428 
429 } // namespace gd
430 
431 #endif // GDCORE_EXPRESSIONVALIDATOR_H
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< ExpressionParserDiagnostic * > & GetAllErrors()
Get all the errors.
Definition: ExpressionValidator.h:77
const std::vector< ExpressionParserDiagnostic * > & GetFatalErrors()
Get only the fatal errors.
Definition: ExpressionValidator.h:68
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:54
static const gd::String & GetExpressionValueType(const gd::String &parameterType)
Return the expression type from the parameter type. Declinations of "number" and "string" types (like...
Definition: ParameterMetadata.h:219
Base class for implementing a platform.
Definition: Platform.h:42
Holds references to variables, objects, properties and other containers.
Definition: ProjectScopedContainers.h:30
String represents an UTF8 encoded string.
Definition: String.h:31
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:93
A function call node (either free function, object function or object behavior function)....
Definition: ExpressionParser2Node.h:364
An identifier node, usually representing an object or a variable with an optional function name or ch...
Definition: ExpressionParser2Node.h:197
Definition: ExpressionParser2Node.h:108