From 43d78a74bd4db4374965ef6394d1236ebc7a8652 Mon Sep 17 00:00:00 2001 From: Florian Rival Date: Tue, 4 Dec 2018 00:44:26 +0000 Subject: [PATCH] Enhance ExpressionParser2 and add ExpressionCodeGenerator --- .../CodeGeneration/EventsCodeGenerator.h | 1 + .../ExpressionCodeGenerator.cpp | 262 ++++++++++++++++++ .../CodeGeneration/ExpressionCodeGenerator.h | 82 ++++++ .../GDCore/Events/Parsers/ExpressionParser2.h | 29 +- .../Events/Parsers/ExpressionParser2Node.h | 52 +++- .../Parsers/ExpressionParser2NodeWorker.h | 3 + Core/GDCore/IDE/Events/ExpressionValidator.h | 4 + Core/tests/DummyPlatform.cpp | 58 ++++ Core/tests/DummyPlatform.h | 18 ++ Core/tests/ExpressionCodeGenerator.cpp | 154 ++++++++++ Core/tests/ExpressionParser2.cpp | 94 +++---- 11 files changed, 680 insertions(+), 77 deletions(-) create mode 100644 Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.cpp create mode 100644 Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.h create mode 100644 Core/tests/DummyPlatform.cpp create mode 100644 Core/tests/DummyPlatform.h create mode 100644 Core/tests/ExpressionCodeGenerator.cpp diff --git a/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.h b/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.h index d171ddcf40..2289ea13d5 100644 --- a/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.h +++ b/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.h @@ -34,6 +34,7 @@ namespace gd { */ class GD_CORE_API EventsCodeGenerator { friend class CallbacksForGeneratingExpressionCode; + friend class ExpressionCodeGenerator; public: /** diff --git a/Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.cpp b/Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.cpp new file mode 100644 index 0000000000..bf7fe71c59 --- /dev/null +++ b/Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.cpp @@ -0,0 +1,262 @@ +/* + * GDevelop Core + * Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "ExpressionCodeGenerator.h" +#include +#include +#include "GDCore/CommonTools.h" +#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h" +#include "GDCore/Events/CodeGeneration/EventsCodeGenerator.h" +#include "GDCore/Events/Parsers/ExpressionParser2Node.h" +#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h" +#include "GDCore/Events/Tools/EventsCodeNameMangler.h" +#include "GDCore/Extensions/Metadata/BehaviorMetadata.h" +#include "GDCore/Extensions/Metadata/ExpressionMetadata.h" +#include "GDCore/Extensions/Metadata/MetadataProvider.h" +#include "GDCore/Extensions/Metadata/ObjectMetadata.h" +#include "GDCore/Extensions/Platform.h" +#include "GDCore/Extensions/PlatformExtension.h" +#include "GDCore/Project/Layout.h" +#include "GDCore/Project/Project.h" +namespace gd { + +void ExpressionCodeGenerator::OnVisitOperatorNode(OperatorNode& node) { + node.leftHandSide->Visit(*this); + output += " "; + output.push_back(node.op); + output += " "; + node.rightHandSide->Visit(*this); +} + +void ExpressionCodeGenerator::OnVisitSubExpressionNode( + SubExpressionNode& node) { + output += "("; + node.expression->Visit(*this); + output += ")"; +} + +void ExpressionCodeGenerator::OnVisitNumberNode(NumberNode& node) { + output += node.number; +} + +void ExpressionCodeGenerator::OnVisitTextNode(TextNode& node) { + output += codeGenerator.ConvertToStringExplicit(node.text); +} + +void ExpressionCodeGenerator::OnVisitVariableNode(VariableNode& node) {} + +void ExpressionCodeGenerator::OnVisitVariableAccessorNode( + VariableAccessorNode& node) {} + +void ExpressionCodeGenerator::OnVisitVariableBracketAccessorNode( + VariableBracketAccessorNode& node) {} + +void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) { + output += codeGenerator.ConvertToStringExplicit(node.identifierName); +} + +void ExpressionCodeGenerator::OnVisitFunctionNode(FunctionNode& node) { + if (gd::MetadataProvider::IsBadExpressionMetadata(node.expressionMetadata)) { + output += "/* Error during generation, function not found */ " + + GenerateDefaultValue(node.type); + return; + } + + if (!node.objectName.empty()) { + if (!node.behaviorName.empty()) { + output += GenerateBehaviorFunctionCode(node.type, + node.objectName, + node.behaviorName, + node.parameters, + node.expressionMetadata); + } else { + output += GenerateObjectFunctionCode( + node.type, node.objectName, node.parameters, node.expressionMetadata); + } + } else { + output += + GenerateFreeFunctionCode(node.parameters, node.expressionMetadata); + } +} + +gd::String ExpressionCodeGenerator::GenerateFreeFunctionCode( + const std::vector>& parameters, + const ExpressionMetadata& expressionMetadata) { + codeGenerator.AddIncludeFiles( + expressionMetadata.codeExtraInformation.GetIncludeFiles()); + + // Launch custom code generator if needed + // TODO: Add support for custom code generator + // if (expressionMetadata.codeExtraInformation.HasCustomCodeGenerator()) { + // output += expressionMetadata.codeExtraInformation.customCodeGenerator( + // parameters, codeGenerator, context); + // return; + // } + + gd::String parametersCode = + GenerateParametersCodes(parameters, expressionMetadata, 0); + + return expressionMetadata.codeExtraInformation.functionCallName + "(" + + parametersCode + ")"; +} + +gd::String ExpressionCodeGenerator::GenerateObjectFunctionCode( + const gd::String& type, + const gd::String& objectName, + const std::vector>& parameters, + const ExpressionMetadata& expressionMetadata) { + const gd::ObjectsContainer& globalObjectsAndGroups = + codeGenerator.GetGlobalObjectsAndGroups(); + const gd::ObjectsContainer& objectsAndGroups = + codeGenerator.GetObjectsAndGroups(); + + codeGenerator.AddIncludeFiles( + expressionMetadata.codeExtraInformation.GetIncludeFiles()); + + // Launch custom code generator if needed + // TODO: Add support for custom code generator + // if (expressionMetadata.codeExtraInformation.HasCustomCodeGenerator()) { + // output += expressionMetadata.codeExtraInformation.customCodeGenerator( + // parameters, codeGenerator, context); + // return; + // } + + // Prepare parameters + gd::String parametersCode = GenerateParametersCodes( + parameters, + expressionMetadata, + // By convention, the first parameter is the object + 1); + + gd::String functionOutput = GenerateDefaultValue(type); + + // Get object(s) concerned by function call + std::vector realObjects = + codeGenerator.ExpandObjectsName(objectName, context); + for (std::size_t i = 0; i < realObjects.size(); ++i) { + context.ObjectsListNeeded(realObjects[i]); + + gd::String objectType = gd::GetTypeOfObject( + globalObjectsAndGroups, objectsAndGroups, realObjects[i]); + const ObjectMetadata& objInfo = MetadataProvider::GetObjectMetadata( + codeGenerator.GetPlatform(), objectType); + + codeGenerator.AddIncludeFiles(objInfo.includeFiles); + functionOutput = codeGenerator.GenerateObjectFunctionCall( + realObjects[i], + objInfo, + expressionMetadata.codeExtraInformation, + parametersCode, + functionOutput, + context); + } + + return functionOutput; +} +gd::String ExpressionCodeGenerator::GenerateBehaviorFunctionCode( + const gd::String& type, + const gd::String& objectName, + const gd::String& behaviorName, + const std::vector>& parameters, + const ExpressionMetadata& expressionMetadata) { + const gd::ObjectsContainer& globalObjectsAndGroups = + codeGenerator.GetGlobalObjectsAndGroups(); + const gd::ObjectsContainer& objectsAndGroups = + codeGenerator.GetObjectsAndGroups(); + + codeGenerator.AddIncludeFiles( + expressionMetadata.codeExtraInformation.GetIncludeFiles()); + + // Launch custom code generator if needed + // TODO: Add support for custom code generator + // if (expressionMetadata.codeExtraInformation.HasCustomCodeGenerator()) { + // output += expressionMetadata.codeExtraInformation.customCodeGenerator( + // parameters, codeGenerator, context); + // return; + // } + + // Prepare parameters + gd::String parametersCode = GenerateParametersCodes( + parameters, + expressionMetadata, + // By convention, the first parameters are the object and behavior + 2); + + // Get object(s) concerned by function call + std::vector realObjects = + codeGenerator.ExpandObjectsName(objectName, context); + + gd::String functionOutput = GenerateDefaultValue(type); + + gd::String behaviorType = gd::GetTypeOfBehavior( + globalObjectsAndGroups, objectsAndGroups, behaviorName); + const BehaviorMetadata& autoInfo = MetadataProvider::GetBehaviorMetadata( + codeGenerator.GetPlatform(), behaviorType); + + for (std::size_t i = 0; i < realObjects.size(); ++i) { + context.ObjectsListNeeded(realObjects[i]); + + codeGenerator.AddIncludeFiles(autoInfo.includeFiles); + functionOutput = codeGenerator.GenerateObjectBehaviorFunctionCall( + realObjects[i], + behaviorName, + autoInfo, + expressionMetadata.codeExtraInformation, + parametersCode, + functionOutput, + context); + } + + return functionOutput; +} + +gd::String ExpressionCodeGenerator::GenerateParametersCodes( + const std::vector>& parameters, + const ExpressionMetadata& expressionMetadata, + size_t initialParameterIndex) { + size_t nonCodeOnlyParameterIndex = 0; + gd::String parametersCode; + for (std::size_t i = initialParameterIndex; + i < expressionMetadata.parameters.size(); + ++i) { + if (i != initialParameterIndex) parametersCode += ", "; + + auto& parameterMetadata = expressionMetadata.parameters[i]; + if (!parameterMetadata.IsCodeOnly()) { + ExpressionCodeGenerator generator(codeGenerator, context); + if (nonCodeOnlyParameterIndex < parameters.size()) { + parameters[nonCodeOnlyParameterIndex]->Visit(generator); + parametersCode += generator.GetOutput(); + } else { + parametersCode += + "/* Error during generation, parameter not existing in the nodes " + "*/ " + + GenerateDefaultValue(parameterMetadata.GetType()); + } + + nonCodeOnlyParameterIndex++; + } else { + parametersCode += + codeGenerator.GenerateParameterCodes(parameterMetadata.GetExtraInfo(), + parameterMetadata, + context, + "", + nullptr); + } + } + + return parametersCode; +} + +gd::String ExpressionCodeGenerator::GenerateDefaultValue( + const gd::String& type) { + return type == "string" ? "\"\"" : "0"; +} + +void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) { + output += GenerateDefaultValue(node.type); +} + +} // namespace gd diff --git a/Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.h b/Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.h new file mode 100644 index 0000000000..70c49a6a14 --- /dev/null +++ b/Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.h @@ -0,0 +1,82 @@ +/* + * GDevelop Core + * Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#ifndef GDCORE_ExpressionCodeGenerator_H +#define GDCORE_ExpressionCodeGenerator_H + +#include +#include +#include "GDCore/Events/Parsers/ExpressionParser2Node.h" +#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h" +#include "GDCore/String.h" +namespace gd { +class Expression; +class ObjectsContainer; +class Platform; +class ParameterMetadata; +class ExpressionMetadata; +class EventsCodeGenerationContext; +class EventsCodeGenerator; +} // namespace gd + +namespace gd { + +/** + * \brief Generate code for a parsed expression. + * + * Code is output in a C-like/JavaScript compatible syntax. + * \see gd::ExpressionParser2 + */ +class GD_CORE_API ExpressionCodeGenerator : public ExpressionParser2NodeWorker { + public: + ExpressionCodeGenerator(EventsCodeGenerator& codeGenerator_, + EventsCodeGenerationContext& context_) + : codeGenerator(codeGenerator_), context(context_){}; + virtual ~ExpressionCodeGenerator(){}; + + const gd::String& GetOutput() { return output; }; + + protected: + void OnVisitSubExpressionNode(SubExpressionNode& node) override; + void OnVisitOperatorNode(OperatorNode& node) override; + void OnVisitNumberNode(NumberNode& node) override; + void OnVisitTextNode(TextNode& node) override; + void OnVisitVariableNode(VariableNode& node) override; + void OnVisitVariableAccessorNode(VariableAccessorNode& node) override; + void OnVisitVariableBracketAccessorNode( + VariableBracketAccessorNode& node) override; + void OnVisitIdentifierNode(IdentifierNode& node) override; + void OnVisitFunctionNode(FunctionNode& node) override; + void OnVisitEmptyNode(EmptyNode& node) override; + + private: + gd::String GenerateFreeFunctionCode( + const std::vector> & parameters, + const ExpressionMetadata& expressionMetadata); + gd::String GenerateObjectFunctionCode( + const gd::String& type, + const gd::String& objectName, + const std::vector> & parameters, + const ExpressionMetadata& expressionMetadata); + gd::String GenerateBehaviorFunctionCode( + const gd::String& type, + const gd::String& objectName, + const gd::String& behaviorName, + const std::vector> & parameters, + const ExpressionMetadata& expressionMetadata); + gd::String GenerateParametersCodes( + const std::vector> & parameters, + const ExpressionMetadata& expressionMetadata, + size_t initialParameterIndex); + gd::String GenerateDefaultValue(const gd::String & type); + + gd::String output; + EventsCodeGenerator& codeGenerator; + EventsCodeGenerationContext& context; +}; + +} // namespace gd + +#endif // GDCORE_ExpressionCodeGenerator_H diff --git a/Core/GDCore/Events/Parsers/ExpressionParser2.h b/Core/GDCore/Events/Parsers/ExpressionParser2.h index 3049fcf213..31af7769fb 100644 --- a/Core/GDCore/Events/Parsers/ExpressionParser2.h +++ b/Core/GDCore/Events/Parsers/ExpressionParser2.h @@ -96,7 +96,7 @@ class GD_CORE_API ExpressionParser2 { expressionStartPosition); } else if (IsAnyChar("(")) { SkipChar(); - leftHandSide = Expression(type); + leftHandSide = SubExpression(type); if (!IsAnyChar(")")) { leftHandSide->diagnostic = @@ -113,7 +113,7 @@ class GD_CORE_API ExpressionParser2 { leftHandSide = Identifier(type); } } else { - leftHandSide = gd::make_unique(); + leftHandSide = gd::make_unique(type); leftHandSide->diagnostic = RaiseSyntaxError( _("You must enter a text, number or a valid expression call.")); } @@ -148,6 +148,10 @@ class GD_CORE_API ExpressionParser2 { return leftHandSide; } + std::unique_ptr SubExpression(gd::String type) { + return std::move(gd::make_unique(Expression(type))); + }; + std::unique_ptr Identifier(gd::String type) { size_t identifierStartPosition = GetCurrentPosition(); gd::String name = ReadIdentifierName(); @@ -231,7 +235,7 @@ class GD_CORE_API ExpressionParser2 { platform, functionFullName); auto function = gd::make_unique( - functionFullName, Parameters(metadata.parameters, 0)); + type, Parameters(metadata.parameters, 0), metadata); function->diagnostic = ValidateFunction(type, metadata, 0, @@ -273,9 +277,11 @@ class GD_CORE_API ExpressionParser2 { platform, objectType, objectFunctionOrBehaviorName); auto function = gd::make_unique( - objectFunctionOrBehaviorName, + type, + objectName, // By convention, the first parameter is the object - Parameters(metadata.parameters, 1)); + Parameters(metadata.parameters, 1), + metadata); function->diagnostic = ValidateFunction(type, metadata, 1, @@ -286,7 +292,7 @@ class GD_CORE_API ExpressionParser2 { return std::move(function); } - auto node = gd::make_unique(); + auto node = gd::make_unique(type); node->diagnostic = RaiseSyntaxError( _("An opening parenthesis (for an object expression), or double colon " "(::) was expected (for a behavior expression).")); @@ -318,9 +324,12 @@ class GD_CORE_API ExpressionParser2 { platform, behaviorType, functionName); auto function = gd::make_unique( - functionName, + type, + objectName, + behaviorName, // By convention, the first 2 parameters are the object and behavior. - Parameters(metadata.parameters, 2)); + Parameters(metadata.parameters, 2), + metadata); function->diagnostic = ValidateFunction(type, metadata, 2, @@ -330,7 +339,7 @@ class GD_CORE_API ExpressionParser2 { return std::move(function); } else { - auto node = gd::make_unique(); + auto node = gd::make_unique(type); node->diagnostic = RaiseSyntaxError( _("An opening parenthesis was expected here to call a function.")); @@ -386,7 +395,7 @@ class GD_CORE_API ExpressionParser2 { } } - parameters.push_back(gd::make_unique()); + parameters.push_back(gd::make_unique("unknown")); parameters.back()->diagnostic = RaiseSyntaxError(_("The list of parameters is not terminated. Add a " "closing parenthesis to end the parameters.")); diff --git a/Core/GDCore/Events/Parsers/ExpressionParser2Node.h b/Core/GDCore/Events/Parsers/ExpressionParser2Node.h index e9c0f166ec..857ad449b1 100644 --- a/Core/GDCore/Events/Parsers/ExpressionParser2Node.h +++ b/Core/GDCore/Events/Parsers/ExpressionParser2Node.h @@ -77,6 +77,17 @@ struct ExpressionNode { std::unique_ptr diagnostic; }; +struct SubExpressionNode : public ExpressionNode { + SubExpressionNode(std::unique_ptr expression_) + : expression(std::move(expression_)){}; + virtual ~SubExpressionNode(){}; + virtual void Visit(ExpressionParser2NodeWorker &worker) { + worker.OnVisitSubExpressionNode(*this); + }; + + std::unique_ptr expression; +}; + /** * \brief An operator node. For example: "lhs + rhs". */ @@ -123,9 +134,9 @@ struct VariableAccessorOrVariableBracketAccessorNode : public ExpressionNode { /** * \brief A variable, potentially with accessor to its children. - * + * * Example: MyVariable or MyVariable.MyChildren - * + * * \see gd::VariableAccessorNode * \see gd::VariableBracketAccessorNode */ @@ -198,17 +209,41 @@ struct FunctionOrEmptyNode : public IdentifierOrFunctionOrEmptyNode { * \brief A function node. For example: "MyExtension::MyFunction(1, 2)". */ struct FunctionNode : public FunctionOrEmptyNode { - FunctionNode(const gd::String &functionFullName_, - std::vector> parameters_) - : functionFullName(functionFullName_), - parameters(std::move(parameters_)){}; + FunctionNode(const gd::String &type_, + std::vector> parameters_, + const ExpressionMetadata &expressionMetadata_) + : type(type_), + parameters(std::move(parameters_)), + expressionMetadata(expressionMetadata_){}; + FunctionNode(const gd::String &type_, + const gd::String &objectName_, + std::vector> parameters_, + const ExpressionMetadata &expressionMetadata_) + : type(type_), + objectName(objectName_), + parameters(std::move(parameters_)), + expressionMetadata(expressionMetadata_){}; + FunctionNode(const gd::String &type_, + const gd::String &objectName_, + const gd::String &behaviorName_, + std::vector> parameters_, + const ExpressionMetadata &expressionMetadata_) + : type(type_), + objectName(objectName_), + behaviorName(behaviorName_), + parameters(std::move(parameters_)), + expressionMetadata(expressionMetadata_){}; virtual ~FunctionNode(){}; virtual void Visit(ExpressionParser2NodeWorker &worker) { worker.OnVisitFunctionNode(*this); }; - gd::String functionFullName; + gd::String type; // This could be removed if the type ("string" or "number") + // was stored in ExpressionMetadata. + gd::String objectName; + gd::String behaviorName; std::vector> parameters; + const ExpressionMetadata &expressionMetadata; }; /** @@ -216,10 +251,13 @@ struct FunctionNode : public FunctionOrEmptyNode { * encountered and any other node could not make sense. */ struct EmptyNode : public FunctionOrEmptyNode { + EmptyNode(const gd::String &type_) : type(type_){}; virtual ~EmptyNode(){}; virtual void Visit(ExpressionParser2NodeWorker &worker) { worker.OnVisitEmptyNode(*this); }; + + gd::String type; }; } // namespace gd diff --git a/Core/GDCore/Events/Parsers/ExpressionParser2NodeWorker.h b/Core/GDCore/Events/Parsers/ExpressionParser2NodeWorker.h index 145850ac6a..7e4d3e31ce 100644 --- a/Core/GDCore/Events/Parsers/ExpressionParser2NodeWorker.h +++ b/Core/GDCore/Events/Parsers/ExpressionParser2NodeWorker.h @@ -8,6 +8,7 @@ namespace gd { class ExpressionNode; +class SubExpressionNode; class OperatorNode; class NumberNode; class TextNode; @@ -32,6 +33,7 @@ namespace gd { */ class GD_CORE_API ExpressionParser2NodeWorker { friend class ExpressionNode; + friend class SubExpressionNode; friend class OperatorNode; friend class NumberNode; friend class TextNode; @@ -48,6 +50,7 @@ class GD_CORE_API ExpressionParser2NodeWorker { virtual ~ExpressionParser2NodeWorker(); protected: + virtual void OnVisitSubExpressionNode(SubExpressionNode& node) = 0; virtual void OnVisitOperatorNode(OperatorNode& node) = 0; virtual void OnVisitNumberNode(NumberNode& node) = 0; virtual void OnVisitTextNode(TextNode& node) = 0; diff --git a/Core/GDCore/IDE/Events/ExpressionValidator.h b/Core/GDCore/IDE/Events/ExpressionValidator.h index 8a8de5855a..95f27b7f5d 100644 --- a/Core/GDCore/IDE/Events/ExpressionValidator.h +++ b/Core/GDCore/IDE/Events/ExpressionValidator.h @@ -41,6 +41,10 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker { }; protected: + void OnVisitSubExpressionNode(SubExpressionNode& node) override { + ReportAnyError(node); + node.expression->Visit(*this); + } void OnVisitOperatorNode(OperatorNode& node) override { node.leftHandSide->Visit(*this); ReportAnyError(node); diff --git a/Core/tests/DummyPlatform.cpp b/Core/tests/DummyPlatform.cpp new file mode 100644 index 0000000000..d5692082ca --- /dev/null +++ b/Core/tests/DummyPlatform.cpp @@ -0,0 +1,58 @@ +/* + * GDevelop Core + * Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "GDCore/Extensions/Platform.h" +#include "GDCore/Extensions/PlatformExtension.h" +#include "GDCore/IDE/Events/ExpressionValidator.h" +#include "GDCore/Project/Layout.h" +#include "GDCore/Project/Project.h" +#include "GDCore/Tools/Localization.h" +#include "catch.hpp" + +void SetupProjectWithDummyPlatform(gd::Project &project, + gd::Platform &platform) { + std::shared_ptr baseObjectExtension = + std::shared_ptr(new gd::PlatformExtension); + baseObjectExtension->SetExtensionInformation( + "BuiltinObject", "Base Object dummy extension", "", "", ""); + auto baseObject = baseObjectExtension->AddObject( + "", "Dummy Base Object", "Dummy Base Object", ""); + + std::shared_ptr extension = + std::shared_ptr(new gd::PlatformExtension); + extension->SetExtensionInformation( + "MyExtension", "My testing extension", "", "", ""); + extension->AddExpression("GetNumber", "Get me a number", "", "", "") + .SetFunctionName("getNumber"); + extension + ->AddExpression( + "GetNumberWith2Params", "Get me a number with 2 params", "", "", "") + .AddParameter("expression", "") + .AddParameter("string", "") + .SetFunctionName("getNumberWith2Params"); + extension + ->AddExpression("GetNumberWith3Params", + "Get me a number with 3 params, 1 optional", + "", + "", + "") + .AddParameter("expression", "") + .AddParameter("string", "") + .AddParameter("expression", "", "", true) + .SetFunctionName("getNumberWith3Params"); + auto &object = extension->AddObject( + "Sprite", "Dummy Sprite", "Dummy sprite object", ""); + object.AddExpression("GetObjectNumber", "Get number from object", "", "", "") + .AddParameter("object", _("Object"), "Sprite") + .SetFunctionName("getObjectNumber"); + // auto behavior = extension->AddBehavior("MyBehavior", "Dummy behavior", + // "MyBehavior", "", "", "","", + // gd::make_unique(), + // gd::make_unique()); + + platform.AddExtension(baseObjectExtension); + platform.AddExtension(extension); + project.AddPlatform(platform); +} \ No newline at end of file diff --git a/Core/tests/DummyPlatform.h b/Core/tests/DummyPlatform.h new file mode 100644 index 0000000000..cb6f31ba11 --- /dev/null +++ b/Core/tests/DummyPlatform.h @@ -0,0 +1,18 @@ +/* + * GDevelop Core + * Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ + +#ifndef DUMMY_PLATFORM +#define DUMMY_PLATFORM + +namespace gd { +class Project; +class Platform; +} // namespace gd + +void SetupProjectWithDummyPlatform(gd::Project &project, + gd::Platform &platform); + +#endif \ No newline at end of file diff --git a/Core/tests/ExpressionCodeGenerator.cpp b/Core/tests/ExpressionCodeGenerator.cpp new file mode 100644 index 0000000000..291fb9b075 --- /dev/null +++ b/Core/tests/ExpressionCodeGenerator.cpp @@ -0,0 +1,154 @@ +/* + * GDevelop Core + * Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "GDCore/Events/CodeGeneration/ExpressionCodeGenerator.h" +#include "DummyPlatform.h" +#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h" +#include "GDCore/Events/CodeGeneration/EventsCodeGenerator.h" +#include "GDCore/Events/Parsers/ExpressionParser2.h" +#include "GDCore/Extensions/Platform.h" +#include "GDCore/Project/Layout.h" +#include "GDCore/Project/Project.h" +#include "GDCore/Tools/VersionWrapper.h" +#include "catch.hpp" + +TEST_CASE("ExpressionCodeGenerator", "[common][events]") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &layout1 = project.InsertNewLayout("Layout1", 0); + layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0); + + gd::ExpressionParser2 parser(platform, project, layout1); + + unsigned int maxDepth = 0; + gd::EventsCodeGenerationContext context(&maxDepth); + gd::EventsCodeGenerator codeGenerator(project, layout1, platform); + + SECTION("Valid text generation") { + { + auto node = parser.ParseExpression("string", "\"hello world\""); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello world\""); + } + { + auto node = parser.ParseExpression("string", "\"hello\" + \"world\" "); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello\" + \"world\""); + } + } + + SECTION("Valid number generation") { + { + auto node = parser.ParseExpression("number", "12.45"); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == "12.45"); + } + { + auto node = parser.ParseExpression("number", "12.5 + -2. / (.3)"); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == "12.5 + -2. / (.3)"); + } + } + + SECTION("Invalid operators generation") { + // TODO: Should any error return directly 0 or ""? + { + auto node = parser.ParseExpression("number", "12.45 +"); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == "12.45 + 0"); + } + { + auto node = parser.ParseExpression("number", "12.45 * *"); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == "12.45 * 0 * 0"); + } + } + + SECTION("Valid function calls") { + { + auto node = + parser.ParseExpression("number", " 1 / MyExtension::GetNumber()"); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == "1 / getNumber()"); + } + { + auto node = parser.ParseExpression( + "number", "MyExtension::GetNumberWith2Params(12, \"hello world\")"); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == + "getNumberWith2Params(12, \"hello world\")"); + } + { + auto node = + parser.ParseExpression("number", + "MyExtension::GetNumberWith2Params(" + "MyExtension::GetNumber(), \"hello world\")"); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == + "getNumberWith2Params(getNumber(), \"hello world\")"); + } + } + SECTION("Invalid function calls") { + { + auto node = parser.ParseExpression( + "number", + "MyExtension::GetNumberWith2Params(MyExtension::GetNumber())"); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == + "getNumberWith2Params(getNumber(), /* Error during generation, " + "parameter not existing in the nodes */ \"\")"); + } + { + auto node = parser.ParseExpression("number", "MyExtension::Idontexist()"); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == + "/* Error during generation, function not found */ 0"); + } + { + auto node = parser.ParseExpression( + "number", "MyExtension::GetNumberWith2Params(1, \"2\", getNumber())"); + gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator, + context); + + node->Visit(expressionCodeGenerator); + REQUIRE(expressionCodeGenerator.GetOutput() == + "getNumberWith2Params(1, \"2\")"); + } + } +} diff --git a/Core/tests/ExpressionParser2.cpp b/Core/tests/ExpressionParser2.cpp index ca1169c7b6..afd6a6f161 100644 --- a/Core/tests/ExpressionParser2.cpp +++ b/Core/tests/ExpressionParser2.cpp @@ -4,6 +4,7 @@ * reserved. This project is released under the MIT License. */ #include "GDCore/Events/Parsers/ExpressionParser2.h" +#include "DummyPlatform.h" #include "GDCore/Extensions/Platform.h" #include "GDCore/Extensions/PlatformExtension.h" #include "GDCore/IDE/Events/ExpressionValidator.h" @@ -11,52 +12,6 @@ #include "GDCore/Project/Project.h" #include "catch.hpp" -namespace { - -void SetupProjectWithDummyPlatform(gd::Project &project, - gd::Platform &platform) { - std::shared_ptr baseObjectExtension = - std::shared_ptr(new gd::PlatformExtension); - baseObjectExtension->SetExtensionInformation( - "BuiltinObject", "Base Object dummy extension", "", "", ""); - auto baseObject = baseObjectExtension->AddObject( - "", "Dummy Base Object", "Dummy Base Object", ""); - - std::shared_ptr extension = - std::shared_ptr(new gd::PlatformExtension); - extension->SetExtensionInformation( - "MyExtension", "My testing extension", "", "", ""); - extension->AddExpression("GetNumber", "Get me a number", "", "", ""); - extension - ->AddExpression( - "GetNumberWith2Params", "Get me a number with 2 params", "", "", "") - .AddParameter("expression", "") - .AddParameter("string", ""); - extension - ->AddExpression("GetNumberWith3Params", - "Get me a number with 3 params, 1 optional", - "", - "", - "") - .AddParameter("expression", "") - .AddParameter("string", "") - .AddParameter("expression", "", "", true); - auto &object = extension->AddObject( - "Sprite", "Dummy Sprite", "Dummy sprite object", ""); - object.AddExpression("GetObjectNumber", "Get number from object", "", "", "") - .AddParameter("object", _("Object"), "Sprite") - .SetFunctionName("getObjectNumber"); - // auto behavior = extension->AddBehavior("MyBehavior", "Dummy behavior", - // "MyBehavior", "", "", "","", - // gd::make_unique(), - // gd::make_unique()); - - platform.AddExtension(baseObjectExtension); - platform.AddExtension(extension); - project.AddPlatform(platform); -} -} // namespace - TEST_CASE("ExpressionParser2", "[common][events]") { gd::Project project; gd::Platform platform; @@ -187,6 +142,21 @@ TEST_CASE("ExpressionParser2", "[common][events]") { } } + SECTION("Invalid parenthesis") { + { + auto node = parser.ParseExpression("string", "((\"hello\""); + REQUIRE(node != nullptr); + + gd::ExpressionValidator validator; + node->Visit(validator); + REQUIRE(validator.GetErrors().size() == 2); + REQUIRE(validator.GetErrors()[0]->GetMessage() == + "Missing a closing parenthesis. Add a closing parenthesis for each opening parenthesis."); + REQUIRE(validator.GetErrors()[1]->GetMessage() == + "Missing a closing parenthesis. Add a closing parenthesis for each opening parenthesis."); + } + } + SECTION("Invalid text operators") { { auto node = parser.ParseExpression("string", "\"Hello \" - \"World\""); @@ -233,6 +203,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") { auto &numberNode = dynamic_cast(*node); REQUIRE(numberNode.number == ".14159"); } + { + auto node = parser.ParseExpression("number", "-123.2"); + REQUIRE(node != nullptr); + auto &numberNode = dynamic_cast(*node); + REQUIRE(numberNode.number == "-123.2"); + } { auto node = parser.ParseExpression("number", "3."); REQUIRE(node != nullptr); @@ -403,7 +379,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") { auto node = parser.ParseExpression("number", "MyExtension::GetNumber()"); REQUIRE(node != nullptr); auto &functionNode = dynamic_cast(*node); - REQUIRE(functionNode.functionFullName == "MyExtension::GetNumber"); gd::ExpressionValidator validator; node->Visit(validator); @@ -414,8 +389,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") { "number", "MyExtension::GetNumberWith2Params(12, \"hello world\")"); REQUIRE(node != nullptr); auto &functionNode = dynamic_cast(*node); - REQUIRE(functionNode.functionFullName == - "MyExtension::GetNumberWith2Params"); gd::ExpressionValidator validator; node->Visit(validator); @@ -426,8 +399,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") { "number", "MyExtension::GetNumberWith3Params(12, \"hello world\")"); REQUIRE(node != nullptr); auto &functionNode = dynamic_cast(*node); - REQUIRE(functionNode.functionFullName == - "MyExtension::GetNumberWith3Params"); gd::ExpressionValidator validator; node->Visit(validator); @@ -439,8 +410,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") { "MyExtension::GetNumberWith3Params(12, \"hello world\", 34)"); REQUIRE(node != nullptr); auto &functionNode = dynamic_cast(*node); - REQUIRE(functionNode.functionFullName == - "MyExtension::GetNumberWith3Params"); gd::ExpressionValidator validator; node->Visit(validator); @@ -451,7 +420,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") { parser.ParseExpression("number", "MySpriteObject.GetObjectNumber()"); REQUIRE(node != nullptr); auto &functionNode = dynamic_cast(*node); - REQUIRE(functionNode.functionFullName == "GetObjectNumber"); gd::ExpressionValidator validator; node->Visit(validator); @@ -547,31 +515,37 @@ TEST_CASE("ExpressionParser2", "[common][events]") { auto &variableNode = dynamic_cast(*node); REQUIRE(variableNode.name == "myVariable"); REQUIRE(variableNode.child != nullptr); - auto &childNode = dynamic_cast(*variableNode.child); + auto &childNode = + dynamic_cast(*variableNode.child); REQUIRE(childNode.name == "myChild"); } { - auto node = parser.ParseExpression("variable", "myVariable[ \"My named children\" ]"); + auto node = parser.ParseExpression( + "variable", "myVariable[ \"My named children\" ]"); REQUIRE(node != nullptr); auto &variableNode = dynamic_cast(*node); REQUIRE(variableNode.name == "myVariable"); REQUIRE(variableNode.child != nullptr); - auto &childNode = dynamic_cast(*variableNode.child); + auto &childNode = + dynamic_cast(*variableNode.child); REQUIRE(childNode.expression != nullptr); auto &textNode = dynamic_cast(*childNode.expression); REQUIRE(textNode.text == "My named children"); } { - auto node = parser.ParseExpression("variable", "myVariable[ \"My named children\" ].grandChild"); + auto node = parser.ParseExpression( + "variable", "myVariable[ \"My named children\" ].grandChild"); REQUIRE(node != nullptr); auto &variableNode = dynamic_cast(*node); REQUIRE(variableNode.name == "myVariable"); REQUIRE(variableNode.child != nullptr); - auto &childNode = dynamic_cast(*variableNode.child); + auto &childNode = + dynamic_cast(*variableNode.child); REQUIRE(childNode.child != nullptr); - auto &grandChildNode = dynamic_cast(*childNode.child); + auto &grandChildNode = + dynamic_cast(*childNode.child); REQUIRE(grandChildNode.name == "grandChild"); } }