Allow to properly use variables/properties/parameters in brackets to access a structure variable (#5738)

* This means expressions like `MyStructure[SomeIndexVariable]` will work properly for a structure if `SomeIndexVariable` is a string variable. Gdevelop now properly uses the type of the variable/property/parameter (to avoid considering what's inside the brackets as a number if it's a string). Remember to declare your variables in the variables editor so that you can use them in expressions.
This commit is contained in:
Florian Rival
2023-10-09 18:21:49 +02:00
committed by GitHub
parent 1bc27bdd8b
commit 913e367f6f
30 changed files with 5328 additions and 257 deletions
@@ -1212,13 +1212,13 @@ gd::String EventsCodeGenerator::GeneratePropertyGetter(const gd::PropertiesConta
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getProperty" + property.GetName() + "()";
return "getProperty" + property.GetName() + "As" + type + "()";
}
gd::String EventsCodeGenerator::GenerateParameterGetter(const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getParameter" + parameter.GetName() + "()";
return "getParameter" + parameter.GetName() + "As" + type + "()";
}
EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
@@ -12,8 +12,8 @@
#include "GDCore/Events/Event.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/String.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
namespace gd {
class EventsList;
class Expression;
@@ -58,8 +58,9 @@ class GD_CORE_API EventsCodeGenerator {
* \brief Construct a code generator for the specified
* objects/groups and platform
*/
EventsCodeGenerator(const gd::Platform& platform,
const gd::ProjectScopedContainers& projectScopedContainers_);
EventsCodeGenerator(
const gd::Platform& platform,
const gd::ProjectScopedContainers& projectScopedContainers_);
virtual ~EventsCodeGenerator(){};
/**
@@ -546,7 +547,9 @@ class GD_CORE_API EventsCodeGenerator {
};
virtual gd::String GenerateVariableValueAs(const gd::String& type) {
return type == "string" ? ".getAsString()" : ".getAsNumber()";
return type == "number|string" ? ".getAsNumberOrString()"
: type == "string" ? ".getAsString()"
: ".getAsNumber()";
}
/**
@@ -577,14 +580,16 @@ class GD_CORE_API EventsCodeGenerator {
return "fakeObjectListOf_" + objectName;
}
virtual gd::String GeneratePropertyGetter(const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
virtual gd::String GeneratePropertyGetter(
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
virtual gd::String GenerateParameterGetter(const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
virtual gd::String GenerateParameterGetter(
const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
/**
* \brief Generate the code to reference an object which is
@@ -665,7 +670,8 @@ class GD_CORE_API EventsCodeGenerator {
* The default implementation generates C-style code : It wraps the predicate
* inside parenthesis and add a !.
*/
virtual gd::String GenerateNegatedPredicate(const gd::String& predicate) const {
virtual gd::String GenerateNegatedPredicate(
const gd::String& predicate) const {
return "!(" + predicate + ")";
};
@@ -103,7 +103,7 @@ void ExpressionCodeGenerator::OnVisitVariableNode(VariableNode& node) {
// This "translation" from the type to an enum could be avoided
// if all types were moved to an enum.
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
@@ -191,7 +191,7 @@ void ExpressionCodeGenerator::OnVisitVariableBracketAccessorNode(
return;
}
ExpressionCodeGenerator generator("string", "", codeGenerator, context);
ExpressionCodeGenerator generator("number|string", "", codeGenerator, context);
node.expression->Visit(generator);
output +=
codeGenerator.GenerateVariableBracketAccessor(generator.GetOutput());
@@ -200,7 +200,7 @@ void ExpressionCodeGenerator::OnVisitVariableBracketAccessorNode(
void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
@@ -271,7 +271,7 @@ void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
void ExpressionCodeGenerator::OnVisitFunctionCallNode(FunctionCallNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
@@ -502,7 +502,7 @@ gd::String ExpressionCodeGenerator::GenerateDefaultValue(
void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
output += GenerateDefaultValue(type);
@@ -511,7 +511,7 @@ void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) {
void ExpressionCodeGenerator::OnVisitObjectFunctionNameNode(
ObjectFunctionNameNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
output += GenerateDefaultValue(type);
@@ -58,7 +58,7 @@ class GD_CORE_API ExpressionObjectsAnalyzer
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers.GetObjectsContainersList(), rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsExpression("variable", type)) {
// Nothing to do (this can't reference an object)
@@ -88,7 +88,7 @@ class GD_CORE_API ExpressionObjectsAnalyzer
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers.GetObjectsContainersList(), rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsObject(type)) {
context.AddObjectName(projectScopedContainers, node.identifierName);
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
+4 -6
View File
@@ -81,8 +81,7 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(platform, objectsContainersList, rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
@@ -115,7 +114,7 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers.GetObjectsContainersList(), rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsObject(type) &&
node.identifierName == objectName) {
hasDoneRenaming = true;
@@ -217,8 +216,7 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(platform, objectsContainersList, rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
@@ -250,7 +248,7 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers.GetObjectsContainersList(), rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsObject(type) &&
node.identifierName == searchedObjectName) {
hasObject = true;
@@ -395,10 +395,8 @@ class GD_CORE_API ExpressionCompletionFinder
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
AddCompletionsForAllIdentifiersWithPrefix("", type);
completions.push_back(
@@ -409,10 +407,8 @@ class GD_CORE_API ExpressionCompletionFinder
// No completions.
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
AddCompletionsForAllIdentifiersWithPrefix("", type);
completions.push_back(
@@ -488,7 +484,7 @@ class GD_CORE_API ExpressionCompletionFinder
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
if (type == "globalvar") {
@@ -535,7 +531,7 @@ class GD_CORE_API ExpressionCompletionFinder
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsObject(type)) {
// Only show completions of objects if an object is required.
AddCompletionsForObjectWithPrefix(
@@ -615,10 +611,8 @@ class GD_CORE_API ExpressionCompletionFinder
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
if (!node.behaviorFunctionName.empty() ||
node.behaviorNameNamespaceSeparatorLocation.IsValid()) {
// Behavior function (or behavior function being written, with the
@@ -669,10 +663,8 @@ class GD_CORE_API ExpressionCompletionFinder
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
bool isCaretOnParenthesis = IsCaretOn(node.openingParenthesisLocation) ||
IsCaretOn(node.closingParenthesisLocation);
@@ -741,10 +733,8 @@ class GD_CORE_API ExpressionCompletionFinder
}
}
void OnVisitEmptyNode(EmptyNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, rootType, node);
platform, projectScopedContainers, rootType, node);
AddCompletionsForAllIdentifiersWithPrefix(node.text, type, node.location);
completions.push_back(
@@ -14,13 +14,12 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Tools/Localization.h"
namespace gd {
class Expression;
class ObjectsContainer;
class ObjectsContainersList;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
@@ -41,10 +40,10 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
* operations.
*/
static const gd::String GetType(const gd::Platform &platform,
const gd::ObjectsContainersList &objectsContainersList,
const gd::ProjectScopedContainers &projectScopedContainers,
gd::ExpressionNode& node) {
gd::ExpressionLeftSideTypeFinder typeFinder(
platform, objectsContainersList);
platform, projectScopedContainers);
node.Visit(typeFinder);
return typeFinder.GetType();
}
@@ -53,9 +52,9 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
protected:
ExpressionLeftSideTypeFinder(const gd::Platform &platform_,
const gd::ObjectsContainersList &objectsContainersList_)
const gd::ProjectScopedContainers &projectScopedContainers_)
: platform(platform_),
objectsContainersList(objectsContainersList_),
projectScopedContainers(projectScopedContainers_),
type("unknown") {};
const gd::String &GetType() {
@@ -67,6 +66,14 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
void OnVisitOperatorNode(OperatorNode& node) override {
node.leftHandSide->Visit(*this);
// The type is decided by the first operand, unless it can (`number|string`)
// or should (`unknown`) be refined, in which case we go for the right
// operand (which got visited knowing the type of the first operand, so it's
// equal or strictly more precise than the left operand).
if (type == "unknown" || type == "number|string") {
node.rightHandSide->Visit(*this);
}
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
node.factor->Visit(*this);
@@ -83,7 +90,7 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, objectsContainersList, node);
platform, projectScopedContainers.GetObjectsContainersList(), node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
type = "unknown";
}
@@ -93,12 +100,99 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
void OnVisitVariableNode(VariableNode& node) override {
type = "unknown";
projectScopedContainers.MatchIdentifierWithName<void>(node.name,
[&]() {
// This represents an object.
// We could store it to explore the type of the variable, but in practice this
// is only called for structures/arrays with 2 levels, and we don't support structure
// type identification for now.
},
[&]() {
// This is a variable.
// We could store it to explore the type of the variable, but in practice this
// is only called for structures/arrays with 2 levels, and we don't support structure
// type identification for now.
}, [&]() {
// This is a property with more than one child - this is unsupported.
}, [&]() {
// This is a parameter with more than one child - this is unsupported.
}, [&]() {
// This is something else.
type = "unknown";
});
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
type = "unknown";
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
type = "unknown";
projectScopedContainers.MatchIdentifierWithName<void>(node.identifierName,
[&]() {
// It's an object variable.
if (projectScopedContainers.GetObjectsContainersList()
.HasObjectOrGroupWithVariableNamed(
node.identifierName, node.childIdentifierName)
== ObjectsContainersList::VariableExistence::DoesNotExist) {
type = "unknown";
return;
}
auto variableType =
projectScopedContainers.GetObjectsContainersList()
.GetTypeOfObjectOrGroupVariable(node.identifierName,
node.childIdentifierName);
ReadTypeFromVariable(variableType);
},
[&]() {
// It's a variable.
const auto& variable =
projectScopedContainers.GetVariablesContainersList().Get(
node.identifierName);
if (node.childIdentifierName.empty()) {
ReadTypeFromVariable(variable.GetType());
} else {
if (!variable.HasChild(node.childIdentifierName)) {
type = "unknown";
return;
}
ReadTypeFromVariable(
variable.GetChild(node.childIdentifierName).GetType());
}
}, [&]() {
// This is a property.
const gd::NamedPropertyDescriptor& property = projectScopedContainers
.GetPropertiesContainersList().Get(node.identifierName).second;
if (property.GetType() == "Number") {
type = "number";
} else if (property.GetType() == "Boolean") {
// Nothing - we don't know the precise type (this could be used a string or as a number)
} else {
// Assume type is String or equivalent.
type = "string";
}
}, [&]() {
// It's a parameter.
const auto& parametersVectorsList = projectScopedContainers.GetParametersVectorsList();
const auto& parameter = gd::ParameterMetadataTools::Get(parametersVectorsList, node.identifierName);
const auto& valueTypeMetadata = parameter.GetValueTypeMetadata();
if (valueTypeMetadata.IsNumber()) {
type = "number";
} else if (valueTypeMetadata.IsString()) {
type = "string";
} else if (valueTypeMetadata.IsBoolean()) {
// Nothing - we don't know the precise type (this could be used as a string or as a number).
} else {
type = "unknown";
}
}, [&]() {
// This is something else.
type = "unknown";
});
}
void OnVisitEmptyNode(EmptyNode& node) override {
type = "unknown";
@@ -108,10 +202,18 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
private:
void ReadTypeFromVariable(gd::Variable::Type variableType) {
if (variableType == gd::Variable::Number) {
type = "number";
} else if (variableType == gd::Variable::String) {
type = "string";
}
}
gd::String type;
const gd::Platform &platform;
const gd::ObjectsContainersList &objectsContainersList;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
};
+14 -11
View File
@@ -15,13 +15,12 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Tools/Localization.h"
namespace gd {
class Expression;
class ObjectsContainer;
class ObjectsContainersList;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
@@ -51,11 +50,11 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
* sub-expression that a given node represents.
*/
static const gd::String GetType(const gd::Platform &platform,
const gd::ObjectsContainersList &objectsContainersList,
const gd::ProjectScopedContainers &projectScopedContainers,
const gd::String &rootType,
gd::ExpressionNode& node) {
gd::ExpressionTypeFinder typeFinder(
platform, objectsContainersList, rootType);
platform, projectScopedContainers, rootType);
node.Visit(typeFinder);
return typeFinder.GetType();
}
@@ -64,10 +63,10 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
protected:
ExpressionTypeFinder(const gd::Platform &platform_,
const gd::ObjectsContainersList &objectsContainersList_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String &rootType_)
: platform(platform_),
objectsContainersList(objectsContainersList_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
type(ExpressionTypeFinder::unknownType),
child(nullptr) {};
@@ -113,8 +112,12 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
}
auto leftSideType = gd::ExpressionLeftSideTypeFinder::GetType(
platform,
objectsContainersList,
projectScopedContainers,
node);
// If we can infer a definitive number or string type, use it.
// Otherwise, we only know that it's number or string, and this can even
// be used as is at runtime.
if (leftSideType == ExpressionTypeFinder::numberType
|| leftSideType == ExpressionTypeFinder::stringType) {
type = leftSideType;
@@ -126,7 +129,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (child == nullptr) {
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, objectsContainersList, node);
platform, projectScopedContainers.GetObjectsContainersList(), node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
VisitParent(node);
}
@@ -138,7 +141,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
const gd::ParameterMetadata* parameterMetadata =
gd::MetadataProvider::GetFunctionCallParameterMetadata(
platform,
objectsContainersList,
projectScopedContainers.GetObjectsContainersList(),
node,
*child);
if (parameterMetadata == nullptr || parameterMetadata->GetType().empty()) {
@@ -159,7 +162,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
else if (rootType == ExpressionTypeFinder::numberOrStringType) {
auto leftSideType = gd::ExpressionLeftSideTypeFinder::GetType(
platform,
objectsContainersList,
projectScopedContainers,
node);
if (leftSideType == ExpressionTypeFinder::numberType
|| leftSideType == ExpressionTypeFinder::stringType) {
@@ -183,7 +186,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
ExpressionNode *child;
const gd::Platform &platform;
const gd::ObjectsContainersList &objectsContainersList;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
};
+35 -2
View File
@@ -89,12 +89,17 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
const auto& propertiesContainersList = projectScopedContainers.GetPropertiesContainersList();
const auto& parametersVectorsList = projectScopedContainers.GetParametersVectorsList();
// Unless we find something precise (like a variable or property or parameter with a known type),
// we consider this node will be of the type required by the parent.
childType = parentType;
return projectScopedContainers.MatchIdentifierWithName<bool>(identifier.identifierName,
[&]() {
// This represents an object.
if (identifier.childIdentifierName.empty()) {
RaiseTypeError(_("An object variable or expression should be entered."),
identifier.identifierNameLocation);
return true; // We should have found a variable.
}
@@ -103,26 +108,31 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
if (variableExistence == gd::ObjectsContainersList::DoesNotExist) {
RaiseTypeError(_("This variable does not exist on this object or group."),
identifier.childIdentifierNameLocation);
return true; // We should have found a variable.
}
else if (variableExistence == gd::ObjectsContainersList::ExistsOnlyOnSomeObjectsOfTheGroup) {
RaiseTypeError(_("This variable only exists on some objects of the group. It must be declared for all objects."),
identifier.childIdentifierNameLocation);
return true; // We should have found a variable.
}
else if (variableExistence == gd::ObjectsContainersList::GroupIsEmpty) {
RaiseTypeError(_("This group is empty. Add an object to this group first."),
identifier.identifierNameLocation);
return true; // We should have found a variable.
}
auto variableType = objectsContainersList.GetTypeOfObjectOrGroupVariable(identifier.identifierName, identifier.childIdentifierName);
ReadChildTypeFromVariable(variableType);
return true; // We found a variable.
}, [&]() {
// This is a variable.
// Try to identify a declared variable with the name (and maybe the child
// variable).
const gd::Variable& variable =
variablesContainersList.Get(identifier.identifierName);
@@ -130,6 +140,7 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
// Just the root variable is accessed, check it can be used in an
// expression.
validateVariableTypeForExpression(variable.GetType());
ReadChildTypeFromVariable(variable.GetType());
return true; // We found a variable.
} else {
@@ -143,6 +154,7 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
const gd::Variable& childVariable =
variable.GetChild(identifier.childIdentifierName);
ReadChildTypeFromVariable(childVariable.GetType());
return true; // We found a variable.
}
}, [&]() {
@@ -150,23 +162,44 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
if (!identifier.childIdentifierName.empty()) {
RaiseTypeError(_("Accessing a child variable of a property is not possible - just write the property name."),
identifier.childIdentifierNameLocation);
return true; // We found a property, even if the child is not allowed.
}
const gd::NamedPropertyDescriptor& property = projectScopedContainers
.GetPropertiesContainersList().Get(identifier.identifierName).second;
if (property.GetType() == "Number") {
childType = Type::Number;
} else if (property.GetType() == "Boolean") {
// Nothing - we don't know the precise type (this could be used a string or as a number)
} else {
// Assume type is String or equivalent.
childType = Type::String;
}
return true; // We found a property.
}, [&]() {
// This is a parameter.
if (!identifier.childIdentifierName.empty()) {
RaiseTypeError(_("Accessing a child variable of a parameter is not possible - just write the parameter name."),
identifier.childIdentifierNameLocation);
return true; // We found a parameter, even if the child is not allowed.
}
const auto& parameter = gd::ParameterMetadataTools::Get(parametersVectorsList, identifier.identifierName);
const auto& valueTypeMetadata = parameter.GetValueTypeMetadata();
if (!valueTypeMetadata.IsNumber() && !valueTypeMetadata.IsString() && !valueTypeMetadata.IsBoolean()) {
if (valueTypeMetadata.IsNumber()) {
childType = Type::Number;
} else if (valueTypeMetadata.IsString()) {
childType = Type::String;
} else if (valueTypeMetadata.IsBoolean()) {
// Nothing - we don't know the precise type (this could be used as a string or as a number).
} else {
RaiseTypeError(_("This parameter is not a string, number or boolean - it can't be used in an expression."),
identifier.identifierNameLocation);
return true; // We found a parameter, even though the type is incompatible.
}
+21 -5
View File
@@ -103,9 +103,9 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
"example: \"Your name: \" + VariableString(PlayerName).", node.rightHandSide->location);
}
else if (node.op != '+') {
RaiseOperatorError(
_("You've used an operator that is not supported. Only + can be used "
"to concatenate texts."),
RaiseOperatorError(
_("You've used an operator that is not supported. Only + can be used "
"to concatenate texts."),
ExpressionParserLocation(node.leftHandSide->location.GetEndPosition() + 1, node.location.GetEndPosition()));
}
} else if (leftType == Type::Object) {
@@ -124,7 +124,11 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
node.rightHandSide->Visit(*this);
const Type rightType = childType;
childType = leftType;
// The type is decided by the first operand, unless it can (`number|string`)
// or should (`unknown`) be refined, in which case we go for the right
// operand (which got visited knowing the type of the first operand, so it's
// equal or strictly more precise than the left operand).
childType = (leftType == Type::Unknown || leftType == Type::NumberOrString) ? leftType : rightType;
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
ReportAnyError(node);
@@ -309,8 +313,10 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
RaiseTypeError(
_("You've entered a name, but this type was expected:") + " " + TypeToString(parentType),
node.location);
childType = parentType;
} else {
childType = parentType;
}
childType = parentType;
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
ReportAnyError(node);
@@ -379,6 +385,16 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
RaiseError("invalid_operator", message, location);
}
void ReadChildTypeFromVariable(gd::Variable::Type variableType) {
if (variableType == gd::Variable::Number) {
childType = Type::Number;
} else if (variableType == gd::Variable::String) {
childType = Type::String;
} else {
// Nothing - we don't know the precise type (this could be used as a string or as a number).
}
}
static Type StringToType(const gd::String &type);
static const gd::String &TypeToString(Type type);
static const gd::String unknownTypeString;
@@ -110,7 +110,7 @@ void UsedExtensionsFinder::OnVisitVariableNode(VariableNode& node) {
result.GetUsedExtensions().insert("BuiltinVariables");
auto type = gd::ExpressionTypeFinder::GetType(
project.GetCurrentPlatform(), GetObjectsContainersList(), rootType, node);
project.GetCurrentPlatform(), GetProjectScopedContainers(), rootType, node);
if (gd::ParameterMetadata::IsExpression("variable", type)) {
// Nothing to do (this can't reference an object)
@@ -154,7 +154,7 @@ void UsedExtensionsFinder::OnVisitVariableBracketAccessorNode(
// Add extensions bound to Objects/Behaviors/Functions
void UsedExtensionsFinder::OnVisitIdentifierNode(IdentifierNode &node) {
auto type = gd::ExpressionTypeFinder::GetType(
project.GetCurrentPlatform(), GetObjectsContainersList(), rootType, node);
project.GetCurrentPlatform(), GetProjectScopedContainers(), rootType, node);
if (gd::ParameterMetadata::IsObject(type) ||
GetObjectsContainersList().HasObjectOrGroupNamed(node.identifierName)) {
// An object or object variable is used.
@@ -148,6 +148,55 @@ ObjectsContainersList::GetObjectOrGroupVariablesContainer(
return nullptr;
}
gd::Variable::Type ObjectsContainersList::GetTypeOfObjectOrGroupVariable(
const gd::String& objectOrGroupName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
const auto& variables =
(*it)->GetObject(objectOrGroupName).GetVariables();
return variables.Get(variableName).GetType();
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// This could be adapted if objects groups have variables in the future.
// Currently, a group is considered as the "intersection" of all of its
// objects. Search "groups is the intersection of its objects" in the
// codebase. Consider that the first object having the variable will
// define its type.
const auto& objectGroup = (*it)->GetObjectGroups().Get(objectOrGroupName);
const auto& objectNames = objectGroup.GetAllObjectsNames();
for (const auto& objectName : objectNames) {
if (HasObjectWithVariableNamed(objectName, variableName)) {
return GetTypeOfObjectVariable(objectName, variableName);
}
}
return Variable::Type::Number;
}
}
return Variable::Type::Number;
}
gd::Variable::Type ObjectsContainersList::GetTypeOfObjectVariable(const gd::String& objectName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectName)) {
const auto& variables =
(*it)->GetObject(objectName).GetVariables();
return variables.Get(variableName).GetType();
}
}
return Variable::Type::Number;
}
void ObjectsContainersList::ForEachObjectOrGroupVariableWithPrefix(
const gd::String& objectOrGroupName,
const gd::String& prefix,
@@ -72,6 +72,11 @@ class GD_CORE_API ObjectsContainersList {
const gd::VariablesContainer* GetObjectOrGroupVariablesContainer(
const gd::String& objectOrGroupName) const;
/**
* \brief Get a type from an object/group variable.
*/
gd::Variable::Type GetTypeOfObjectOrGroupVariable(const gd::String& objectOrGroupName, const gd::String& variableName) const;
/**
* \brief Get a type from an object/group name.
* \note If a group contains only objects of a same type, then the group has
@@ -160,6 +165,8 @@ class GD_CORE_API ObjectsContainersList {
bool HasObjectWithVariableNamed(const gd::String& objectName,
const gd::String& variableName) const;
gd::Variable::Type GetTypeOfObjectVariable(const gd::String& objectName, const gd::String& variableName) const;
void ForEachObjectVariableWithPrefix(
const gd::String& objectOrGroupName,
const gd::String& prefix,
+327 -13
View File
@@ -22,12 +22,19 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
auto &layout1 = project.InsertNewLayout("Layout1", 0);
// Add some variables and objects:
layout1.GetVariables().InsertNew("MySceneVariable", 0);
layout1.GetVariables().InsertNew("MySceneVariable2", 1);
layout1.GetVariables().InsertNew("MySceneStructureVariable", 2).GetChild("MyChild");
layout1.GetVariables().InsertNew("MySceneStructureVariable2", 2).GetChild("MyChild");
project.GetVariables().InsertNew("MyGlobalNumberVariable").SetValue(1234);
project.GetVariables().InsertNew("MyGlobalStringVariable").SetString("TestGlobal");
layout1.GetVariables().InsertNew("MySceneVariable").SetValue(123);
layout1.GetVariables().InsertNew("MySceneVariable2").SetValue(123);
layout1.GetVariables().InsertNew("MySceneStringVariable").SetString("MyString");
layout1.GetVariables().InsertNew("MySceneBooleanVariable").SetBool(true);
layout1.GetVariables().InsertNew("MySceneStructureVariable").GetChild("MyChild");
layout1.GetVariables().InsertNew("MySceneStructureVariable2").GetChild("MyChild");
layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
auto &mySpriteObject = layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
mySpriteObject.GetVariables().InsertNew("MyNumberVariable").SetValue(123);
mySpriteObject.GetVariables().InsertNew("MyStringVariable").SetString("Test");
mySpriteObject.GetVariables().InsertNew("MyStructureVariable").GetChild("MyStringChild").SetString("Test");
layout1.InsertNewObject(
project, "MyExtension::Sprite", "MyOtherSpriteObject", 1);
layout1.InsertNewObject(project,
@@ -432,7 +439,7 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getPropertyMyProperty() + 1");
REQUIRE(expressionCodeGenerator.GetOutput() == "getPropertyMyPropertyAsnumber() + 1");
}
{
auto node =
@@ -444,7 +451,56 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getPropertyMyProperty() + getPropertyMyProperty2()");
REQUIRE(expressionCodeGenerator.GetOutput() == "getPropertyMyPropertyAsnumber() + getPropertyMyProperty2Asnumber()");
}
}
SECTION("Properties (1 level, number|string)") {
gd::PropertiesContainer propertiesContainer(gd::EventsFunctionsContainer::Extension);
auto projectScopedContainersWithProperties = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithProperties.AddPropertiesContainer(propertiesContainer);
propertiesContainer.InsertNew("MyNumberProperty").SetType("Number");
propertiesContainer.InsertNew("MyStringProperty").SetType("String");
propertiesContainer.InsertNew("MyBooleanProperty").SetType("Boolean");
gd::EventsCodeGenerator codeGeneratorWithProperties(platform, projectScopedContainersWithProperties);
{
auto node =
parser.ParseExpression("MyNumberProperty");
gd::ExpressionCodeGenerator expressionCodeGenerator("number|string",
"",
codeGeneratorWithProperties,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getPropertyMyNumberPropertyAsnumber()");
}
{
auto node =
parser.ParseExpression("MyStringProperty");
gd::ExpressionCodeGenerator expressionCodeGenerator("number|string",
"",
codeGeneratorWithProperties,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getPropertyMyStringPropertyAsstring()");
}
{
auto node =
parser.ParseExpression("MyBooleanProperty");
gd::ExpressionCodeGenerator expressionCodeGenerator("number|string",
"",
codeGeneratorWithProperties,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getPropertyMyBooleanPropertyAsnumber|string()");
}
}
SECTION("Parameters (1 level)") {
@@ -473,7 +529,7 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getParameterMyParameter1() + 1");
REQUIRE(expressionCodeGenerator.GetOutput() == "getParameterMyParameter1Asnumber() + 1");
}
{
auto node =
@@ -485,7 +541,64 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getParameterMyParameter1() + getParameterMyParameter2()");
REQUIRE(expressionCodeGenerator.GetOutput() == "getParameterMyParameter1Asnumber() + getParameterMyParameter2Asnumber()");
}
}
SECTION("Parameters (1 level, number|string)") {
std::vector<gd::ParameterMetadata> parameters;
gd::ParameterMetadata param1;
param1.SetName("MyNumberParameter");
param1.SetType("number");
gd::ParameterMetadata param2;
param2.SetName("MyStringParameter");
param2.SetType("string");
gd::ParameterMetadata param3;
param3.SetName("MyBooleanParameter");
param3.SetType("yesorno");
parameters.push_back(param1);
parameters.push_back(param2);
parameters.push_back(param3);
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
gd::EventsCodeGenerator codeGeneratorWithProperties(platform, projectScopedContainersWithParameters);
{
auto node =
parser.ParseExpression("MyNumberParameter");
gd::ExpressionCodeGenerator expressionCodeGenerator("number|string",
"",
codeGeneratorWithProperties,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getParameterMyNumberParameterAsnumber()");
}
{
auto node =
parser.ParseExpression("MyStringParameter");
gd::ExpressionCodeGenerator expressionCodeGenerator("number|string",
"",
codeGeneratorWithProperties,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getParameterMyStringParameterAsstring()");
}
{
auto node =
parser.ParseExpression("MyBooleanParameter");
gd::ExpressionCodeGenerator expressionCodeGenerator("number|string",
"",
codeGeneratorWithProperties,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getParameterMyBooleanParameterAsnumber|string()");
}
}
SECTION("Scene variables (1 level)") {
@@ -554,7 +667,7 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getAsNumber() + getLayoutVariable(MySceneStructureVariable2).getChild(\"MyChild\").getAsNumber()");
}
}
SECTION("Scene variables (2 levels with bracket accessor)") {
SECTION("Scene variables (2 levels with bracket accessor, string)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable[\"MyChild\"] + 1");
@@ -580,6 +693,144 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getAsNumber() + getLayoutVariable(MySceneStructureVariable2).getChild(\"MyChild\").getAsNumber()");
}
}
SECTION("Scene variables (2 levels with bracket accessor, number)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable[3] + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(3).getAsNumber() + 1");
}
{
auto node =
parser.ParseExpression("MySceneStructureVariable[3] + MySceneStructureVariable2[3]");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(3).getAsNumber() + getLayoutVariable(MySceneStructureVariable2).getChild(3).getAsNumber()");
}
}
SECTION("Scene variables (2 levels with bracket accessor, using a number variable as index)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable[MySceneVariable] + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(getLayoutVariable(MySceneVariable).getAsNumber()).getAsNumber() + 1");
}
}
SECTION("Scene variables (2 levels with bracket accessor, using a string variable as index)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable[MySceneStringVariable] + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(getLayoutVariable(MySceneStringVariable).getAsString()).getAsNumber() + 1");
}
}
SECTION("Scene variables (2 levels with bracket accessor, using a non string/number variable as index)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable[MySceneBooleanVariable] + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(getLayoutVariable(MySceneBooleanVariable).getAsNumberOrString()).getAsNumber() + 1");
}
}
SECTION("Scene variables (2 levels with bracket accessor, using a unknown variable type as index)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable[MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric] + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getChild(\"CantKnownTheTypeSoStayGeneric\").getAsNumberOrString()).getAsNumber() + 1");
}
}
SECTION("Scene variables (2 levels with bracket accessor, using a unknown variable type and an operator with a number as index)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable[MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + 2] + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getChild(\"CantKnownTheTypeSoStayGeneric\").getAsNumber() + 2).getAsNumber() + 1");
}
}
SECTION("Scene variables (2 levels with bracket accessor, using a unknown variable type and an operator with a string as index)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable[MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + \"Test\"] + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getChild(\"CantKnownTheTypeSoStayGeneric\").getAsString() + \"Test\").getAsNumber() + 1");
}
}
SECTION("Scene variables (2 levels with bracket accessor, using a unknown variable type as index) (expression type: number|string)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable[MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric]");
gd::ExpressionCodeGenerator expressionCodeGenerator("number|string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getChild(\"CantKnownTheTypeSoStayGeneric\").getAsNumberOrString()).getAsNumberOrString()");
}
}
SECTION("Scene variables (2 levels with bracket accessor, using a number variable casted to string as index)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable[\"\" + MySceneVariable] + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(\"\" + getLayoutVariable(MySceneVariable).getAsString()).getAsNumber() + 1");
}
}
SECTION("Object variable with non existing object (invalid)") {
auto node =
parser.ParseExpression("MyNonExistingSpriteObject.MyVariable");
@@ -757,7 +1008,20 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"fakeBadVariable");
}
}
SECTION("Valid variables") {
SECTION("Valid variables (upcoming, new 'variable' type working for any variable)") {
// When implemented, copy the test cases from the next section, like this:
// SECTION("simple variable") {
// REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
// codeGenerator, context, "variable", "MySceneVariable", "")
// == "getLayoutVariable(MySceneVariable)");
// }
// SECTION("simple (global) variable") {
// REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
// codeGenerator, context, "variable", "MyGlobalNumberVariable", "")
// == "getProjectVariable(MyGlobalNumberVariable)");
// }
}
SECTION("Valid variables (legacy, pre-scoped variables)") {
SECTION("simple variable") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable", "")
@@ -779,13 +1043,63 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"\"world\" ]", "")
== "getLayoutVariable(myVariable).getChild(\"hello\" + \"world\")");
}
SECTION("object variable (legacy)") {
SECTION("bracket access (using a string object variable inside)") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable[MySpriteObject.MyStringVariable]", "")
== "getLayoutVariable(myVariable).getChild(getVariableForObject(MySpriteObject, MyStringVariable).getAsString())");
}
SECTION("bracket access (using a number object variable inside)") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable[MySpriteObject.MyNumberVariable]", "")
== "getLayoutVariable(myVariable).getChild(getVariableForObject(MySpriteObject, MyNumberVariable).getAsNumber())");
}
SECTION("bracket access (using a string variable inside)") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable[MySceneStringVariable]", "")
== "getLayoutVariable(myVariable).getChild(getLayoutVariable(MySceneStringVariable).getAsString())");
}
SECTION("bracket access (using a number variable inside)") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable[MySceneVariable]", "")
== "getLayoutVariable(myVariable).getChild(getLayoutVariable(MySceneVariable).getAsNumber())");
}
SECTION("bracket access (using a string global variable inside)") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable[MyGlobalStringVariable]", "")
== "getLayoutVariable(myVariable).getChild(getProjectVariable(MyGlobalStringVariable).getAsString())");
}
SECTION("bracket access (using a number global variable inside)") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable[MyGlobalNumberVariable]", "")
== "getLayoutVariable(myVariable).getChild(getProjectVariable(MyGlobalNumberVariable).getAsNumber())");
}
SECTION("bracket access (using a boolean variable inside)") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable[MySceneBooleanVariable]", "")
== "getLayoutVariable(myVariable).getChild(getLayoutVariable(MySceneBooleanVariable).getAsNumberOrString())");
}
SECTION("bracket access (using a structure variable inside)") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable[MySceneStructureVariable.MyChild.SubChild]", "")
== "getLayoutVariable(myVariable).getChild(getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getChild(\"SubChild\").getAsNumberOrString())");
}
SECTION("object variable") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "objectvar", "myVariable", "MySpriteObject")
== "getVariableForObject(MySpriteObject, myVariable)");
}
SECTION("object variable with bracket access (using a structure variable inside)") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "objectvar", "myVariable[MySceneStringVariable]", "MySpriteObject")
== "getVariableForObject(MySpriteObject, myVariable).getChild(getLayoutVariable(MySceneStringVariable).getAsString())");
}
SECTION("object variable with bracket access (using an object variable inside)") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "objectvar", "myVariable[MySpriteObject.MyStructureVariable.MyChild]", "MySpriteObject")
== "getVariableForObject(MySpriteObject, myVariable).getChild(getVariableForObject(MySpriteObject, MyStructureVariable).getChild(\"MyChild\").getAsNumberOrString())");
}
}
SECTION("Valid function calls with variables") {
SECTION("Valid function calls with variables (legacy, pre-scoped)") {
SECTION("Simple access") {
SECTION("Scene variable") {
auto node = parser.ParseExpression(
+550 -46
View File
@@ -22,10 +22,13 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &layout1 = project.InsertNewLayout("Layout1", 0);
layout1.GetVariables().InsertNew("MySceneVariable", 0);
layout1.GetVariables().InsertNew("MySceneVariable2", 1);
layout1.GetVariables().InsertNew("MySceneStructureVariable", 2).GetChild("MyChild");
layout1.GetVariables().InsertNew("MySceneStructureVariable2", 2).GetChild("MyChild");
layout1.GetVariables().InsertNew("MySceneVariable");
layout1.GetVariables().InsertNew("MySceneVariable2");
layout1.GetVariables().InsertNew("MySceneStructureVariable").GetChild("MyChild");
layout1.GetVariables().InsertNew("MySceneStructureVariable2").GetChild("MyChild");
layout1.GetVariables().InsertNew("MySceneNumberVariable").SetValue(123);
layout1.GetVariables().InsertNew("MySceneStringVariable").SetString("Test");
layout1.GetVariables().InsertNew("MySceneBooleanVariable").SetBool(true);
// Create an instance of BuiltinObject.
// This is not possible in practice.
@@ -38,9 +41,11 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
layout1.GetObjectGroups().InsertNew("EmptyGroup");
auto &mySpriteObject = layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 1);
mySpriteObject.GetVariables().InsertNew("MyVariable", 0);
mySpriteObject.GetVariables().InsertNew("MyVariable2", 1);
mySpriteObject.GetVariables().InsertNew("MyVariable3", 2);
mySpriteObject.GetVariables().InsertNew("MyVariable");
mySpriteObject.GetVariables().InsertNew("MyVariable2");
mySpriteObject.GetVariables().InsertNew("MyVariable3");
mySpriteObject.GetVariables().InsertNew("MyNumberVariable").SetValue(123);
mySpriteObject.GetVariables().InsertNew("MyStringVariable").SetString("Test");
auto &mySpriteObject2 = layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject2", 1);
mySpriteObject2.GetVariables().InsertNew("MyVariable", 0);
mySpriteObject2.GetVariables().InsertNew("MyVariable2", 1);
@@ -64,7 +69,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "string", emptyNode);
platform, projectScopedContainers, "string", emptyNode);
REQUIRE(type == "string");
REQUIRE(emptyNode.text == "");
@@ -79,7 +84,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number", emptyNode);
platform, projectScopedContainers, "number", emptyNode);
REQUIRE(type == "number");
REQUIRE(emptyNode.text == "");
@@ -94,7 +99,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "object", emptyNode);
platform, projectScopedContainers, "object", emptyNode);
REQUIRE(type == "object");
REQUIRE(emptyNode.text == "");
@@ -111,7 +116,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "string", emptyNode);
platform, projectScopedContainers, "string", emptyNode);
REQUIRE(type == "string");
REQUIRE(emptyNode.text == "");
}
@@ -120,7 +125,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number", emptyNode);
platform, projectScopedContainers, "number", emptyNode);
REQUIRE(type == "number");
REQUIRE(emptyNode.text == "");
}
@@ -129,7 +134,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "object", emptyNode);
platform, projectScopedContainers, "object", emptyNode);
REQUIRE(type == "object");
REQUIRE(emptyNode.text == "");
}
@@ -485,7 +490,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &operatorNode = dynamic_cast<gd::OperatorNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number", operatorNode);
platform, projectScopedContainers, "number", operatorNode);
REQUIRE(operatorNode.op == '+');
REQUIRE(type == "number");
auto &leftNumberNode =
@@ -504,7 +509,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &operatorNode = dynamic_cast<gd::OperatorNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "string", operatorNode);
platform, projectScopedContainers, "string", operatorNode);
REQUIRE(operatorNode.op == '+');
REQUIRE(type == "string");
auto &leftTextNode =
@@ -526,7 +531,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &operatorNode = dynamic_cast<gd::OperatorNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", operatorNode);
platform, projectScopedContainers, "number|string", operatorNode);
REQUIRE(operatorNode.op == '+');
REQUIRE(type == "number");
auto &leftNumberNode =
@@ -545,7 +550,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &operatorNode = dynamic_cast<gd::OperatorNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", operatorNode);
platform, projectScopedContainers, "number|string", operatorNode);
REQUIRE(operatorNode.op == '+');
REQUIRE(type == "string");
auto &leftTextNode =
@@ -567,7 +572,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &unaryOperatorNode = dynamic_cast<gd::UnaryOperatorNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number", unaryOperatorNode);
platform, projectScopedContainers, "number", unaryOperatorNode);
REQUIRE(unaryOperatorNode.op == '-');
REQUIRE(type == "number");
auto &numberNode =
@@ -583,7 +588,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &unaryOperatorNode = dynamic_cast<gd::UnaryOperatorNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number", unaryOperatorNode);
platform, projectScopedContainers, "number", unaryOperatorNode);
REQUIRE(unaryOperatorNode.op == '+');
REQUIRE(type == "number");
auto &numberNode =
@@ -599,7 +604,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &unaryOperatorNode = dynamic_cast<gd::UnaryOperatorNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number", unaryOperatorNode);
platform, projectScopedContainers, "number", unaryOperatorNode);
REQUIRE(unaryOperatorNode.op == '-');
REQUIRE(type == "number");
auto &numberNode =
@@ -617,7 +622,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &unaryOperatorNode = dynamic_cast<gd::UnaryOperatorNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", unaryOperatorNode);
platform, projectScopedContainers, "number|string", unaryOperatorNode);
REQUIRE(unaryOperatorNode.op == '-');
REQUIRE(type == "number");
auto &numberNode =
@@ -633,7 +638,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &unaryOperatorNode = dynamic_cast<gd::UnaryOperatorNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", unaryOperatorNode);
platform, projectScopedContainers, "number|string", unaryOperatorNode);
REQUIRE(unaryOperatorNode.op == '+');
REQUIRE(type == "number");
auto &numberNode =
@@ -649,7 +654,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &unaryOperatorNode = dynamic_cast<gd::UnaryOperatorNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", unaryOperatorNode);
platform, projectScopedContainers, "number|string", unaryOperatorNode);
REQUIRE(unaryOperatorNode.op == '-');
REQUIRE(type == "number");
auto &numberNode =
@@ -914,6 +919,242 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(validator.GetFatalErrors()[0]->GetEndPosition() == 19);
}
}
SECTION("Numbers and texts mismatches ('number|string' type, with a known variable type first)") {
{
auto node =
parser.ParseExpression("MySceneNumberVariable + \"hello world\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a text, but a number was expected.");
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 24);
}
{
auto node =
parser.ParseExpression("MySceneStringVariable + 123");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a number, but a text was expected (in quotes).");
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 24);
}
{
auto node =
parser.ParseExpression("MySceneNumberVariable + MySceneBooleanVariable + \"hello world\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a text, but a number was expected.");
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 49);
}
{
auto node =
parser.ParseExpression("MySceneStringVariable + MySceneBooleanVariable + 123");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a number, but a text was expected (in quotes).");
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 49);
}
{
auto node =
parser.ParseExpression("MySpriteObject.MyNumberVariable + \"hello world\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a text, but a number was expected.");
}
{
auto node =
parser.ParseExpression("MySpriteObject.MyStringVariable + 123");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a number, but a text was expected (in quotes).");
}
}
SECTION("Numbers and texts mismatches ('number|string' type, with a parameter first)") {
std::vector<gd::ParameterMetadata> parameters;
{
gd::ParameterMetadata param;
param.SetName("MyNumberParameter");
param.SetType("number");
parameters.push_back(param);
}
{
gd::ParameterMetadata param;
param.SetName("MyStringParameter");
param.SetType("string");
parameters.push_back(param);
}
{
gd::ParameterMetadata param;
param.SetName("MyBooleanParameter");
param.SetType("yesorno");
parameters.push_back(param);
}
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
{
auto node =
parser.ParseExpression("MyNumberParameter + \"hello world\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainersWithParameters, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a text, but a number was expected.");
}
{
auto node =
parser.ParseExpression("MyStringParameter + 123");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainersWithParameters, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a number, but a text was expected (in quotes).");
}
{
auto node =
parser.ParseExpression("MyNumberParameter + MyBooleanParameter + \"hello world\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainersWithParameters, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a text, but a number was expected.");
}
{
auto node =
parser.ParseExpression("MyStringParameter + MyBooleanParameter + 123");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainersWithParameters, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a number, but a text was expected (in quotes).");
}
}
SECTION("Numbers and texts mismatches ('number|string' type, with a property first)") {
gd::PropertiesContainer propertiesContainer(gd::EventsFunctionsContainer::Extension);
auto projectScopedContainersWithProperties = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithProperties.AddPropertiesContainer(propertiesContainer);
propertiesContainer.InsertNew("MyNumberProperty").SetType("Number");
propertiesContainer.InsertNew("MyStringProperty").SetType("String");
propertiesContainer.InsertNew("MyBooleanProperty").SetType("Boolean");
{
auto node =
parser.ParseExpression("MyNumberProperty + \"hello world\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainersWithProperties, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a text, but a number was expected.");
}
{
auto node =
parser.ParseExpression("MyStringProperty + 123");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainersWithProperties, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a number, but a text was expected (in quotes).");
}
{
auto node =
parser.ParseExpression("MyNumberProperty + MyBooleanProperty + \"hello world\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainersWithProperties, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a text, but a number was expected.");
}
{
auto node =
parser.ParseExpression("MyStringProperty + MyBooleanProperty + 123");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainersWithProperties, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a number, but a text was expected (in quotes).");
}
}
SECTION("Numbers and texts mismatches ('number|string' type, with an unknown variable type first)") {
{
auto node = parser.ParseExpression("MySceneBooleanVariable + 123 + \"hello world\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a text, but a number was expected.");
}
{
auto node = parser.ParseExpression("MySceneBooleanVariable + \"hello world\" + 123");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a number, but a text was expected (in quotes).");
}
{
auto node = parser.ParseExpression("MySceneStructureVariable.MyChild.UnknownSubChild + 123 + \"hello world\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a text, but a number was expected.");
}
{
auto node = parser.ParseExpression("MySceneStructureVariable.MyChild.UnknownSubChild + \"hello world\" + 123");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"You entered a number, but a text was expected (in quotes).");
}
}
SECTION("Numbers and texts mismatches with parenthesis") {
{
auto node =
@@ -1451,21 +1692,78 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
SECTION("Valid property") {
gd::PropertiesContainer propertiesContainer(gd::EventsFunctionsContainer::Extension);
auto projectScopedContainersWithProperties = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithProperties.AddPropertiesContainer(propertiesContainer);
propertiesContainer.InsertNew("MyProperty").SetType("Number");
propertiesContainer.InsertNew("MyProperty2").SetType("String");
propertiesContainer.InsertNew("MyProperty3").SetType("Boolean");
{
gd::PropertiesContainer propertiesContainer(gd::EventsFunctionsContainer::Extension);
auto node =
parser.ParseExpression("MyProperty");
auto projectScopedContainersWithProperties = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithProperties.AddPropertiesContainer(propertiesContainer);
gd::ExpressionValidator validator(platform, projectScopedContainersWithProperties, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
propertiesContainer.InsertNew("MyProperty");
propertiesContainer.InsertNew("MyProperty2");
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithProperties, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node =
parser.ParseExpression("MyProperty2");
gd::ExpressionValidator validator(platform, projectScopedContainersWithProperties, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithProperties, "number|string", *node.get());
REQUIRE(type == "string");
}
{
auto node =
parser.ParseExpression("MyProperty3");
gd::ExpressionValidator validator(platform, projectScopedContainersWithProperties, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithProperties, "number|string", *node.get());
REQUIRE(type == "number|string");
}
{
auto node =
parser.ParseExpression("MyProperty + MyProperty2");
gd::ExpressionValidator validator(platform, projectScopedContainersWithProperties, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithProperties, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node =
parser.ParseExpression("MyProperty + MyProperty2 + MyProperty3");
gd::ExpressionValidator validator(platform, projectScopedContainersWithProperties, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithProperties, "number|string", *node.get());
REQUIRE(type == "number");
}
}
@@ -1485,7 +1783,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
gd::ExpressionValidator validator(platform, projectScopedContainersWithProperties, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name.");
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You must wrap your text inside double quotes (example: \"Hello world\").");
}
}
@@ -1529,7 +1827,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
SECTION("Valid parameter") {
{
std::vector<gd::ParameterMetadata> parameters;
gd::ParameterMetadata param1;
param1.SetName("MyParameter1");
@@ -1537,18 +1834,75 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
gd::ParameterMetadata param2;
param2.SetName("MyParameter2");
param2.SetType("string");
gd::ParameterMetadata param3;
param3.SetName("MyParameter3");
param3.SetType("yesorno");
parameters.push_back(param1);
parameters.push_back(param2);
parameters.push_back(param3);
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
{
auto node =
parser.ParseExpression("MyParameter1");
gd::ExpressionValidator validator(platform, projectScopedContainersWithParameters, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithParameters, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node =
parser.ParseExpression("MyParameter2");
gd::ExpressionValidator validator(platform, projectScopedContainersWithParameters, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithParameters, "number|string", *node.get());
REQUIRE(type == "string");
}
{
auto node =
parser.ParseExpression("MyParameter3");
gd::ExpressionValidator validator(platform, projectScopedContainersWithParameters, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithParameters, "number|string", *node.get());
REQUIRE(type == "number|string");
}
{
auto node =
parser.ParseExpression("MyParameter1 + MyParameter2");
gd::ExpressionValidator validator(platform, projectScopedContainersWithParameters, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithParameters, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node =
parser.ParseExpression("MyParameter1 + MyParameter2 + MyParameter3");
gd::ExpressionValidator validator(platform, projectScopedContainersWithParameters, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithParameters, "number|string", *node.get());
REQUIRE(type == "number");
}
}
@@ -1598,7 +1952,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
gd::ExpressionValidator validator(platform, projectScopedContainersWithParameters, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name.");
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You must enter a number.");
}
}
@@ -1658,7 +2012,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", functionNode);
platform, projectScopedContainers, "number|string", functionNode);
REQUIRE(functionNode.functionName == "MyExtension::GetNumber");
REQUIRE(type == "number");
REQUIRE(functionNode.objectName == "");
@@ -1674,7 +2028,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", functionNode);
platform, projectScopedContainers, "number|string", functionNode);
REQUIRE(functionNode.functionName == "MyExtension::ToString");
REQUIRE(type == "string");
REQUIRE(functionNode.objectName == "");
@@ -1809,7 +2163,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &objectFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "string", objectFunctionCall);
platform, projectScopedContainers, "string", objectFunctionCall);
REQUIRE(objectFunctionCall.objectName == "MyObject");
REQUIRE(objectFunctionCall.functionName == "");
REQUIRE(type == "string");
@@ -1820,7 +2174,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &objectFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number", objectFunctionCall);
platform, projectScopedContainers, "number", objectFunctionCall);
REQUIRE(objectFunctionCall.objectName == "MyObject");
REQUIRE(objectFunctionCall.functionName == "");
REQUIRE(type == "number");
@@ -1833,7 +2187,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &objectFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", objectFunctionCall);
platform, projectScopedContainers, "number|string", objectFunctionCall);
REQUIRE(objectFunctionCall.objectName == "MyObject");
REQUIRE(objectFunctionCall.functionName == "");
REQUIRE(type == "number|string");
@@ -1854,7 +2208,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &objectFunctionName = dynamic_cast<gd::FunctionCallNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "string", objectFunctionName);
platform, projectScopedContainers, "string", objectFunctionName);
REQUIRE(objectFunctionName.objectName == "MyObject");
REQUIRE(objectFunctionName.behaviorName == "MyBehavior");
REQUIRE(objectFunctionName.functionName == "");
@@ -1866,7 +2220,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &objectFunctionName = dynamic_cast<gd::FunctionCallNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number", objectFunctionName);
platform, projectScopedContainers, "number", objectFunctionName);
REQUIRE(objectFunctionName.objectName == "MyObject");
REQUIRE(objectFunctionName.behaviorName == "MyBehavior");
REQUIRE(objectFunctionName.functionName == "");
@@ -1881,7 +2235,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &objectFunctionName = dynamic_cast<gd::FunctionCallNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", objectFunctionName);
platform, projectScopedContainers, "number|string", objectFunctionName);
REQUIRE(objectFunctionName.objectName == "MyObject");
REQUIRE(objectFunctionName.behaviorName == "MyBehavior");
REQUIRE(objectFunctionName.functionName == "");
@@ -1893,7 +2247,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &freeFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "string", freeFunctionCall);
platform, projectScopedContainers, "string", freeFunctionCall);
REQUIRE(freeFunctionCall.objectName == "");
REQUIRE(freeFunctionCall.functionName == "fun");
REQUIRE(type == "string");
@@ -1904,7 +2258,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &freeFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number", freeFunctionCall);
platform, projectScopedContainers, "number", freeFunctionCall);
REQUIRE(freeFunctionCall.objectName == "");
REQUIRE(freeFunctionCall.functionName == "fun");
REQUIRE(type == "number");
@@ -1916,7 +2270,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &freeFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", freeFunctionCall);
platform, projectScopedContainers, "number|string", freeFunctionCall);
REQUIRE(freeFunctionCall.objectName == "");
REQUIRE(freeFunctionCall.functionName == "fun");
REQUIRE(type == "number|string");
@@ -2272,21 +2626,21 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
auto node = parser.ParseExpression("123");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", *node.get());
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node = parser.ParseExpression("123 + MyExtension::GetNumber()");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", *node.get());
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node = parser.ParseExpression("\"Hello\"");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", *node.get());
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "string");
}
{
@@ -2294,10 +2648,160 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
"\"Hello\" + MyExtension::ToString(3)");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, objectsContainersList, "number|string", *node.get());
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "string");
}
}
SECTION("Valid type inferred from expressions with type 'number|string', with an known variable first") {
{
auto node = parser.ParseExpression("MySceneNumberVariable + 123");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node = parser.ParseExpression("MySceneStringVariable + \"hello world\"");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "string");
}
{
auto node = parser.ParseExpression("MySceneNumberVariable + MySceneBooleanVariable + 123");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node = parser.ParseExpression("MySceneStringVariable + MySceneBooleanVariable + \"hello world\"");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "string");
}
}
SECTION("Valid type inferred from expressions with type 'number|string', with an unknown variable first") {
{
auto node = parser.ParseExpression("MySceneBooleanVariable + 123");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node = parser.ParseExpression("MySceneBooleanVariable + \"hello world\"");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "string");
}
{
auto node = parser.ParseExpression("MySceneStructureVariable.MyChild.UnknownSubChild + 123");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node = parser.ParseExpression("MySceneStructureVariable.MyChild.UnknownSubChild + \"hello world\"");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "string");
}
}
SECTION("Valid type inferred from expressions with type 'number|string', with a property first") {
gd::PropertiesContainer propertiesContainer(gd::EventsFunctionsContainer::Extension);
auto projectScopedContainersWithProperties = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithProperties.AddPropertiesContainer(propertiesContainer);
propertiesContainer.InsertNew("MyNumberProperty").SetType("Number");
propertiesContainer.InsertNew("MyStringProperty").SetType("String");
propertiesContainer.InsertNew("MyBooleanProperty").SetType("Boolean");
{
auto node = parser.ParseExpression("MyNumberProperty + 123");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node = parser.ParseExpression("MyStringProperty + \"hello world\"");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "string");
}
{
auto node = parser.ParseExpression("MyBooleanProperty + 123");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node = parser.ParseExpression("MyBooleanProperty + \"hello world\"");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, "number|string", *node.get());
REQUIRE(type == "string");
}
}
SECTION("Valid type inferred from expressions with type 'number|string', with a parameter first") {
std::vector<gd::ParameterMetadata> parameters;
{
gd::ParameterMetadata param;
param.SetName("MyNumberParameter");
param.SetType("number");
parameters.push_back(param);
}
{
gd::ParameterMetadata param;
param.SetName("MyStringParameter");
param.SetType("string");
parameters.push_back(param);
}
{
gd::ParameterMetadata param;
param.SetName("MyBooleanParameter");
param.SetType("yesorno");
parameters.push_back(param);
}
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
{
auto node = parser.ParseExpression("MyNumberParameter + 123");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithParameters, "number|string", *node.get());
REQUIRE(type == "number");
}
{
auto node = parser.ParseExpression("MyStringParameter + \"hello world\"");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithParameters, "number|string", *node.get());
REQUIRE(type == "string");
}
{
auto node = parser.ParseExpression("MyBooleanParameter + \"hello world\"");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithParameters, "number|string", *node.get());
REQUIRE(type == "string");
}
{
auto node = parser.ParseExpression("MyBooleanParameter + 123");
REQUIRE(node != nullptr);
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainersWithParameters, "number|string", *node.get());
REQUIRE(type == "number");
}
}
SECTION("Valid function call with object variable") {
{
@@ -1381,7 +1381,16 @@ gd::String EventsCodeGenerator::GeneratePropertyGetter(
property.GetName())) +
"()";
if (type == "string") {
if (type == "number|string") {
if (property.GetType() == "Number") {
return propertyGetterCode;
} else if (property.GetType() == "Boolean") {
return "(" + propertyGetterCode + " ? \"true\" : \"false\")";
} else {
// Assume type is String or equivalent.
return propertyGetterCode;
}
} else if (type == "string") {
if (property.GetType() == "Number") {
return "(\"\" + " + propertyGetterCode + ")";
} else if (property.GetType() == "Boolean") {
@@ -1413,7 +1422,16 @@ gd::String EventsCodeGenerator::GenerateParameterGetter(
gd::String parameterGetterCode =
"eventsFunctionContext.getArgument(" + ConvertToStringExplicit(parameter.GetName()) + ")";
if (type == "string") {
if (type == "number|string") {
if (parameter.GetValueTypeMetadata().IsNumber()) {
return parameterGetterCode;
} else if (parameter.GetValueTypeMetadata().IsBoolean()) {
return "(" + parameterGetterCode + " ? \"true\" : \"false\")";
} else {
// Assume type is String or equivalent.
return parameterGetterCode;
}
} else if (type == "string") {
if (parameter.GetValueTypeMetadata().IsNumber()) {
return "(\"\" + " + parameterGetterCode + ")";
} else if (parameter.GetValueTypeMetadata().IsBoolean()) {
@@ -302,11 +302,17 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
const gd::String& objectName);
virtual gd::String GenerateVariableAccessor(gd::String childName) {
// This could be probably optimised by using `getChildNamed`.
return ".getChild(" + ConvertToStringExplicit(childName) + ")";
};
virtual gd::String GenerateVariableBracketAccessor(
gd::String expressionCode) {
// This uses `getChild` which allows to access a child
// with a number (an index, for an array) or a string (for a structure).
// This could be optimised, if the type of the accessed variable AND the type of the index is known,
// so that `getChildAt` (for an array, with an index) or `getChildNamed` (for a structure, with a name)
// is used instead.
return ".getChild(" + expressionCode + ")";
};
+52 -17
View File
@@ -255,26 +255,43 @@ namespace gdjs {
}
}
/**
* Get the child with the specified name or at the specified index.
*
* If the variable is an array, prefer `getChildAt`.
* If the variable is a structure, prefer `getChildNamed`.
*
* If the variable has not the specified child, an empty variable with the specified name
* (or index) is added as child.
*
* @returns The child variable
*/
getChild(childName: string | number): gdjs.Variable {
if (this._type === 'array')
return this.getChildAt(
// @ts-ignore
Number.isInteger(childName) ? childName : parseInt(childName, 10) || 0
);
else {
if (this._type !== 'structure') this.castTo('structure');
return this.getChildNamed('' + childName);
}
}
/**
* Get the child with the specified name.
*
* If the variable has not the specified child, an empty variable with the specified name
* is added as child.
*
* @returns The child variable
*/
getChild(childName: string): gdjs.Variable {
// Make sure the variable is a collection
if (this.isPrimitive()) this.castTo('structure');
getChildNamed(childName: string): gdjs.Variable {
const child = this._children[childName];
if (child === undefined || child === null)
return (this._children[childName] = new gdjs.Variable());
if (this._type === 'array')
return this.getChildAt(parseInt(childName, 10) || 0);
if (
this._children[childName] === undefined ||
this._children[childName] === null
)
this._children[childName] = new gdjs.Variable();
return this._children[childName];
return child;
}
/**
@@ -286,8 +303,8 @@ namespace gdjs {
* @returns The variable (for chaining calls)
*/
addChild(childName: string, childVariable: gdjs.Variable): this {
// Make sure this is a structure
this.castTo('structure');
if (this._type !== 'structure') this.castTo('structure');
this._children[childName] = childVariable;
return this;
}
@@ -393,6 +410,21 @@ namespace gdjs {
this._str = '' + newValue;
}
/**
* Get the value of the variable, as a number if it's one,
* or as a string (if it's a string or something else)
*
* In most cases, prefer calling `getAsNumber` or `getAsString` directly.
* This is a fallback in case a variable type can't be known statically for sure,
* like `getValue`.
*
* @private
*/
getAsNumberOrString(): number | string {
if (this._type === 'number') return this._value;
return this.getAsString();
}
/**
* Get the value of the variable, considered as a boolean
* @return The boolean value of the variable.
@@ -550,7 +582,8 @@ namespace gdjs {
* Get a variable at a given index of the array.
*/
getChildAt(index: integer) {
this.castTo('array');
if (this._type !== 'array') this.castTo('array');
if (
this._childrenArray[index] === undefined ||
this._childrenArray[index] === null
@@ -570,7 +603,8 @@ namespace gdjs {
* Pushes a copy of a variable into the array.
*/
pushVariableCopy(variable: gdjs.Variable) {
this.castTo('array');
if (this._type !== 'array') this.castTo('array');
this._childrenArray.push(variable.clone());
}
@@ -578,7 +612,8 @@ namespace gdjs {
* Pushes a value into the array.
*/
pushValue(value: string | float | boolean) {
this.castTo('array');
if (this._type !== 'array') this.castTo('array');
this._childrenArray.push(
new gdjs.Variable({
type: typeof value as 'string' | 'number' | 'boolean',
+4
View File
@@ -244,6 +244,7 @@ namespace gdjs {
getValue: () => 0,
getChild: () => gdjs.VariablesContainer.badVariable,
getChildAt: () => gdjs.VariablesContainer.badVariable,
getChildNamed: () => gdjs.VariablesContainer.badVariable,
hasChild: function () {
return false;
},
@@ -271,6 +272,9 @@ namespace gdjs {
getAsNumber: function () {
return 0;
},
getAsNumberOrString: function () {
return 0;
},
getAsBoolean: function () {
return false;
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

File diff suppressed because it is too large Load Diff
+6
View File
@@ -17,6 +17,7 @@ describe('gdjs.Variable', function () {
expect(intVar.getAsNumber()).to.be(526);
expect(intVar.getAsString()).to.be('526');
expect(intVar.getAsNumberOrString()).to.be(526);
expect(intVar.getAsBoolean()).to.be(true);
expect(intVar.getType()).to.be('number');
@@ -25,11 +26,13 @@ describe('gdjs.Variable', function () {
expect(floatVar.getAsNumber()).to.be(10.568);
expect(floatVar.getAsString()).to.be('10.568');
expect(floatVar.getAsNumberOrString()).to.be(10.568);
expect(floatVar.getAsBoolean()).to.be(true);
expect(floatVar.getType()).to.be('number');
expect(strVar.getAsNumber()).to.be(0);
expect(strVar.getAsString()).to.be('test variable');
expect(strVar.getAsNumberOrString()).to.be('test variable');
expect(strVar.getAsBoolean()).to.be(true);
expect(strVar.getType()).to.be('string');
@@ -45,12 +48,14 @@ describe('gdjs.Variable', function () {
expect(numStrVar.getAsNumber()).to.be(5);
expect(numStrVar.getAsString()).to.be('5Apples');
expect(numStrVar.getAsBoolean()).to.be(true);
expect(numStrVar.getAsNumberOrString()).to.be('5Apples');
expect(numStrVar.getType()).to.be('string');
expect(boolVar.getType()).to.be('boolean');
expect(boolVar.getAsString()).to.be('true');
expect(boolVar.getAsNumber()).to.be(1);
expect(boolVar.getAsBoolean()).to.be(true);
expect(boolVar.getAsNumberOrString()).to.be('true');
});
it('should do some variable arithmetics', function () {
@@ -66,6 +71,7 @@ describe('gdjs.Variable', function () {
expect(a.getAsNumber()).to.be(3);
a.concatenateString('Apples');
expect(a.getAsString()).to.be('3Apples');
expect(a.getAsNumberOrString()).to.be('3Apples');
});
it('should clear a collection', function () {
+1 -1
View File
@@ -2600,7 +2600,7 @@ interface ExpressionNodeLocationFinder {
};
interface ExpressionTypeFinder {
[Const, Ref] DOMString STATIC_GetType([Const, Ref] Platform platform, [Const, Ref] ObjectsContainersList objectsContainersList, [Const] DOMString rootType, [Ref] ExpressionNode node);
[Const, Ref] DOMString STATIC_GetType([Const, Ref] Platform platform, [Const, Ref] ProjectScopedContainers projectScopedContainers, [Const] DOMString rootType, [Ref] ExpressionNode node);
};
interface ExpressionNode {
+4
View File
@@ -191,6 +191,10 @@ class Variable {
this.setString(this.getAsString() + str);
}
getAsNumberOrString() {
return this._value;
}
addChild(childName, childVariable) {
// Make sure this is a structure
this.castTo('structure');
@@ -171,109 +171,198 @@ describe('libGD.js - GDJS Scene Code Generation integration tests', function ()
project.delete();
});
// TODO: this does not pass because of string variables not understood as strings inside brackets.
// it('generates code for scene variables, including structures being accessed by other variables', function () {
// const project = new gd.ProjectHelper.createNewGDJSProject();
// const layout = project.insertNewLayout('Scene', 0);
// layout.insertNewObject(project, '', 'MyObject', 0);
// layout.getVariables().insertNew('MyNumberVariable', 0).setValue(123);
// layout.getVariables().insertNew('MyStringVariable', 1).setString('Test');
// layout
// .getVariables()
// .insertNew('MyOtherStringVariable', 1)
// .setString('SomeChild');
// layout
// .getVariables()
// .insertNew('MyOtherStringVariable2', 1)
// .setString('SomeOtherChild');
// const structureVariable = layout
// .getVariables()
// .insertNew('MyStructureVariable', 2);
// structureVariable.getChild('Test').setValue(1);
// structureVariable.getChild('123').setValue(2);
// structureVariable.getChild('42').setValue(4);
// structureVariable.getChild('MyObject').setValue(8);
// structureVariable.getChild('SomeChild').getChild('Test').setValue(16);
// structureVariable.getChild('SomeChild').getChild('123').setValue(32);
// structureVariable.getChild('SomeChild').getChild('42').setValue(64);
// structureVariable.getChild('SomeChild').getChild('MyObject').setValue(128);
// structureVariable
// .getChild('SomeChild')
// .getChild('SomeOtherChild')
// .getChild('Test')
// .setValue(256);
// structureVariable
// .getChild('SomeChild')
// .getChild('SomeOtherChild')
// .getChild('123')
// .setValue(512);
// structureVariable
// .getChild('SomeChild')
// .getChild('SomeOtherChild')
// .getChild('42')
// .setValue(1024);
// structureVariable
// .getChild('SomeChild')
// .getChild('SomeOtherChild')
// .getChild('MyObject')
// .setValue(2048);
// const serializedLayoutEvents = gd.Serializer.fromJSObject([
// {
// type: 'BuiltinCommonInstructions::Standard',
// conditions: [],
// actions: [
// {
// type: { value: 'ModVarScene' },
// parameters: [
// 'Counter',
// '+',
// 'MyStructureVariable[MyStringVariable] + MyStructureVariable[MyNumberVariable] + MyStructureVariable[MyObject.X()] + MyStructureVariable[MyObject.ObjectName()]',
// ],
// },
// {
// type: { value: 'ModVarScene' },
// parameters: [
// 'Counter',
// '+',
// 'MyStructureVariable.SomeChild[MyStringVariable] + MyStructureVariable.SomeChild[MyNumberVariable] + MyStructureVariable.SomeChild[MyObject.X()] + MyStructureVariable.SomeChild[MyObject.ObjectName()]',
// ],
// },
// {
// type: { value: 'ModVarScene' },
// parameters: [
// 'Counter',
// '+',
// 'MyStructureVariable[MyOtherStringVariable][MyOtherStringVariable2][MyStringVariable] +MyStructureVariable[MyOtherStringVariable][MyOtherStringVariable2][MyNumberVariable] + MyStructureVariable[MyOtherStringVariable][MyOtherStringVariable2][MyObject.X()] + MyStructureVariable[MyOtherStringVariable][MyOtherStringVariable2][MyObject.ObjectName()]',
// ],
// },
// ],
// events: [],
// },
// ]);
// layout.getEvents().unserializeFrom(project, serializedLayoutEvents);
describe('(Scene) variable code generation', () => {
let project = null;
let layout = null;
beforeEach(() => {
project = new gd.ProjectHelper.createNewGDJSProject();
layout = project.insertNewLayout('Scene', 0);
layout.insertNewObject(project, '', 'MyObject', 0);
// const runCompiledEvents = generateCompiledEventsForLayout(
// gd,
// project,
// layout
// );
// These variables are "simple" and their type will be known at code generation.
layout.getVariables().insertNew('MyNumberVariable', 0).setValue(123);
layout.getVariables().insertNew('MyStringVariable', 1).setString('Test');
layout
.getVariables()
.insertNew('MyOtherStringVariable', 1)
.setString('SomeChild');
// const serializedSceneElement = new gd.SerializerElement();
// layout.serializeTo(serializedSceneElement);
// Use a variable that has a value deep inside a structure - so code generation
// does not know its type and will issue a `getAsNumberOrString`.
layout
.getVariables()
.insertNew('MyOtherStructureVariable', 1)
.getChild('Child')
.getChild('SubChild')
.setString('SomeOtherChild');
const structureVariable = layout
.getVariables()
.insertNew('MyStructureVariable', 2);
// const { gdjs, runtimeScene } = makeMinimalGDJSMock({
// sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)),
// });
// serializedSceneElement.delete();
// const myObjectInstance = runtimeScene.createObject('MyObject');
// myObjectInstance.setX(42);
// runCompiledEvents(gdjs, runtimeScene);
// Make a structure that we will address using other variables.
structureVariable.getChild('Test').setValue(1);
structureVariable.getChild('123').setValue(2);
structureVariable.getChild('42').setValue(4);
structureVariable.getChild('MyObject').setValue(8);
structureVariable.getChild('SomeChild').getChild('Test').setValue(16);
structureVariable.getChild('SomeChild').getChild('123').setValue(32);
structureVariable.getChild('SomeChild').getChild('42').setValue(64);
structureVariable
.getChild('SomeChild')
.getChild('MyObject')
.setValue(128);
structureVariable
.getChild('SomeChild')
.getChild('SomeOtherChild')
.getChild('Test')
.setValue(256);
structureVariable
.getChild('SomeChild')
.getChild('SomeOtherChild')
.getChild('123')
.setValue(512);
structureVariable
.getChild('SomeChild')
.getChild('SomeOtherChild')
.getChild('42')
.setValue(1024);
structureVariable
.getChild('SomeChild')
.getChild('SomeOtherChild')
.getChild('MyObject')
.setValue(2048);
});
afterEach(() => {
project.delete();
});
// expect(runtimeScene.getVariables().has('Counter')).toBe(true);
// expect(runtimeScene.getVariables().get('Counter').getAsNumber()).toBe(
// 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 + 512 + 1024 + 2048
// );
it('generates code for structure variables accessed by another variable (1 level)', function () {
const serializedLayoutEvents = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [],
actions: [
{
type: { value: 'ModVarScene' },
parameters: [
'Counter',
'+',
'MyStructureVariable[MyStringVariable] + MyStructureVariable[MyNumberVariable] + MyStructureVariable[MyObject.X()] + MyStructureVariable[MyObject.ObjectName()]',
],
},
],
events: [],
},
]);
layout.getEvents().unserializeFrom(project, serializedLayoutEvents);
// project.delete();
// });
const runCompiledEvents = generateCompiledEventsForLayout(
gd,
project,
layout
);
const serializedSceneElement = new gd.SerializerElement();
layout.serializeTo(serializedSceneElement);
const { gdjs, runtimeScene } = makeMinimalGDJSMock({
sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)),
});
serializedSceneElement.delete();
const myObjectInstance = runtimeScene.createObject('MyObject');
myObjectInstance.setX(42);
runCompiledEvents(gdjs, runtimeScene);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
expect(runtimeScene.getVariables().get('Counter').getAsNumber()).toBe(
1 + 2 + 4 + 8
);
});
it('generates code for structure variables accessed by another variable (2 levels)', function () {
const serializedLayoutEvents = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [],
actions: [
{
type: { value: 'ModVarScene' },
parameters: [
'Counter',
'+',
'MyStructureVariable.SomeChild[MyStringVariable] + MyStructureVariable.SomeChild[MyNumberVariable] + MyStructureVariable.SomeChild[MyObject.X()] + MyStructureVariable.SomeChild[MyObject.ObjectName()]',
],
},
],
events: [],
},
]);
layout.getEvents().unserializeFrom(project, serializedLayoutEvents);
const runCompiledEvents = generateCompiledEventsForLayout(
gd,
project,
layout
);
const serializedSceneElement = new gd.SerializerElement();
layout.serializeTo(serializedSceneElement);
const { gdjs, runtimeScene } = makeMinimalGDJSMock({
sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)),
});
serializedSceneElement.delete();
const myObjectInstance = runtimeScene.createObject('MyObject');
myObjectInstance.setX(42);
runCompiledEvents(gdjs, runtimeScene);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
expect(runtimeScene.getVariables().get('Counter').getAsNumber()).toBe(
16 + 32 + 64 + 128
);
});
it('generates code for structure variables accessed by another variable (3 levels)', function () {
const serializedLayoutEvents = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [],
actions: [
{
type: { value: 'ModVarScene' },
parameters: [
'Counter',
'+',
'MyStructureVariable[MyOtherStringVariable][MyOtherStructureVariable.Child.SubChild][MyStringVariable] +MyStructureVariable[MyOtherStringVariable][MyOtherStructureVariable.Child.SubChild][MyNumberVariable] + MyStructureVariable[MyOtherStringVariable][MyOtherStructureVariable.Child.SubChild][MyObject.X()] + MyStructureVariable[MyOtherStringVariable][MyOtherStructureVariable.Child.SubChild][MyObject.ObjectName()]',
],
},
],
events: [],
},
]);
layout.getEvents().unserializeFrom(project, serializedLayoutEvents);
const runCompiledEvents = generateCompiledEventsForLayout(
gd,
project,
layout
);
const serializedSceneElement = new gd.SerializerElement();
layout.serializeTo(serializedSceneElement);
const { gdjs, runtimeScene } = makeMinimalGDJSMock({
sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)),
});
serializedSceneElement.delete();
const myObjectInstance = runtimeScene.createObject('MyObject');
myObjectInstance.setX(42);
runCompiledEvents(gdjs, runtimeScene);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
expect(runtimeScene.getVariables().get('Counter').getAsNumber()).toBe(
256 + 512 + 1024 + 2048
);
});
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdExpressionTypeFinder {
static getType(platform: gdPlatform, objectsContainersList: gdObjectsContainersList, rootType: string, node: gdExpressionNode): string;
static getType(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, rootType: string, node: gdExpressionNode): string;
delete(): void;
ptr: number;
};
@@ -273,6 +273,7 @@ export default class ExpressionField extends React.Component<Props, State> {
const {
globalObjectsContainer,
objectsContainer,
scope,
expressionType,
value,
} = this.props;
@@ -296,12 +297,14 @@ export default class ExpressionField extends React.Component<Props, State> {
expressionNode,
cursorPosition + 'fakeIdentifier'.length - 1
);
const projectScopedContainers = getProjectScopedContainersFromScope(
scope,
globalObjectsContainer,
objectsContainer
);
const type = gd.ExpressionTypeFinder.getType(
gd.JsPlatform.get(),
gd.ObjectsContainersList.makeNewObjectsContainersListForContainers(
globalObjectsContainer,
objectsContainer
),
projectScopedContainers,
expressionType,
currentNode
);