mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-22 01:55:25 -04:00
71e3b65134
* This object displays a field where the player can type a text. * The cursor can be moved and the text can be selected and entered like any other input field according to the platform. For example, you can use all the usual keyboard shortcuts or emoji pickers on desktop. On mobile, all the usual gestures are supported, emoji pickers, text selection, clipboard... * You can choose between different input field types: text, text area, email, password, phone number... This allows the keyboards to be adapted on mobile devices. The password field will also hide what is entered by the user. * Because the object is way more convenient to use (and actually works on mobile devices) than the Text Entry object, it is recommended you use it almost always instead of the Text Entry object. * This object has 2 limitations, because it's using the native capabilities of the platform to display a "native" input field: the Z order can't be changed (the object is *always* displayed on top of the game) and no effects can be displayed. Because it's handled by the operating system, it may also slightly differ on each platform. * It's recommended you avoid displaying the object outside of a menu or a static UI, as it may not always display exactly at a position synchronised with the rest of the rendering of the game, especially if used in a fast paced or moving scene.
505 lines
20 KiB
C++
505 lines
20 KiB
C++
/*
|
|
* 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);
|
|
layout1.InsertNewObject(
|
|
project, "MyExtension::Sprite", "MyOtherSpriteObject", 1);
|
|
layout1.InsertNewObject(project,
|
|
"MyExtension::FakeObjectWithUnsupportedCapability",
|
|
"MyFakeObjectWithUnsupportedCapability",
|
|
2);
|
|
auto &group = layout1.GetObjectGroups().InsertNew("AllObjects");
|
|
group.AddObject("MySpriteObject");
|
|
group.AddObject("MyOtherSpriteObject");
|
|
group.AddObject("MyFakeObjectWithUnsupportedCapability");
|
|
|
|
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);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello world\"");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression("string", "\"hello\" + \"world\" ");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello\" + \"world\"");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression(
|
|
"string", "\"{\\\"hello\\\": \\\"world \\\\\\\" \\\"}\"");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"\"{\\\"hello\\\": \\\"world \\\\\\\" \\\"}\"");
|
|
}
|
|
}
|
|
|
|
SECTION("Valid number generation") {
|
|
{
|
|
auto node = parser.ParseExpression("number", "12.45");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() == "12.45");
|
|
}
|
|
}
|
|
|
|
SECTION("Invalid operators generation") {
|
|
// TODO: Should any error return directly 0 or ""?
|
|
{
|
|
auto node = parser.ParseExpression("number", "12.45 +");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() == "12.45 + 0");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression("number", "12.45 * *");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() == "12.45 * 0");
|
|
}
|
|
}
|
|
|
|
SECTION("Valid unary operator generation") {
|
|
{
|
|
auto node = parser.ParseExpression("number", "-12.45");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() == "-(12.45)");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression("number", "12.5 + -2. / (.3)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() == "12.5 + -(2.) / (0.3)");
|
|
}
|
|
}
|
|
|
|
SECTION("Valid function calls") {
|
|
{
|
|
auto node =
|
|
parser.ParseExpression("number", " 1 / MyExtension::GetNumber()");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() == "1 / getNumber()");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression(
|
|
"number", "MyExtension::GetNumberWith2Params(12, \"hello world\")");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
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);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"getNumberWith2Params(getNumber(), \"hello world\")");
|
|
}
|
|
{
|
|
auto node =
|
|
parser.ParseExpression("number", "MySpriteObject.GetObjectNumber()");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"MySpriteObject.getObjectNumber() ?? 0");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression(
|
|
"string",
|
|
"MySpriteObject.GetObjectStringWith1Param(MyExtension::GetNumber())");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"MySpriteObject.getObjectStringWith1Param(getNumber()) ?? \"\"");
|
|
}
|
|
}
|
|
SECTION("Valid function calls with optional arguments") {
|
|
{
|
|
auto node =
|
|
parser.ParseExpression("number", "MyExtension::MouseX(\"layer1\",)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"getMouseX(\"\", \"layer1\", 0)");
|
|
// (first argument is the currentScene)
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression("number",
|
|
"MyExtension::MouseX(\"layer1\",2+2)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"getMouseX(\"\", \"layer1\", 2 + 2)");
|
|
// (first argument is the currentScene)
|
|
}
|
|
}
|
|
SECTION(
|
|
"Valid function calls (deprecated way of specifying optional "
|
|
"arguments)") {
|
|
{
|
|
auto node = parser.ParseExpression("number", "MyExtension::MouseX(,)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"getMouseX(\"\", \"\", 0)");
|
|
// (first argument is the currentScene)
|
|
}
|
|
}
|
|
SECTION("Invalid function calls") {
|
|
{
|
|
auto node =
|
|
parser.ParseExpression("string",
|
|
"MySpriteObject.GetObjectStringWith3Param("
|
|
"MySpriteObject.GetObjectNumber() / 2.3, "
|
|
"MySpriteObject.GetObjectStringWith1Param("
|
|
"MyExtension::GetNumber()), test)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"MySpriteObject.getObjectStringWith3Param(MySpriteObject."
|
|
"getObjectNumber() ?? 0 / 2.3, "
|
|
"MySpriteObject.getObjectStringWith1Param(getNumber()) ?? \"\", "
|
|
"/* Error during generation, unrecognized identifier type: "
|
|
"unknown with value test */ \"test\") ?? \"\"");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression(
|
|
"number",
|
|
"MyExtension::GetNumberWith2Params(MyExtension::GetNumber())");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"getNumberWith2Params(getNumber(), /* Error during generation, "
|
|
"parameter not existing in the nodes */ \"\")");
|
|
}
|
|
{
|
|
// Using GenerateExpressionCode, the default value of 0 should be returned
|
|
// as expression is invalid.
|
|
REQUIRE(
|
|
gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
|
codeGenerator,
|
|
context,
|
|
"number",
|
|
"MyExtension::GetNumberWith2Params(MyExtension::GetNumber())") ==
|
|
"0");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression("number", "MyExtension::Idontexist()");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"/* Error during generation, function not found: "
|
|
"MyExtension::Idontexist */ 0");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression("number",
|
|
"MyExtension::GetNumberWith2Params(1, "
|
|
"\"2\", MyExtension::GetNumber())");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"getNumberWith2Params(1, \"2\")");
|
|
}
|
|
}
|
|
SECTION("Invalid function calls (capabilities)") {
|
|
{
|
|
// Capability is supported, so the expression is valid.
|
|
auto node = parser.ParseExpression(
|
|
"string",
|
|
"MySpriteObject.GetSomethingRequiringEffectCapability(123)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(
|
|
expressionCodeGenerator.GetOutput() ==
|
|
"MySpriteObject.getSomethingRequiringEffectCapability(123) ?? \"\"");
|
|
}
|
|
{
|
|
// Capability is not supported, so the expression is not even valid.
|
|
auto node =
|
|
parser.ParseExpression("string",
|
|
"MyFakeObjectWithUnsupportedCapability."
|
|
"GetSomethingRequiringEffectCapability(123)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() == "\"\"");
|
|
}
|
|
{
|
|
// We use a group, capability is supported only by one object of the
|
|
// group. The expression itself is valid, but code generation should skip
|
|
// the objects with unsupported capability.
|
|
auto node = parser.ParseExpression(
|
|
"string", "AllObjects.GetSomethingRequiringEffectCapability(123)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(
|
|
expressionCodeGenerator.GetOutput() ==
|
|
"MyOtherSpriteObject.getSomethingRequiringEffectCapability(123) ?? "
|
|
"MySpriteObject.getSomethingRequiringEffectCapability(123) ?? \"\"");
|
|
}
|
|
}
|
|
SECTION("Invalid variables") {
|
|
{
|
|
// Test an empty expression
|
|
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
|
codeGenerator, context, "scenevar", "") == "fakeBadVariable");
|
|
}
|
|
{
|
|
// Test a unary operator
|
|
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
|
codeGenerator, context, "objectvar", "-") ==
|
|
"fakeBadVariable");
|
|
}
|
|
{
|
|
// Test an operator
|
|
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
|
codeGenerator, context, "globalvar", "/") ==
|
|
"fakeBadVariable");
|
|
}
|
|
}
|
|
SECTION("Invalid variables, using operators") {
|
|
{
|
|
// Test a unary operator
|
|
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
|
codeGenerator, context, "objectvar", "-(var1)") ==
|
|
"fakeBadVariable");
|
|
}
|
|
{
|
|
// Test an operator
|
|
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
|
codeGenerator, context, "globalvar", "var1+var2") ==
|
|
"fakeBadVariable");
|
|
}
|
|
{
|
|
// Test multiple operators
|
|
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
|
codeGenerator, context, "globalvar", "var1/var2/var3/var4") ==
|
|
"fakeBadVariable");
|
|
}
|
|
}
|
|
SECTION("Valid function calls with variables") {
|
|
SECTION("Simple access") {
|
|
{
|
|
auto node = parser.ParseExpression(
|
|
"number", "MyExtension::GetVariableAsNumber(myVariable)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"returnVariable(getLayoutVariable(myVariable))");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression(
|
|
"number",
|
|
"MyExtension::GetGlobalVariableAsNumber(myGlobalVariable)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"returnVariable(getProjectVariable(myGlobalVariable))");
|
|
}
|
|
}
|
|
SECTION("Child access") {
|
|
{
|
|
auto node = parser.ParseExpression(
|
|
"number",
|
|
"MyExtension::GetVariableAsNumber(myVariable.child1.child2)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"returnVariable(getLayoutVariable(myVariable).getChild("
|
|
"\"child1\").getChild(\"child2\"))");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression(
|
|
"number",
|
|
"MyExtension::GetVariableAsNumber(myVariable[ \"hello\" + "
|
|
"\"world\" ].child2)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"returnVariable(getLayoutVariable(myVariable).getChild("
|
|
"\"hello\" + \"world\").getChild(\"child2\"))");
|
|
}
|
|
{
|
|
auto node = parser.ParseExpression(
|
|
"number",
|
|
"MyExtension::GetVariableAsNumber(myVariable[ \"hello\" + "
|
|
"MySpriteObject.GetObjectStringWith1Param(MyOtherSpriteObject."
|
|
"GetObjectVariableAsNumber(mySecondVariable)) ].child2)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(
|
|
expressionCodeGenerator.GetOutput() ==
|
|
"returnVariable(getLayoutVariable(myVariable).getChild(\"hello\" + "
|
|
"MySpriteObject.getObjectStringWith1Param(MyOtherSpriteObject."
|
|
"returnVariable"
|
|
"(getVariableForObject(MyOtherSpriteObject, mySecondVariable)) ?? "
|
|
"0) ?? \"\").getChild(\"child2\"))");
|
|
}
|
|
}
|
|
}
|
|
SECTION("Objects") {
|
|
gd::String output = gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
|
codeGenerator,
|
|
context,
|
|
"string",
|
|
"MySpriteObject.GetObjectStringWith2ObjectParam(Object1, Object2)");
|
|
REQUIRE(output ==
|
|
"MySpriteObject.getObjectStringWith2ObjectParam(fakeObjectListOf_"
|
|
"Object1, fakeObjectListOf_Object2) ?? \"\"");
|
|
}
|
|
SECTION("Mixed test (1)") {
|
|
{
|
|
auto node = parser.ParseExpression("number", "-+-MyExtension::MouseX(,)");
|
|
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
|
|
context);
|
|
|
|
REQUIRE(node);
|
|
node->Visit(expressionCodeGenerator);
|
|
REQUIRE(expressionCodeGenerator.GetOutput() ==
|
|
"-(+(-(getMouseX(\"\", \"\", 0))))");
|
|
// (first argument is the currentScene)
|
|
}
|
|
}
|
|
SECTION("Mixed test (2)") {
|
|
gd::String output = gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
|
codeGenerator,
|
|
context,
|
|
"number",
|
|
"MyExtension::GetVariableAsNumber(myVariable[ \"hello\" + "
|
|
"MySpriteObject.GetObjectStringWith1Param(MyOtherSpriteObject."
|
|
"GetObjectVariableAsNumber(mySecondVariable)) ].child2)");
|
|
REQUIRE(output ==
|
|
"returnVariable(getLayoutVariable(myVariable).getChild(\"hello\" + "
|
|
"MySpriteObject.getObjectStringWith1Param(MyOtherSpriteObject."
|
|
"returnVariable"
|
|
"(getVariableForObject(MyOtherSpriteObject, mySecondVariable)) ?? "
|
|
"0) ?? \"\").getChild(\"child2\"))");
|
|
}
|
|
SECTION("Mixed test (3)") {
|
|
gd::String output = gd::ExpressionCodeGenerator::GenerateExpressionCode(
|
|
codeGenerator,
|
|
context,
|
|
"globalvar",
|
|
"myVariable[ \"My children\" + "
|
|
"MyExtension::ToString(+-MyExtension::GetNumberWith3Params(12, \"hello "
|
|
"world\")) ].grandChild");
|
|
REQUIRE(output ==
|
|
"getProjectVariable(myVariable).getChild(\"My children\" + "
|
|
"toString(+(-(getNumberWith3Params(12, \"hello world\", "
|
|
"0))))).getChild(\"grandChild\")");
|
|
}
|
|
}
|