Compare commits

...

74 Commits

Author SHA1 Message Date
Clément Pasteau
a7ef02f1c1 Bump to 5.2.174 (#5721) 2023-10-05 12:35:38 +02:00
github-actions[bot]
2cc7506ad7 Update translations [skip ci] (#5714)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-10-05 12:28:00 +02:00
D8H
608b460da7 Fix particle emitter spray cone angle being properly updated (#5720) 2023-10-05 12:19:39 +02:00
Clément Pasteau
6b082731c2 Fix dom errors because using a component inside a listItemText (#5717)
Do not show in changelog
2023-10-04 16:15:12 +02:00
Clément Pasteau
e040dd5f28 Deprecate "Always" condition (#5716)
* It was considered misleading as using it or not didn't change the condition's result
2023-10-04 15:07:45 +02:00
Clément Pasteau
ae64cfb3bb Fix crash when using Tilemaps on the web-app (#5715) 2023-10-04 14:55:29 +02:00
Clément Pasteau
02b96eb298 Fix loading video resources in Runtime (#5713) 2023-10-04 10:55:57 +02:00
Clément Pasteau
6f59a3ba03 Improve autocomplete options height on mobile (#5709) 2023-10-03 16:27:12 +02:00
github-actions[bot]
6180016b51 Update translations [skip ci] (#5706)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-10-03 16:10:54 +02:00
Clément Pasteau
b15e72410e Fix DOM and Console errors (#5708)
Do not show in changelog
2023-10-03 15:56:34 +02:00
Clément Pasteau
d6fbb0e78b Show a link to try premium game templates (#5707) 2023-10-03 13:51:04 +02:00
Clément Pasteau
8eb9136847 Fix wrong jsdoc typing (#5705)
Do not show in changelog
2023-10-02 16:28:59 +02:00
Clément Pasteau
46c02ad9b7 Improvements indicator wording (#5702)
Do not show in changelog
2023-10-02 11:50:42 +02:00
github-actions[bot]
9846a9b45e Update translations [skip ci] (#5701)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-10-02 11:41:41 +02:00
AlexandreS
3d86820581 Fix: Do not highlight instance on hover if moving an instance around (#5700) 2023-10-02 10:50:09 +02:00
D8H
b636eeb859 Fix a regression on the negation of compare number/string conditions (#5698) 2023-09-29 20:04:33 +02:00
github-actions[bot]
189bcc4ceb Update translations [skip ci] (#5697)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-09-29 16:09:38 +02:00
Clément Pasteau
3fe031063c Bump to 5.2.173 (#5696) 2023-09-29 15:30:51 +02:00
github-actions[bot]
d10712d584 Update translations [skip ci] (#5691)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-09-29 14:48:29 +02:00
Clément Pasteau
1721fb6211 Cloud projects can now be shared with collaborators (#5687)
* Ability to add 1 guest per project or multiple collaborators with a startup subscription
* Projects shared appear on the projects list of all collaborators and all are able to open it
* A warning appears if you are trying to save a project that someone else modified, to avoid losing changes, so ensure you bring changes one at a time!
2023-09-29 14:42:18 +02:00
D8H
e31ea07902 Fix a 0-size frame buffer exception (#5695)
* Don't show in changelog
2023-09-29 11:05:42 +02:00
Arthur Pacaud (arthuro555)
85929b6e76 Make it possible to render a GDevelop scene in VR (#5681)
Only show in developer changelog
2023-09-27 23:15:38 +02:00
Clément Pasteau
0662607409 Don't show premium features if GDevelop is not the seller (#5692)
Do not show in changelog
2023-09-27 17:51:26 +02:00
Arthur Pacaud (arthuro555)
52f10d73ab Make the Debugger more resilient to errors (#5680) 2023-09-27 16:47:01 +02:00
Florian Rival
d4cb4fafd3 Rework the variables editor so that it's more performant and comfortable to use (#5690)
- The editor is now way faster to open when lots of variables are present. It's also way faster when editing and when lots of variables are present.
- Variable names that have forbidden characters are automatically fixed, not breaking your workflow.
- Deletion of multiple variables in an array was fixed. 
- Also fix adding a new variable sometimes overriding a variable inherited from the object
- Allow editing text variables in a separate window (more comfortable and faster)
2023-09-27 16:30:46 +02:00
D8H
9f4b12ce8a Add missing text parameter types for event-based extensions (#5689)
* Replace "effect parameter" with "effect property" in the UI and documentation.
* Inline property fields for effect name and property.
2023-09-27 11:05:19 +02:00
github-actions[bot]
b5b7edafb1 Update translations [skip ci] (#5640)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-09-27 10:06:31 +02:00
D8H
1335819ea7 Add an expression, a condition and an action for objects center on Z axis (#5682) 2023-09-25 10:12:03 +02:00
Florian Rival
aa7ae758f7 Avoid some useless rendering of toolbars (#5679)
Don't show in changelog
2023-09-23 15:35:22 +02:00
Tristan Rhodes
7da88662fb Fix action sentence for BBText objects alignment (#5678) 2023-09-23 13:37:05 +02:00
Florian Rival
dbc1385787 Fix error when loading resources in editor (#5675)
Don't show in changelog
2023-09-23 13:35:00 +02:00
Florian Rival
8944df1831 Add support for writing parameter names directly in expressions (#5676) 2023-09-22 22:41:13 +02:00
Florian Rival
a0925c79c3 Add a new simplified way to use variables in expressions, and automate renaming of variables in events (#5580)
* You can now simply write the name of the scene or global variable in an expression to use it: `1 + MyVariable` (instead of `1 + Variable(MyVariable)`).
* Objects can also have their variables accessed like this: `MyObject.MyVariable` (instead of `MyObject.Variable(MyVariable)`.
* This also works for properties inside functions of behaviors or custom objects. For example, you can write `Speed` instead of `Object.Behavior::PropertySpeed()`.
* This syntax will also handle all types of variables without the need to write ToString. For example, you can now write "Score: " + CoinsEarned instead of "Score: " + ToString(Variable(CoinsEarned)).
* This syntax will only work (and autocompletions will be shown) if you add the variable in the variables editor of the scene, the project or in the variables of the object. It's a good practice to always declare your variables here and give them a default value - do it to benefit from this new simplified syntax, which will make your formulas and expressions much more readable.
* When you rename a variable in an editor, it will now rename the variables everywhere in the events of the project. This makes it much easier to change the name of a variable if you find a better one. Note that this works for "rootæ variables, but not variables inside structures or arrays.
2023-09-21 18:56:12 +02:00
D8H
73642092bb Upgrade games 2D rendering engine (PixiJS) to version 7.3.0 (#5633)
* This opens the path to multiple performance improvements (faster WebGL rendering, and then WebGPU rendering) as well as optimised resources loading in the future.
2023-09-21 17:14:05 +02:00
D8H
dfe84fbe2b Fix an issue in the frame rate synchronization of the Physics behavior (#5649)
* It should avoid jittery cameras that was happening from time to time.
2023-09-19 15:11:57 +02:00
Aurélien Vivet
cd896c399d Fix typo (#5668)
- Fix the width word used in the getHeight() method for the objects.
- Fix the purial of the window in an action.
2023-09-19 13:32:34 +01:00
Clément Pasteau
d8126ead34 Prevent triggering layout change when the virtual keyboard appears. (#5666)
* It fixes a bug on mobile where starting typing would show the virtual keyboard, triggering a resize of the window, which would change the layout and make the input disappear.
2023-09-18 18:15:22 +02:00
AlexandreS
bdbbe1f409 Fix Health bar and Joystick guided lessons in web app (#5665) 2023-09-18 10:38:36 +02:00
D8H
3a241d03f1 Allow sprite animations to play backward with a negative speed scale (#5662) 2023-09-18 09:24:51 +02:00
Florian Rival
e4cf92e949 Improve documentation of expressions by showing parameter in a human readable way 2023-09-16 16:35:24 +02:00
Florian Rival
0dc513317c Revert "Update createObjectsFrom and createObjectsFromExternalLayout to return a list of objects that it creates. (#5658)" (#5660)
This reverts commit 3e85242165.
2023-09-15 14:46:33 +02:00
Dawid Fatyga
3e85242165 Update createObjectsFrom and createObjectsFromExternalLayout to return a list of objects that it creates. (#5658)
Only show in developer changelog
2023-09-15 14:45:25 +02:00
D8H
ca9fe51d99 Fix scaling actions icons (#5647) 2023-09-13 12:54:22 +02:00
Florian Rival
2e9a5ee287 Fix ExpressionParser2 tests (#5654)
Don't show in changelog
2023-09-13 12:54:09 +02:00
Clément Pasteau
b57e944da5 Fix opening a custom object after having opened a pack in the store (#5651) 2023-09-11 14:30:36 +02:00
Florian Rival
06cdeb763d Fix user questionnaire sometimes shown even when already filled (#5648) 2023-09-08 14:15:00 +02:00
Clément Pasteau
5809a02a26 Add more events about premium game templates opening (#5644)
Do not show in changelog
2023-09-07 19:24:23 +02:00
Florian Rival
3a2878c737 Add more restricted characters in internationalized names (#5643)
Only show in developer changelog
2023-09-07 14:54:56 +02:00
Clément Pasteau
2cbfb806e2 Fix speed of adding an asset (#5642)
Do not show in changelog
2023-09-07 14:36:26 +02:00
AlexandreS
18211d197a Remember collapsed state of object behaviors configuration panels (#5641) 2023-09-07 14:30:23 +02:00
Clément Pasteau
128657c876 Prevent throwing when leaderboards cannot be fetched (#5639)
Do not show in changelog
2023-09-07 11:26:42 +02:00
github-actions[bot]
930d4d394f Update translations [skip ci] (#5629)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-09-07 09:53:55 +02:00
D8H
cc460657e5 Upgrade Storybook to 7.4.0 (#5625)
Only show in developer changelog 

Co-authored-by: Clément Pasteau <4895034+ClementPasteau@users.noreply.github.com>
Co-authored-by: Florian Rival <Florian.Rival@gmail.com>
2023-09-06 20:04:01 +02:00
AlexandreS
d9f2cb51a3 Add view of team and its groups (classrooms) for education plan holders (#5603) 2023-09-06 17:00:19 +02:00
AlexandreS
d736afe4a0 Fix events search crashing the events sheet when a parameter contains more than 5 spaces (#5628) 2023-09-06 16:53:35 +02:00
Clément Pasteau
10825c0a8c Allow using Premium game templates, high quality games (#5611)
* Those templates are ready-made games, which can be used for as many games as you want
* They directly work on mobile, web and desktop for a seamless experience
* They contain everything for a full game, start menu, options, leaderboards, and even character selection
* They can easily be modified to integrate your assets, or your custom logic to make this game yours
2023-09-06 15:26:45 +02:00
Clément Pasteau
a75a8e1bfe Add steamworks extension (#5440) 2023-09-06 09:31:27 +02:00
D8H
9a02905fb6 Refactor resources loading in Runtime (#5632)
* Only for dev
2023-09-06 00:12:07 +02:00
AlexandreS
414292812f Fix context menu actions disappearing when opening a project and Resources tab is restored (#5630) 2023-09-05 16:29:28 +02:00
AlexandreS
4d9035ad07 Display the navigation bar of the homepage at the bottom on mobile (#5627) 2023-09-05 14:10:01 +02:00
github-actions[bot]
bcf0184fdd Update translations [skip ci] (#5622)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-09-05 10:28:24 +02:00
Florian Rival
817f2cf758 Display a message if a subscription is valid but will expire in the future (#5626) 2023-09-04 12:35:00 +02:00
Aurélien Vivet
3c5e10f5c3 Fix typo in theme template (theme.json) (#5623)
Thanks to @Entr0py404 for finding this!
Only show in developer changelog

Co-authored-by: Tristan Rhodes <tristan.rhodes@gmail.com>
2023-09-02 18:38:34 +02:00
Clément Pasteau
7d19b811e4 Fix creating a local project in the right folder (#5621) 2023-09-01 17:46:47 +02:00
Clément Pasteau
17b3579e5b Bump to 5.2.172 (#5620) 2023-09-01 14:32:32 +02:00
github-actions[bot]
afa8d4f7f6 Update translations [skip ci] (#5614)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-09-01 14:30:07 +02:00
Florian Rival
b262cb4c3e Fix link in disabled events still processed internally (#5619)
* This could generate infinite loop crashs even when a link is included as
a sub event of a disabled event.
2023-09-01 13:58:51 +02:00
Clément Pasteau
1c69302c02 Fix using correct parameter for size capability behavior (#5618)
Do not show in changelog
2023-09-01 09:57:51 +02:00
AlexandreS
5ba36916c6 Display a nicer refresh icon in the UI and fields (#5617) 2023-09-01 09:15:21 +02:00
D8H
7f3e2b210a Fix missing effect tab on some objects (#5616) 2023-08-31 20:41:06 +02:00
D8H
f83df15817 Fix a performance regression on scenes with a lot of sprites (#5615) 2023-08-31 18:19:50 +02:00
Gleb Volkov
40639f5578 Add clearCanvas flag to RuntimeScene for multi-scene rendering support (#5525)
* Add `clearCanvas` field to `RuntimeScene` class and pass it to `PIXI.Renderer.render` as `clear` option to support multi-scene rendering.

* Show in developer changelog
2023-08-31 10:10:15 +02:00
Florian Rival
f07e3bfe19 Reduce "layout shifts" in Events Sheet when adding events or modifying them (#5612)
* Browsing the events sheet and editing events should be a bit more comfortable now.
2023-08-31 10:09:47 +02:00
D8H
ef9a82ee33 Fix scene editor window rectangle color (#5613) 2023-08-30 21:44:01 +02:00
731 changed files with 44058 additions and 41527 deletions

View File

@@ -62,9 +62,10 @@ jobs:
# Build GDevelop IDE (seems like we need to allow Node.js to use more space than usual)
# Note: Code signing is done using CSC_LINK (see https://www.electron.build/code-signing).
# To test signing the code in the CI, add "export CSC_FOR_PULL_REQUEST=true && " before the command.
- run:
name: Build GDevelop IDE
command: export NODE_OPTIONS="--max-old-space-size=7168" && cd newIDE/electron-app && CI=false npm run build -- --mac --publish=never
command: export CSC_FOR_PULL_REQUEST=true && export NODE_OPTIONS="--max-old-space-size=7168" && cd newIDE/electron-app && CI=false npm run build -- --mac --publish=never
- run:
name: Clean dist folder to keep only installers/binaries.

View File

@@ -26,7 +26,7 @@ jobs:
run: sudo apt update && sudo apt install gettext -y
- name: Install newIDE dependencies
run: npm install
run: npm ci
working-directory: newIDE/app
- name: Extract translations

View File

@@ -60,7 +60,7 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Release" AND NOT WIN32 AND CMAKE_COMPILER_IS_
endif()
#Activate C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 11) # Upgrading to C++17 would need to remove usage of bind2nd (should be easy).
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Mark some warnings as errors
@@ -77,7 +77,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
-Wno-unused-private-field
# Make as much warnings considered as errors as possible (only one for now).
-Werror=return-stack-address)
-Werror=return-stack-address
-Werror=return-type)
endif()
# Define common directories:

View File

@@ -45,10 +45,39 @@ ForEachChildVariableEvent::GetAllActionsVectors() const {
return allActions;
}
vector<pair<gd::Expression*, gd::ParameterMetadata> >
ForEachChildVariableEvent::GetAllExpressionsWithMetadata() {
vector<pair<gd::Expression*, gd::ParameterMetadata> >
allExpressionsWithMetadata;
auto metadata = gd::ParameterMetadata().SetType("scenevar");
allExpressionsWithMetadata.push_back(
std::make_pair(&iterableVariableName, metadata));
allExpressionsWithMetadata.push_back(
std::make_pair(&valueIteratorVariableName, metadata));
allExpressionsWithMetadata.push_back(
std::make_pair(&keyIteratorVariableName, metadata));
return allExpressionsWithMetadata;
}
vector<pair<const gd::Expression*, const gd::ParameterMetadata> >
ForEachChildVariableEvent::GetAllExpressionsWithMetadata() const {
vector<pair<const gd::Expression*, const gd::ParameterMetadata> >
allExpressionsWithMetadata;
auto metadata = gd::ParameterMetadata().SetType("scenevar");
allExpressionsWithMetadata.push_back(
std::make_pair(&iterableVariableName, metadata));
allExpressionsWithMetadata.push_back(
std::make_pair(&valueIteratorVariableName, metadata));
allExpressionsWithMetadata.push_back(
std::make_pair(&keyIteratorVariableName, metadata));
return allExpressionsWithMetadata;
}
void ForEachChildVariableEvent::SerializeTo(SerializerElement& element) const {
element.AddChild("iterableVariableName").SetValue(iterableVariableName);
element.AddChild("valueIteratorVariableName").SetValue(valueIteratorVariableName);
element.AddChild("keyIteratorVariableName").SetValue(keyIteratorVariableName);
element.AddChild("iterableVariableName").SetValue(iterableVariableName.GetPlainString());
element.AddChild("valueIteratorVariableName").SetValue(valueIteratorVariableName.GetPlainString());
element.AddChild("keyIteratorVariableName").SetValue(keyIteratorVariableName.GetPlainString());
gd::EventsListSerialization::SerializeInstructionsTo(
conditions, element.AddChild("conditions"));
gd::EventsListSerialization::SerializeInstructionsTo(

View File

@@ -8,6 +8,7 @@
#define FOREACHCHILDVARIABLEEVENT_H
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Expression.h"
namespace gd {
class Instruction;
class Project;
@@ -44,7 +45,7 @@ class GD_CORE_API ForEachChildVariableEvent : public gd::BaseEvent {
*
* It is the structure variable that will be iterated on.
*/
const gd::String& GetIterableVariableName() const { return iterableVariableName; };
const gd::String& GetIterableVariableName() const { return iterableVariableName.GetPlainString(); };
/**
* \brief Set the iterable variable name attached to the event.
@@ -56,15 +57,15 @@ class GD_CORE_API ForEachChildVariableEvent : public gd::BaseEvent {
/**
* \brief Get the value iterator variable attached to the event.
*
* It is the variable that will contain the value of the
* It is the variable that will contain the value of the
* iterable's child being iterated on.
*/
const gd::String& GetValueIteratorVariableName() const { return valueIteratorVariableName; };
const gd::String& GetValueIteratorVariableName() const { return valueIteratorVariableName.GetPlainString(); };
/**
* \brief Set the value iterator variable attached to the event.
*
* It is the variable that will contain the value of the
* It is the variable that will contain the value of the
* iterable's child being iterated on.
*/
void SetValueIteratorVariableName(gd::String newName) { valueIteratorVariableName = newName; };
@@ -72,15 +73,15 @@ class GD_CORE_API ForEachChildVariableEvent : public gd::BaseEvent {
/**
* \brief Get the key iterator variable attached to the event.
*
* It is the variable that will contain the name of the
* It is the variable that will contain the name of the
* iterable's child being iterated on.
*/
const gd::String& GetKeyIteratorVariableName() const { return keyIteratorVariableName; };
const gd::String& GetKeyIteratorVariableName() const { return keyIteratorVariableName.GetPlainString(); };
/**
* \brief Set the key iterator variable attached to the event.
*
* It is the variable that will contain the name of the
* It is the variable that will contain the name of the
* iterable's child being iterated on.
*/
void SetKeyIteratorVariableName(gd::String newName) { keyIteratorVariableName = newName; };
@@ -92,14 +93,19 @@ class GD_CORE_API ForEachChildVariableEvent : public gd::BaseEvent {
virtual std::vector<gd::InstructionsList*> GetAllConditionsVectors();
virtual std::vector<gd::InstructionsList*> GetAllActionsVectors();
virtual std::vector<std::pair<const gd::Expression*, const gd::ParameterMetadata> >
GetAllExpressionsWithMetadata() const;
virtual std::vector<std::pair<gd::Expression*, gd::ParameterMetadata> >
GetAllExpressionsWithMetadata();
virtual void SerializeTo(SerializerElement& element) const;
virtual void UnserializeFrom(gd::Project& project,
const SerializerElement& element);
private:
gd::String valueIteratorVariableName;
gd::String keyIteratorVariableName;
gd::String iterableVariableName;
gd::Expression valueIteratorVariableName;
gd::Expression keyIteratorVariableName;
gd::Expression iterableVariableName;
gd::InstructionsList conditions;
gd::InstructionsList actions;
gd::EventsList events;

View File

@@ -64,12 +64,6 @@ class GD_CORE_API ForEachEvent : public gd::BaseEvent {
virtual void UnserializeFrom(gd::Project& project,
const SerializerElement& element);
std::vector<gd::Expression*> GetAllObjectExpressions() {
std::vector<gd::Expression*> allObjectExpressions;
allObjectExpressions.push_back(&objectsToPick);
return allObjectExpressions;
}
private:
gd::Expression objectsToPick;
gd::InstructionsList conditions;

View File

@@ -131,30 +131,36 @@ void LinkEvent::SerializeTo(SerializerElement& element) const {
void LinkEvent::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
SerializerElement& includeElement = element.GetChild("include", 0, "Limites");
SetTarget(element.GetChild("target", 0, "Scene").GetValue().GetString());
if (includeElement.HasAttribute("includeAll")) {
// Compatibility with GDevelop <= 4.0.92
if (includeElement.GetBoolAttribute("includeAll", true)) {
SetIncludeAllEvents();
// Compatibility with GD <= 5
if (element.HasChild("include", "Limites")) {
SerializerElement& includeElement = element.GetChild("include", 0, "Limites");
if (includeElement.HasAttribute("includeAll")) {
// Compatibility with GDevelop <= 4.0.92
if (includeElement.GetBoolAttribute("includeAll", true)) {
SetIncludeAllEvents();
} else {
SetIncludeStartAndEnd(includeElement.GetIntAttribute("start"),
includeElement.GetIntAttribute("end"));
}
} else {
SetIncludeStartAndEnd(includeElement.GetIntAttribute("start"),
includeElement.GetIntAttribute("end"));
// GDevelop > 4.0.92
IncludeConfig config = static_cast<IncludeConfig>(
includeElement.GetIntAttribute("includeConfig", 0));
if (config == INCLUDE_ALL)
SetIncludeAllEvents();
else if (config == INCLUDE_EVENTS_GROUP)
SetIncludeEventsGroup(includeElement.GetStringAttribute("eventsGroup"));
else if (config == INCLUDE_BY_INDEX)
SetIncludeStartAndEnd(includeElement.GetIntAttribute("start"),
includeElement.GetIntAttribute("end"));
}
} else {
// GDevelop > 4.0.92
IncludeConfig config = static_cast<IncludeConfig>(
includeElement.GetIntAttribute("includeConfig", 0));
if (config == INCLUDE_ALL)
SetIncludeAllEvents();
else if (config == INCLUDE_EVENTS_GROUP)
SetIncludeEventsGroup(includeElement.GetStringAttribute("eventsGroup"));
else if (config == INCLUDE_BY_INDEX)
SetIncludeStartAndEnd(includeElement.GetIntAttribute("start"),
includeElement.GetIntAttribute("end"));
// Since GDevelop 5, links always include all events.
SetIncludeAllEvents();
}
// end of compatibility code
}
bool LinkEvent::AcceptVisitor(gd::EventVisitor &eventVisitor) {

View File

@@ -35,7 +35,7 @@ vector<pair<gd::Expression*, gd::ParameterMetadata> >
RepeatEvent::GetAllExpressionsWithMetadata() {
vector<pair<gd::Expression*, gd::ParameterMetadata> >
allExpressionsWithMetadata;
auto metadata = gd::ParameterMetadata().SetType("expression");
auto metadata = gd::ParameterMetadata().SetType("number");
allExpressionsWithMetadata.push_back(
std::make_pair(&repeatNumberExpression, metadata));
@@ -61,7 +61,7 @@ vector<pair<const gd::Expression*, const gd::ParameterMetadata> >
RepeatEvent::GetAllExpressionsWithMetadata() const {
vector<pair<const gd::Expression*, const gd::ParameterMetadata> >
allExpressionsWithMetadata;
auto metadata = gd::ParameterMetadata().SetType("expression");
auto metadata = gd::ParameterMetadata().SetType("number");
allExpressionsWithMetadata.push_back(
std::make_pair(&repeatNumberExpression, metadata));

View File

@@ -16,6 +16,7 @@
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/Project.h"
using namespace std;
@@ -295,16 +296,10 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
gd::String objectInParameter =
condition.GetParameter(pNb).GetPlainString();
if (!GetObjectsAndGroups().HasObjectNamed(objectInParameter) &&
!GetGlobalObjectsAndGroups().HasObjectNamed(objectInParameter) &&
!GetObjectsAndGroups().GetObjectGroups().Has(objectInParameter) &&
!GetGlobalObjectsAndGroups().GetObjectGroups().Has(
objectInParameter)) {
if (!GetObjectsContainersList().HasObjectOrGroupNamed(objectInParameter)) {
return "/* Unknown object - skipped. */";
} else if (!instrInfos.parameters[pNb].GetExtraInfo().empty() &&
gd::GetTypeOfObject(GetGlobalObjectsAndGroups(),
GetObjectsAndGroups(),
objectInParameter) !=
GetObjectsContainersList().GetTypeOfObject(objectInParameter) !=
instrInfos.parameters[pNb].GetExtraInfo()) {
return "/* Mismatched object type - skipped. */";
}
@@ -315,11 +310,10 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
gd::String objectName = condition.GetParameter(0).GetPlainString();
if (!objectName.empty() && !instrInfos.parameters.empty()) {
std::vector<gd::String> realObjects =
ExpandObjectsName(objectName, context);
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Set up the context
gd::String objectType = gd::GetTypeOfObject(
GetGlobalObjectsAndGroups(), GetObjectsAndGroups(), realObjects[i]);
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo =
MetadataProvider::GetObjectMetadata(platform, objectType);
@@ -343,13 +337,10 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
}
} else if (instrInfos.IsBehaviorInstruction()) {
gd::String objectName = condition.GetParameter(0).GetPlainString();
gd::String behaviorType =
gd::GetTypeOfBehavior(GetGlobalObjectsAndGroups(),
GetObjectsAndGroups(),
condition.GetParameter(1).GetPlainString());
gd::String behaviorType = GetObjectsContainersList().GetTypeOfBehavior(condition.GetParameter(1).GetPlainString());
if (instrInfos.parameters.size() >= 2) {
std::vector<gd::String> realObjects =
ExpandObjectsName(objectName, context);
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Setup context
const BehaviorMetadata& autoInfo =
@@ -479,16 +470,10 @@ gd::String EventsCodeGenerator::GenerateActionCode(
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters[pNb].GetType())) {
gd::String objectInParameter = action.GetParameter(pNb).GetPlainString();
if (!GetObjectsAndGroups().HasObjectNamed(objectInParameter) &&
!GetGlobalObjectsAndGroups().HasObjectNamed(objectInParameter) &&
!GetObjectsAndGroups().GetObjectGroups().Has(objectInParameter) &&
!GetGlobalObjectsAndGroups().GetObjectGroups().Has(
objectInParameter)) {
if (!GetObjectsContainersList().HasObjectOrGroupNamed(objectInParameter)) {
return "/* Unknown object - skipped. */";
} else if (!instrInfos.parameters[pNb].GetExtraInfo().empty() &&
gd::GetTypeOfObject(GetGlobalObjectsAndGroups(),
GetObjectsAndGroups(),
objectInParameter) !=
GetObjectsContainersList().GetTypeOfObject(objectInParameter) !=
instrInfos.parameters[pNb].GetExtraInfo()) {
return "/* Mismatched object type - skipped. */";
}
@@ -501,11 +486,10 @@ gd::String EventsCodeGenerator::GenerateActionCode(
if (!instrInfos.parameters.empty()) {
std::vector<gd::String> realObjects =
ExpandObjectsName(objectName, context);
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Setup context
gd::String objectType = gd::GetTypeOfObject(
GetGlobalObjectsAndGroups(), GetObjectsAndGroups(), realObjects[i]);
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo =
MetadataProvider::GetObjectMetadata(platform, objectType);
@@ -529,14 +513,11 @@ gd::String EventsCodeGenerator::GenerateActionCode(
}
} else if (instrInfos.IsBehaviorInstruction()) {
gd::String objectName = action.GetParameter(0).GetPlainString();
gd::String behaviorType =
gd::GetTypeOfBehavior(GetGlobalObjectsAndGroups(),
GetObjectsAndGroups(),
action.GetParameter(1).GetPlainString());
gd::String behaviorType = GetObjectsContainersList().GetTypeOfBehavior(action.GetParameter(1).GetPlainString());
if (instrInfos.parameters.size() >= 2) {
std::vector<gd::String> realObjects =
ExpandObjectsName(objectName, context);
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Setup context
const BehaviorMetadata& autoInfo =
@@ -912,41 +893,6 @@ gd::String EventsCodeGenerator::ConvertToStringExplicit(
return "\"" + ConvertToString(plainString) + "\"";
}
std::vector<gd::String> EventsCodeGenerator::ExpandObjectsName(
const gd::String& objectName,
const EventsCodeGenerationContext& context) const {
// Note: this logic is duplicated in EventsContextAnalyzer::ExpandObjectsName
std::vector<gd::String> realObjects;
if (globalObjectsAndGroups.GetObjectGroups().Has(objectName))
realObjects = globalObjectsAndGroups.GetObjectGroups()
.Get(objectName)
.GetAllObjectsNames();
else if (objectsAndGroups.GetObjectGroups().Has(objectName))
realObjects =
objectsAndGroups.GetObjectGroups().Get(objectName).GetAllObjectsNames();
else
realObjects.push_back(objectName);
// If current object is present, use it and only it.
if (find(realObjects.begin(),
realObjects.end(),
context.GetCurrentObject()) != realObjects.end()) {
realObjects.clear();
realObjects.push_back(context.GetCurrentObject());
}
// Ensure that all returned objects actually exists.
for (std::size_t i = 0; i < realObjects.size();) {
if (!objectsAndGroups.HasObjectNamed(realObjects[i]) &&
!globalObjectsAndGroups.HasObjectNamed(realObjects[i]))
realObjects.erase(realObjects.begin() + i);
else
++i;
}
return realObjects;
}
void EventsCodeGenerator::DeleteUselessEvents(gd::EventsList& events) {
for (std::size_t eId = events.size() - 1; eId < events.size(); --eId) {
if (events[eId].CanHaveSubEvents()) // Process sub events, if any
@@ -963,6 +909,8 @@ void EventsCodeGenerator::DeleteUselessEvents(gd::EventsList& events) {
*/
void EventsCodeGenerator::PreprocessEventList(gd::EventsList& listEvent) {
for (std::size_t i = 0; i < listEvent.GetEventsCount(); ++i) {
if (listEvent[i].IsDisabled()) continue;
listEvent[i].Preprocess(*this, listEvent, i);
if (i <
listEvent.GetEventsCount()) { // Be sure that that there is still an
@@ -1260,12 +1208,24 @@ gd::String EventsCodeGenerator::GenerateArgumentsList(
return argumentsStr;
}
gd::String EventsCodeGenerator::GeneratePropertyGetter(const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getProperty" + property.GetName() + "()";
}
gd::String EventsCodeGenerator::GenerateParameterGetter(const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getParameter" + parameter.GetName() + "()";
}
EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
const gd::Layout& layout,
const gd::Platform& platform_)
: platform(platform_),
globalObjectsAndGroups(project_),
objectsAndGroups(layout),
projectScopedContainers(gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project_, layout)),
hasProjectAndLayout(true),
project(&project_),
scene(&layout),
@@ -1277,11 +1237,9 @@ EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
EventsCodeGenerator::EventsCodeGenerator(
const gd::Platform& platform_,
const gd::ObjectsContainer& globalObjectsAndGroups_,
const gd::ObjectsContainer& objectsAndGroups_)
const gd::ProjectScopedContainers& projectScopedContainers_)
: platform(platform_),
globalObjectsAndGroups(globalObjectsAndGroups_),
objectsAndGroups(objectsAndGroups_),
projectScopedContainers(projectScopedContainers_),
hasProjectAndLayout(false),
project(nullptr),
scene(nullptr),

View File

@@ -13,12 +13,14 @@
#include "GDCore/Events/Event.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/String.h"
#include "GDCore/Project/ProjectScopedContainers.h"
namespace gd {
class EventsList;
class Expression;
class Project;
class Layout;
class ObjectsContainer;
class ObjectsContainersList;
class ExternalEvents;
class ParameterMetadata;
class ObjectMetadata;
@@ -57,8 +59,7 @@ class GD_CORE_API EventsCodeGenerator {
* objects/groups and platform
*/
EventsCodeGenerator(const gd::Platform& platform,
const gd::ObjectsContainer& globalObjectsAndGroups_,
const gd::ObjectsContainer& objectsAndGroups_);
const gd::ProjectScopedContainers& projectScopedContainers_);
virtual ~EventsCodeGenerator(){};
/**
@@ -326,18 +327,12 @@ class GD_CORE_API EventsCodeGenerator {
*/
bool ErrorOccurred() const { return errorOccurred; };
/**
* \brief Get the global objects/groups used for code generation.
*/
const gd::ObjectsContainer& GetGlobalObjectsAndGroups() const {
return globalObjectsAndGroups;
}
const gd::ObjectsContainersList& GetObjectsContainersList() const {
return projectScopedContainers.GetObjectsContainersList();
};
/**
* \brief Get the objects/groups used for code generation.
*/
const gd::ObjectsContainer& GetObjectsAndGroups() const {
return objectsAndGroups;
const gd::ProjectScopedContainers& GetProjectScopedContainers() const {
return projectScopedContainers;
}
/**
@@ -363,22 +358,6 @@ class GD_CORE_API EventsCodeGenerator {
*/
const gd::Platform& GetPlatform() const { return platform; }
/**
* \brief Convert a group name to the full list of objects contained in the
* group.
*
* Get a list containing the "real" objects name when the events refers to \a
* objectName :<br> If \a objectName is really an object, the list will only
* contains \a objectName unchanged.<br> If \a objectName is a group, the list
* will contains all the objects of the group.<br> If \a objectName is the
* "current" object in the context ( i.e: The object being used for launching
* an action... ), none of the two rules below apply, and the list will only
* contains the context "current" object name.
*/
std::vector<gd::String> ExpandObjectsName(
const gd::String& objectName,
const EventsCodeGenerationContext& context) const;
/**
* \brief Get the maximum depth of custom conditions reached during code
* generation.
@@ -566,6 +545,10 @@ class GD_CORE_API EventsCodeGenerator {
return ".getChild(" + ConvertToStringExplicit(childName) + ")";
};
virtual gd::String GenerateVariableValueAs(const gd::String& type) {
return type == "string" ? ".getAsString()" : ".getAsNumber()";
}
/**
* \brief Generate the code to get the child of a variable,
* using generated the expression.
@@ -594,6 +577,15 @@ 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 GenerateParameterGetter(const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
/**
* \brief Generate the code to reference an object which is
* in an empty/null state.
@@ -777,8 +769,7 @@ class GD_CORE_API EventsCodeGenerator {
const gd::Platform& platform; ///< The platform being used.
const gd::ObjectsContainer& globalObjectsAndGroups;
const gd::ObjectsContainer& objectsAndGroups;
gd::ProjectScopedContainers projectScopedContainers;
bool hasProjectAndLayout; ///< true only if project and layout are valid
///< references. If false, they should not be used.

View File

@@ -7,6 +7,7 @@
#include <memory>
#include <vector>
#include <vector>
#include "GDCore/CommonTools.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h"
@@ -25,6 +26,9 @@
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/VariablesContainersList.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/IDE/Events/ExpressionVariableOwnerFinder.h"
@@ -47,8 +51,7 @@ gd::String ExpressionCodeGenerator::GenerateExpressionCode(
}
gd::ExpressionValidator validator(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetProjectScopedContainers(),
rootType);
node->Visit(validator);
if (!validator.GetFatalErrors().empty()) {
@@ -100,34 +103,94 @@ 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.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetObjectsContainersList(),
rootType,
node);
EventsCodeGenerator::VariableScope scope =
type == "globalvar"
? gd::EventsCodeGenerator::PROJECT_VARIABLE
: ((type == "scenevar")
? gd::EventsCodeGenerator::LAYOUT_VARIABLE
: gd::EventsCodeGenerator::OBJECT_VARIABLE);
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
rootObjectName,
node);
output += codeGenerator.GenerateGetVariable(
node.name, scope, context, objectName);
if (node.child) node.child->Visit(*this);
if (gd::ParameterMetadata::IsExpression("variable", type)) {
// The node is a variable inside an expression waiting for a *variable* to be returned, not its value.
EventsCodeGenerator::VariableScope scope =
type == "globalvar"
? gd::EventsCodeGenerator::PROJECT_VARIABLE
: ((type == "scenevar")
? gd::EventsCodeGenerator::LAYOUT_VARIABLE
: gd::EventsCodeGenerator::OBJECT_VARIABLE);
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
rootObjectName,
node);
output += codeGenerator.GenerateGetVariable(
node.name, scope, context, objectName);
if (node.child) node.child->Visit(*this);
} else {
// The node represents a variable or an object variable in an expression waiting for its *value* to be returned.
codeGenerator.GetProjectScopedContainers().MatchIdentifierWithName<void>(node.name, [&](){
// Generate the code to access the object variables.
// Defer generation of the access to the object and variable to the child,
// once we know the name of the variable.
objectNameToUseForVariableAccessor = node.name;
if (node.child) node.child->Visit(*this);
objectNameToUseForVariableAccessor = "";
output += codeGenerator.GenerateVariableValueAs(type);
}, [&]() {
if (!codeGenerator.HasProjectAndLayout()) {
gd::LogWarning("Tried to generate access to a variable without a project/scene - the code generator only works for global and scene variables for now.");
output += GenerateDefaultValue(type);
return;
}
// This could be adapted in the future if more scopes are supported.
EventsCodeGenerator::VariableScope scope = gd::EventsCodeGenerator::PROJECT_VARIABLE;
if (codeGenerator.GetProjectScopedContainers().GetVariablesContainersList().GetBottomMostVariablesContainer()->Has(node.name)) {
scope = gd::EventsCodeGenerator::LAYOUT_VARIABLE;
}
output += codeGenerator.GenerateGetVariable(node.name, scope, context, "");
if (node.child) node.child->Visit(*this);
output += codeGenerator.GenerateVariableValueAs(type);
}, [&]() {
// Properties are not supported.
output += GenerateDefaultValue(type);
}, [&]() {
// Parameters are not supported.
output += GenerateDefaultValue(type);
}, [&]() {
// The identifier does not represents a variable (or a child variable), or not at least an existing
// one, nor an object variable. It's invalid.
output += GenerateDefaultValue(type);
});
}
}
void ExpressionCodeGenerator::OnVisitVariableAccessorNode(
VariableAccessorNode& node) {
output += codeGenerator.GenerateVariableAccessor(node.name);
if (!objectNameToUseForVariableAccessor.empty()) {
// Use the name of the object passed by the parent, as we need both to access an object variable.
output += codeGenerator.GenerateGetVariable(node.name,
gd::EventsCodeGenerator::OBJECT_VARIABLE, context, objectNameToUseForVariableAccessor);
// We have accessed an object variable, from now we can continue accessing the child variables
// (including using the bracket notation).
objectNameToUseForVariableAccessor = "";
} else {
output += codeGenerator.GenerateVariableAccessor(node.name);
}
if (node.child) node.child->Visit(*this);
}
void ExpressionCodeGenerator::OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) {
if (!objectNameToUseForVariableAccessor.empty()) {
// Bracket notation can't be used to directly access a variable of an object (`MyObject["MyVariable"]`).
// This would be rejected by the ExpressionValidator.
output += codeGenerator.GenerateBadVariable();
return;
}
ExpressionCodeGenerator generator("string", "", codeGenerator, context);
node.expression->Visit(generator);
output +=
@@ -137,10 +200,10 @@ void ExpressionCodeGenerator::OnVisitVariableBracketAccessorNode(
void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetObjectsContainersList(),
rootType,
node);
if (gd::ParameterMetadata::IsObject(type)) {
output +=
codeGenerator.GenerateObject(node.identifierName, type, context);
@@ -153,8 +216,7 @@ void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
: gd::EventsCodeGenerator::OBJECT_VARIABLE);
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetObjectsContainersList(),
rootObjectName,
node);
output += codeGenerator.GenerateGetVariable(
@@ -162,30 +224,60 @@ void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
if (!node.childIdentifierName.empty()) {
output += codeGenerator.GenerateVariableAccessor(node.childIdentifierName);
}
} else if (node.childIdentifierName.empty()) {
output += "/* Error during generation, unrecognized identifier type: " +
codeGenerator.ConvertToString(type) + " with value " +
codeGenerator.ConvertToString(node.identifierName) + " */ " +
codeGenerator.ConvertToStringExplicit(node.identifierName);
}
else {
// This is for function names that are put in IdentifierNode
// because the type is needed to tell them apart from variables.
output += GenerateDefaultValue(type);
} else {
const auto& variablesContainersList = codeGenerator.GetProjectScopedContainers().GetVariablesContainersList();
const auto& propertiesContainersList = codeGenerator.GetProjectScopedContainers().GetPropertiesContainersList();
const auto& parametersVectorsList = codeGenerator.GetProjectScopedContainers().GetParametersVectorsList();
// The node represents a variable, property, parameter or an object.
codeGenerator.GetProjectScopedContainers().MatchIdentifierWithName<void>(node.identifierName, [&]() {
// Generate the code to access the object variable.
output += codeGenerator.GenerateGetVariable(
node.childIdentifierName, gd::EventsCodeGenerator::OBJECT_VARIABLE, context, node.identifierName);
output += codeGenerator.GenerateVariableValueAs(type);
}, [&]() {
if (!codeGenerator.HasProjectAndLayout()) {
gd::LogWarning("Tried to generate access to a variable without a project/scene - the code generator only works for global and scene variables for now.");
output += GenerateDefaultValue(type);
return;
}
// This could be adapted in the future if more scopes are supported at runtime.
EventsCodeGenerator::VariableScope scope = gd::EventsCodeGenerator::PROJECT_VARIABLE;
if (variablesContainersList.GetBottomMostVariablesContainer()->Has(node.identifierName)) {
scope = gd::EventsCodeGenerator::LAYOUT_VARIABLE;
}
output += codeGenerator.GenerateGetVariable(node.identifierName, scope, context, "");
if (!node.childIdentifierName.empty()) {
output += codeGenerator.GenerateVariableAccessor(node.childIdentifierName);
}
output += codeGenerator.GenerateVariableValueAs(type);
}, [&]() {
const auto& propertiesContainerAndProperty = propertiesContainersList.Get(node.identifierName);
output += codeGenerator.GeneratePropertyGetter(
propertiesContainerAndProperty.first, propertiesContainerAndProperty.second, type, context);
}, [&]() {
const auto& parameter = gd::ParameterMetadataTools::Get(parametersVectorsList, node.identifierName);
output += codeGenerator.GenerateParameterGetter(parameter, type, context);
}, [&]() {
// The identifier does not represents a variable (or a child variable), or not at least an existing
// one, nor an object variable. It's invalid.
output += GenerateDefaultValue(type);
});
}
}
void ExpressionCodeGenerator::OnVisitFunctionCallNode(FunctionCallNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetObjectsContainersList(),
rootType,
node);
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetObjectsContainersList(),
node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
@@ -236,11 +328,6 @@ gd::String ExpressionCodeGenerator::GenerateObjectFunctionCode(
const gd::String& objectName,
const std::vector<std::unique_ptr<ExpressionNode>>& parameters,
const ExpressionMetadata& expressionMetadata) {
const gd::ObjectsContainer& globalObjectsAndGroups =
codeGenerator.GetGlobalObjectsAndGroups();
const gd::ObjectsContainer& objectsAndGroups =
codeGenerator.GetObjectsAndGroups();
codeGenerator.AddIncludeFiles(
expressionMetadata.GetIncludeFiles());
@@ -261,12 +348,11 @@ gd::String ExpressionCodeGenerator::GenerateObjectFunctionCode(
// Get object(s) concerned by function call
std::vector<gd::String> realObjects =
codeGenerator.ExpandObjectsName(objectName, context);
codeGenerator.GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
context.ObjectsListNeeded(realObjects[i]);
gd::String objectType = gd::GetTypeOfObject(
globalObjectsAndGroups, objectsAndGroups, realObjects[i]);
gd::String objectType = codeGenerator.GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo = MetadataProvider::GetObjectMetadata(
codeGenerator.GetPlatform(), objectType);
@@ -288,11 +374,6 @@ gd::String ExpressionCodeGenerator::GenerateBehaviorFunctionCode(
const gd::String& behaviorName,
const std::vector<std::unique_ptr<ExpressionNode>>& parameters,
const ExpressionMetadata& expressionMetadata) {
const gd::ObjectsContainer& globalObjectsAndGroups =
codeGenerator.GetGlobalObjectsAndGroups();
const gd::ObjectsContainer& objectsAndGroups =
codeGenerator.GetObjectsAndGroups();
codeGenerator.AddIncludeFiles(
expressionMetadata.GetIncludeFiles());
@@ -311,12 +392,11 @@ gd::String ExpressionCodeGenerator::GenerateBehaviorFunctionCode(
// Get object(s) concerned by function call
std::vector<gd::String> realObjects =
codeGenerator.ExpandObjectsName(objectName, context);
codeGenerator.GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
gd::String functionOutput = GenerateDefaultValue(type);
gd::String behaviorType = gd::GetTypeOfBehavior(
globalObjectsAndGroups, objectsAndGroups, behaviorName);
gd::String behaviorType = codeGenerator.GetObjectsContainersList().GetTypeOfBehavior(behaviorName);
const BehaviorMetadata& autoInfo = MetadataProvider::GetBehaviorMetadata(
codeGenerator.GetPlatform(), behaviorType);
@@ -352,8 +432,7 @@ gd::String ExpressionCodeGenerator::GenerateParametersCodes(
if (!parameterMetadata.IsCodeOnly()) {
if (nonCodeOnlyParameterIndex < parameters.size()) {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetObjectsContainersList(),
rootObjectName,
*parameters[nonCodeOnlyParameterIndex].get());
ExpressionCodeGenerator generator(parameterMetadata.GetType(), objectName, codeGenerator, context);
@@ -423,8 +502,7 @@ gd::String ExpressionCodeGenerator::GenerateDefaultValue(
void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetObjectsContainersList(),
rootType,
node);
output += GenerateDefaultValue(type);
@@ -433,8 +511,7 @@ void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) {
void ExpressionCodeGenerator::OnVisitObjectFunctionNameNode(
ObjectFunctionNameNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetObjectsContainersList(),
rootType,
node);
output += GenerateDefaultValue(type);

View File

@@ -102,6 +102,7 @@ class GD_CORE_API ExpressionCodeGenerator : public ExpressionParser2NodeWorker {
const std::vector<std::unique_ptr<ExpressionNode>>& parameters);
gd::String output;
gd::String objectNameToUseForVariableAccessor;
EventsCodeGenerator& codeGenerator;
EventsCodeGenerationContext& context;
const gd::String rootType;

View File

@@ -240,7 +240,7 @@ class GD_CORE_API BaseEvent {
*/
virtual void UnserializeFrom(gd::Project& project,
const SerializerElement& element){};
virtual bool AcceptVisitor(gd::EventVisitor& eventVisitor);
virtual void AcceptVisitor(gd::ReadOnlyEventVisitor& eventVisitor) const;
///@}
@@ -281,15 +281,6 @@ class GD_CORE_API BaseEvent {
* \brief True if the event should be folded in the events editor.
*/
bool IsFolded() const { return folded; }
/**
* \brief Return a list of all objects linked to the event.
*/
virtual std::vector<gd::Expression*> GetAllObjectExpressions() {
std::vector<gd::Expression*> allObjectExpressions;
return allObjectExpressions;
}
///@}
std::weak_ptr<gd::BaseEvent>

View File

@@ -47,14 +47,9 @@ class GD_CORE_API ExpressionParser2 {
virtual ~ExpressionParser2(){};
/**
* Parse the given expression with the specified type.
* Parse the given expression into a tree of nodes.
*
* \param type Type of the expression: "string", "number",
* type supported by gd::ParameterMetadata::IsObject, types supported by
* gd::ParameterMetadata::IsExpression or "unknown".
* \param expression The expression to parse
* \param objectName Specify the object name, only for the
* case of "objectvar" type.
* \param expression The expression to parse.
*
* \return The node representing the expression as a parsed tree.
*/
@@ -267,12 +262,11 @@ class GD_CORE_API ExpressionParser2 {
} else if (CheckIfChar(IsDot)) {
ExpressionParserLocation dotLocation = SkipChar();
SkipAllWhitespaces();
return ObjectFunctionOrBehaviorFunction(
return ObjectFunctionOrBehaviorFunctionOrVariable(
name, nameLocation, dotLocation);
} else if (CheckIfChar(IsOpeningSquareBracket)) {
return Variable(name, nameLocation);
}
else {
} else {
auto identifier = gd::make_unique<IdentifierNode>(name);
identifier->location = ExpressionParserLocation(
nameLocation.GetStartPosition(), GetCurrentPosition());
@@ -318,7 +312,7 @@ class GD_CORE_API ExpressionParser2 {
auto dotLocation = SkipChar();
SkipAllWhitespaces();
auto identifierAndLocation = ReadIdentifierName();
auto identifierAndLocation = ReadIdentifierName(/*allowDeprecatedSpacesInName=*/ false);
auto child =
gd::make_unique<VariableAccessorNode>(identifierAndLocation.name);
child->child = VariableAccessorOrVariableBracketAccessor();
@@ -358,11 +352,11 @@ class GD_CORE_API ExpressionParser2 {
}
std::unique_ptr<IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode>
ObjectFunctionOrBehaviorFunction(
ObjectFunctionOrBehaviorFunctionOrVariable(
const gd::String &parentIdentifier,
const ExpressionParserLocation &parentIdentifierLocation,
const ExpressionParserLocation &parentIdentifierDotLocation) {
auto childIdentifierAndLocation = ReadIdentifierName();
auto childIdentifierAndLocation = ReadIdentifierName(/*allowDeprecatedSpacesInName=*/ false);
const gd::String &childIdentifierName = childIdentifierAndLocation.name;
const auto &childIdentifierNameLocation =
childIdentifierAndLocation.location;
@@ -420,12 +414,6 @@ class GD_CORE_API ExpressionParser2 {
auto node = gd::make_unique<IdentifierNode>(
parentIdentifier, childIdentifierName);
if (!CheckIfChar(IsParameterSeparator) && !CheckIfChar(IsClosingParenthesis) && !IsEndReached()) {
node->diagnostic = RaiseSyntaxError(
_("An opening parenthesis (for an object expression), a double colon "
"(:: for a behavior expression), a dot or an opening bracket (for "
"a child variable) where expected."));
}
node->location = ExpressionParserLocation(
parentIdentifierLocation.GetStartPosition(), GetCurrentPosition());
node->identifierNameLocation = parentIdentifierLocation;
@@ -625,13 +613,13 @@ class GD_CORE_API ExpressionParser2 {
ExpressionParserLocation location;
};
IdentifierAndLocation ReadIdentifierName() {
IdentifierAndLocation ReadIdentifierName(bool allowDeprecatedSpacesInName = true) {
gd::String name;
size_t startPosition = currentPosition;
while (currentPosition < expression.size() &&
(CheckIfChar(IsAllowedInIdentifier)
// Allow whitespace in identifier name for compatibility
|| expression[currentPosition] == ' ')) {
|| (allowDeprecatedSpacesInName && expression[currentPosition] == ' '))) {
name += expression[currentPosition];
currentPosition++;
}

View File

@@ -189,7 +189,7 @@ struct GD_CORE_API IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
*
* The name of a function to call on an object or the behavior,
* for example: "MyObject.Function" or "MyObject.Physics".
*
*
* A variable, potentially with accessor to its child,
* for example: MyVariable or MyVariable.MyChild
*/
@@ -239,13 +239,14 @@ struct GD_CORE_API VariableAccessorOrVariableBracketAccessorNode : public Expres
};
/**
* \brief A variable with bracket accessor or at least 2 "dot" accessors.
* \brief A variable, or object variable, with bracket accessor or at least 2 "dot" accessors.
*
* Example: `MyVariable["MyChildren"]` or `MyVariable.MyChildren.MyGrandChildren`.
* Example: `MyObject["MyVariable"]` or `MyObject.MyVariable.MyChildren`.
*
* Example: MyVariable[MyChildren] or MyVariable.MyChildren.MyGranChildren.
*
* Other cases like "MyVariable" or "MyVariable.MyChildren" are IdentifierNode
* to allow handling ambiguities.
*
*
* \see gd::IdentifierNode
* \see gd::VariableAccessorNode
* \see gd::VariableBracketAccessorNode
@@ -267,7 +268,7 @@ struct GD_CORE_API VariableNode : public FunctionCallOrObjectFunctionNameOrEmpty
};
/**
* \brief A bracket accessor of a variable. Example: MyChild
* \brief A direct accessor to a child variable. Example: MyChild
* in MyVariable.MyChild
*/
struct GD_CORE_API VariableAccessorNode
@@ -285,7 +286,7 @@ struct GD_CORE_API VariableAccessorNode
};
/**
* \brief A bracket accessor of a variable. Example: ["MyChild"]
* \brief A bracket accessor to a child variable. Example: ["MyChild"]
* (in MyVariable["MyChild"]).
*/
struct GD_CORE_API VariableBracketAccessorNode
@@ -303,10 +304,10 @@ struct GD_CORE_API VariableBracketAccessorNode
/**
* \brief The name of a function to call on an object or the behavior
* For example: "MyObject.Physics::LinearVelocity".
*
*
* Other cases like "MyObject.Function" or "MyObject.Physics" are IdentifierNode
* to allow handling ambiguities.
*
*
* \see gd::IdentifierNode
*/
struct GD_CORE_API ObjectFunctionNameNode

View File

@@ -75,10 +75,21 @@ inline bool IsZeroDigit(gd::String::value_type character) {
return character == '0';
}
inline bool IsAdditionalReservedCharacter(gd::String::value_type character) {
// These characters are not part of the grammar - but are often used in programming language
// and could become operators or part of the grammar one day.
return character == '~' || character == '\'' || character == '%' ||
character == '#' || character == '@' || character == '|' ||
character == '&' || character == '`' || character == '$' ||
character == ';';
}
/**
* Check if the given character can be used in an identifier. This is
* any unicode character, except for:
* `, . " () [] {} + - < > ? ^ = \ : ! / *` and whitespaces (space, line break, carriage return).
* `, . " () [] {} + - < > ? ^ = \ : ! / * ~ ' % # @ | & $ ;`
* and backtick and whitespaces (space, line break, carriage return).
*
* This is loosely based on what is allowed in languages like JavaScript
* (see https://mathiasbynens.be/notes/javascript-properties), without support
@@ -96,7 +107,7 @@ inline bool IsAllowedInIdentifier(gd::String::value_type character) {
if (!IsParameterSeparator(character) && !IsDot(character) &&
!IsQuote(character) && !IsBracket(character) &&
!IsExpressionOperator(character) && !IsTermOperator(character) &&
!IsWhitespace(character)) {
!IsWhitespace(character) && !IsAdditionalReservedCharacter(character)) {
return true;
}

View File

@@ -1277,9 +1277,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
// Deprecated
obj.AddAction("SetEffectDoubleParameter",
_("Effect parameter (number)"),
_("Change the value of a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (number)"),
_("Change the value of a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
_("Effects"),
@@ -1287,17 +1287,17 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"res/actions/effect.png")
.AddParameter("object", _("Object"))
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("expression", _("New value"))
.MarkAsSimple()
.SetHidden();
// Deprecated
obj.AddAction("SetEffectStringParameter",
_("Effect parameter (string)"),
_("Change the value (string) of a parameter of an effect.") +
_("Effect property (string)"),
_("Change the value (string) of a property of an effect.") +
"\n" +
_("You can find the parameter names (and change the effect "
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
_("Effects"),
@@ -1305,16 +1305,16 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"res/actions/effect.png")
.AddParameter("object", _("Object"))
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("string", _("New value"))
.MarkAsSimple()
.SetHidden();
// Deprecated
obj.AddAction("SetEffectBooleanParameter",
_("Effect parameter (enable or disable)"),
_("Enable or disable a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (enable or disable)"),
_("Enable or disable a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Enable _PARAM2_ for effect _PARAM1_ of _PARAM0_: _PARAM3_"),
_("Effects"),
@@ -1322,8 +1322,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"res/actions/effect.png")
.AddParameter("object", _("Object"))
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("yesorno", _("Enable?"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("yesorno", _("Enable this property"))
.MarkAsSimple()
.SetHidden();

View File

@@ -444,9 +444,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
extension
.AddAction(
"SetLayerEffectParameter",
_("Effect parameter (number)"),
_("Change the value of a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (number)"),
_("Change the value of a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of layer _PARAM1_"),
_("Effects"),
@@ -456,16 +456,16 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
.AddParameter("layer", _("Layer"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("layerEffectName", _("Effect name"))
.AddParameter("layerEffectParameterName", _("Parameter name"))
.AddParameter("layerEffectParameterName", _("Property name"))
.AddParameter("expression", _("New value"))
.MarkAsAdvanced();
extension
.AddAction(
"SetLayerEffectStringParameter",
_("Effect parameter (string)"),
_("Change the value (string) of a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (string)"),
_("Change the value (string) of a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of layer _PARAM1_"),
_("Effects"),
@@ -475,16 +475,16 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
.AddParameter("layer", _("Layer"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("layerEffectName", _("Effect name"))
.AddParameter("layerEffectParameterName", _("Parameter name"))
.AddParameter("layerEffectParameterName", _("Property name"))
.AddParameter("string", _("New value"))
.MarkAsAdvanced();
extension
.AddAction(
"SetLayerEffectBooleanParameter",
_("Effect parameter (enable or disable)"),
_("Enable or disable a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (enable or disable)"),
_("Enable or disable a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Enable _PARAM3_ for effect _PARAM2_ of layer _PARAM1_: _PARAM4_"),
_("Effects"),
@@ -494,8 +494,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
.AddParameter("layer", _("Layer"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("layerEffectName", _("Effect name"))
.AddParameter("layerEffectParameterName", _("Parameter name"))
.AddParameter("yesorno", _("Enable this parameter"))
.AddParameter("layerEffectParameterName", _("Property name"))
.AddParameter("yesorno", _("Enable this property"))
.MarkAsAdvanced();
extension

View File

@@ -59,7 +59,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
"Name",
_("Animation (by name)"),
_("the animation played by the object using the name of the "
"animation."),
"animation"),
_("the animation"),
_("Animations and images"),
"res/actions/animation24.png")
@@ -72,7 +72,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
aut.AddScopedAction("PauseAnimation",
_("Pause the animation"),
_("Pause the animation of the object"),
_("Pause the animation of the object."),
_("Pause the animation of _PARAM0_"),
_("Animations and images"),
"res/actions/animation24.png",
@@ -83,7 +83,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
aut.AddScopedAction("PlayAnimation",
_("Resume the animation"),
_("Resume the animation of the object"),
_("Resume the animation of the object."),
_("Resume the animation of _PARAM0_"),
_("Animations and images"),
"res/actions/animation24.png",

View File

@@ -52,9 +52,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
.MarkAsSimple();
aut.AddScopedAction("SetEffectDoubleParameter",
_("Effect parameter (number)"),
_("Change the value of a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (number)"),
_("Change the value of a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of _PARAM0_"),
_("Effects"),
@@ -63,15 +63,15 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("expression", _("New value"))
.MarkAsSimple();
aut.AddScopedAction("SetEffectStringParameter",
_("Effect parameter (string)"),
_("Change the value (string) of a parameter of an effect.") +
_("Effect property (string)"),
_("Change the value (string) of a property of an effect.") +
"\n" +
_("You can find the parameter names (and change the effect "
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of _PARAM0_"),
_("Effects"),
@@ -80,14 +80,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("string", _("New value"))
.MarkAsSimple();
aut.AddScopedAction("SetEffectBooleanParameter",
_("Effect parameter (enable or disable)"),
_("Enable or disable a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (enable or disable)"),
_("Enable or disable a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Enable _PARAM3_ for effect _PARAM2_ of _PARAM0_: _PARAM4_"),
_("Effects"),
@@ -96,8 +96,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("yesorno", _("Enable?"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("yesorno", _("Enable this property"))
.MarkAsSimple();
aut.AddScopedCondition("IsEffectEnabled",

View File

@@ -14,7 +14,7 @@ using namespace std;
namespace gd {
void GD_CORE_API BuiltinExtensionsImplementer::ImplementsResizableExtension(
gd::PlatformExtension& extension) {
gd::PlatformExtension &extension) {
extension
.SetExtensionInformation("ResizableCapability",
_("Resizable capability"),
@@ -22,84 +22,86 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsResizableExtension(
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Size"))
.SetIcon("res/actions/scale24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
"res/actions/scale24_black.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"ResizableBehavior",
_("Resizable capability"),
"Resizable",
_("Change the object dimensions."),
"",
"res/actions/scale24_black.png",
"ResizableBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
gd::BehaviorMetadata &aut =
extension
.AddBehavior("ResizableBehavior",
_("Resizable capability"),
"Resizable",
_("Change the object dimensions."),
"",
"res/actions/scale24_black.png",
"ResizableBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
aut.AddScopedAction("SetWidth",
_("Width"),
_("Change the width of the object."),
_("the width"),
_("Size"),
"res/actions/scaleWidth24_black.png",
"res/actions/scaleWidth_black.png")
_("Width"),
_("Change the width of the object."),
_("the width"),
_("Size"),
"res/actions/scaleWidth24_black.png",
"res/actions/scaleWidth_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ResizableBehavior")
.UseStandardOperatorParameters("number",
ParameterOptions::MakeNewOptions().SetDescription(
_("Width")))
.UseStandardOperatorParameters(
"number",
ParameterOptions::MakeNewOptions().SetDescription(_("Width")))
.MarkAsAdvanced();
aut.AddScopedCondition("Width",
_("Width"),
_("Compare the width of the object."),
_("the width"),
_("Size"),
"res/conditions/scaleWidth24_black.png",
"res/conditions/scaleWidth_black.png")
_("Width"),
_("Compare the width of the object."),
_("the width"),
_("Size"),
"res/conditions/scaleWidth24_black.png",
"res/conditions/scaleWidth_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ResizableBehavior")
.UseStandardRelationalOperatorParameters(
"number", ParameterOptions::MakeNewOptions().SetDescription(
_("Width")))
"number",
ParameterOptions::MakeNewOptions().SetDescription(_("Width")))
.MarkAsAdvanced();
aut.AddScopedAction("SetHeight",
_("Height"),
_("Change the height of the object."),
_("the height"),
_("Size"),
"res/actions/scaleHeight24_black.png",
"res/actions/scaleHeight_black.png")
_("Height"),
_("Change the height of the object."),
_("the height"),
_("Size"),
"res/actions/scaleHeight24_black.png",
"res/actions/scaleHeight_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ResizableBehavior")
.UseStandardOperatorParameters("number",
ParameterOptions::MakeNewOptions().SetDescription(
_("Height")))
.UseStandardOperatorParameters(
"number",
ParameterOptions::MakeNewOptions().SetDescription(_("Height")))
.MarkAsAdvanced();
aut.AddScopedCondition("Height",
_("Height"),
_("Compare the height of the object."),
_("the height"),
_("Size"),
"res/conditions/scaleHeight24_black.png",
"res/conditions/scaleHeight_black.png")
_("Height"),
_("Compare the height of the object."),
_("the height"),
_("Size"),
"res/conditions/scaleHeight24_black.png",
"res/conditions/scaleHeight_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ResizableBehavior")
.UseStandardRelationalOperatorParameters(
"number", ParameterOptions::MakeNewOptions().SetDescription(
_("Height")))
"number",
ParameterOptions::MakeNewOptions().SetDescription(_("Height")))
.MarkAsAdvanced();
aut.AddScopedAction("SetSize",
_("Size"),
_("Change the size of an object."),
_("Change the size of _PARAM0_: set to _PARAM1_ x _PARAM2_"),
_("Size"),
"res/actions/scale24_black.png",
"res/actions/scale_black.png")
aut.AddScopedAction(
"SetSize",
_("Size"),
_("Change the size of an object."),
_("Change the size of _PARAM0_: set to _PARAM2_ x _PARAM3_"),
_("Size"),
"res/actions/scale24_black.png",
"res/actions/scale_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ResizableBehavior")
.AddParameter("expression", _("Width"))

View File

@@ -61,7 +61,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
_("the scale on X axis of the object (default scale is 1)"),
_("the scale on X axis"),
_("Scale"),
"res/actions/scale24_black.png")
"res/actions/scaleWidth24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ScalableBehavior")
.UseStandardParameters(
@@ -77,7 +77,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
_("the scale on Y axis of the object (default scale is 1)"),
_("the scale on Y axis"),
_("Scale"),
"res/actions/scale24_black.png")
"res/actions/scaleHeight24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ScalableBehavior")
.UseStandardParameters(

View File

@@ -35,6 +35,8 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
.AddInstructionOrExpressionGroupMetadata(_("Events and control flow"))
.SetIcon("res/conditions/toujours24_black.png");
// This condition is deprecated as this does not bring anything new
// and can be confusing or misleading for beginners.
extension
.AddCondition("Always",
_("Always"),
@@ -46,7 +48,8 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
"res/conditions/toujours_black.png")
.SetHelpPath("/all-features/advanced-conditions")
.AddCodeOnlyParameter("conditionInverted", "")
.MarkAsAdvanced();
.MarkAsAdvanced()
.SetHidden();
// Compatibility with GD <= 5.0.127
extension
@@ -114,8 +117,9 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
// Compatibility with GD <= 5.0.127
extension
.AddDuplicatedCondition(
"Egal", "BuiltinCommonInstructions::CompareNumbers", {.unscoped = true})
.AddDuplicatedCondition("Egal",
"BuiltinCommonInstructions::CompareNumbers",
{.unscoped = true})
.SetHidden();
// end of compatibility code
@@ -135,8 +139,9 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
// Compatibility with GD <= 5.0.127
extension
.AddDuplicatedCondition(
"StrEqual", "BuiltinCommonInstructions::CompareStrings", {.unscoped = true})
.AddDuplicatedCondition("StrEqual",
"BuiltinCommonInstructions::CompareStrings",
{.unscoped = true})
.SetHidden();
// end of compatibility code

View File

@@ -14,6 +14,7 @@
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/String.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
@@ -391,20 +392,19 @@ MetadataProvider::GetBehaviorAnyExpressionMetadata(const gd::Platform& platform,
}
const gd::ExpressionMetadata& MetadataProvider::GetFunctionCallMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& node) {
if (!node.behaviorName.empty()) {
gd::String behaviorType =
GetTypeOfBehavior(globalObjectsContainer, objectsContainer, node.behaviorName);
gd::String behaviorType =
objectsContainersList.GetTypeOfBehavior(node.behaviorName);
return MetadataProvider::GetBehaviorAnyExpressionMetadata(
platform, behaviorType, node.functionName);
}
else if (!node.objectName.empty()) {
gd::String objectType =
GetTypeOfObject(globalObjectsContainer, objectsContainer, node.objectName);
gd::String objectType =
objectsContainersList.GetTypeOfObject(node.objectName);
return MetadataProvider::GetObjectAnyExpressionMetadata(
platform, objectType, node.functionName);
}
@@ -412,10 +412,9 @@ const gd::ExpressionMetadata& MetadataProvider::GetFunctionCallMetadata(
return MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName);
}
const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& functionCall,
ExpressionNode& parameter) {
int parameterIndex = -1;
@@ -429,17 +428,15 @@ const gd::ExpressionMetadata& MetadataProvider::GetFunctionCallMetadata(
return nullptr;
}
return MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
platform,
objectsContainersList,
functionCall,
parameterIndex);
}
const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& functionCall,
int parameterIndex) {
// Search the parameter metadata index skipping invisible ones.
@@ -448,7 +445,7 @@ const gd::ExpressionMetadata& MetadataProvider::GetFunctionCallMetadata(
ExpressionParser2::WrittenParametersFirstIndex(
functionCall.objectName, functionCall.behaviorName);
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, globalObjectsContainer, objectsContainer, functionCall);
platform, objectsContainersList, functionCall);
if (IsBadExpressionMetadata(metadata)) {
return nullptr;

View File

@@ -15,6 +15,7 @@ class ExpressionMetadata;
class ExpressionMetadata;
class Platform;
class PlatformExtension;
class ObjectsContainersList;
struct FunctionCallNode;
struct ExpressionNode;
} // namespace gd
@@ -237,22 +238,19 @@ class GD_CORE_API MetadataProvider {
const gd::Platform& platform, gd::String objectType, gd::String exprType);
static const gd::ExpressionMetadata& GetFunctionCallMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& node);
static const gd::ParameterMetadata* GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& functionCall,
ExpressionNode& parameter);
static const gd::ParameterMetadata* GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& functionCall,
int parameterIndex);

View File

@@ -34,6 +34,11 @@ class GD_CORE_API ParameterMetadata {
*/
gd::ValueTypeMetadata &GetValueTypeMetadata() { return valueTypeMetadata; }
/**
* \brief Return the metadata of the parameter type.
*/
const gd::ValueTypeMetadata &GetValueTypeMetadata() const { return valueTypeMetadata; }
/**
* \brief Set the metadata of the parameter type.
*/

View File

@@ -8,6 +8,7 @@
#include "GDCore/Events/Expression.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
#include "InstructionMetadata.h"
@@ -15,6 +16,8 @@
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
namespace gd {
const ParameterMetadata ParameterMetadataTools::badParameterMetadata;
void ParameterMetadataTools::ParametersToObjectsContainer(
const gd::Project& project,
const std::vector<gd::ParameterMetadata>& parameters,
@@ -57,6 +60,55 @@ void ParameterMetadataTools::ParametersToObjectsContainer(
}
}
void ParameterMetadataTools::ForEachParameterWithPrefix(
const std::vector<const std::vector<gd::ParameterMetadata>*>&
parametersVectorsList,
const gd::String& prefix,
std::function<void(const gd::ParameterMetadata&)> cb) {
for (auto it = parametersVectorsList.rbegin();
it != parametersVectorsList.rend();
++it) {
const std::vector<gd::ParameterMetadata>* parametersVector = *it;
for (const auto& parameterMetadata: *parametersVector) {
if (parameterMetadata.GetName().find(prefix) == 0) cb(parameterMetadata);
}
}
}
bool ParameterMetadataTools::Has(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& parameterName) {
for (auto it = parametersVectorsList.rbegin();
it != parametersVectorsList.rend();
++it) {
const std::vector<gd::ParameterMetadata>* parametersVector = *it;
for (const auto& parameterMetadata: *parametersVector) {
if (parameterMetadata.GetName() == parameterName) return true;
}
}
return false;
}
const gd::ParameterMetadata& ParameterMetadataTools::Get(
const std::vector<const std::vector<gd::ParameterMetadata>*>&
parametersVectorsList,
const gd::String& parameterName) {
for (auto it = parametersVectorsList.rbegin();
it != parametersVectorsList.rend();
++it) {
const std::vector<gd::ParameterMetadata>* parametersVector = *it;
for (const auto& parameterMetadata: *parametersVector) {
if (parameterMetadata.GetName() == parameterName) return parameterMetadata;
}
}
return badParameterMetadata;
}
void ParameterMetadataTools::IterateOverParameters(
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
@@ -105,8 +157,7 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
void ParameterMetadataTools::IterateOverParametersWithIndex(
const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer, FunctionCallNode &node,
const gd::ObjectsContainersList &objectsContainersList, FunctionCallNode &node,
std::function<void(const gd::ParameterMetadata &parameterMetadata,
std::unique_ptr<gd::ExpressionNode> &parameterNode,
size_t parameterIndex, const gd::String &lastObjectName)>
@@ -117,8 +168,7 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
const gd::ExpressionMetadata &metadata =
isObjectFunction ? MetadataProvider::GetObjectAnyExpressionMetadata(
platform,
GetTypeOfObject(globalObjectsContainer,
objectsContainer, node.objectName),
objectsContainersList.GetTypeOfObject(node.objectName),
node.functionName)
: MetadataProvider::GetAnyExpressionMetadata(
platform, node.functionName);

View File

@@ -3,9 +3,8 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#ifndef ParameterMetadataTools_H
#define ParameterMetadataTools_H
#pragma once
#include <functional>
#include <vector>
#include <memory>
#include "GDCore/String.h"
@@ -13,6 +12,7 @@ namespace gd {
class Platform;
class Project;
class ObjectsContainer;
class ObjectsContainersList;
class ParameterMetadata;
class Expression;
struct FunctionCallNode;
@@ -27,6 +27,19 @@ class GD_CORE_API ParameterMetadataTools {
const std::vector<gd::ParameterMetadata>& parameters,
gd::ObjectsContainer& outputObjectsContainer);
static void ForEachParameterWithPrefix(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& prefix,
std::function<void(const gd::ParameterMetadata&)> cb);
static bool Has(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& parameterName);
static const gd::ParameterMetadata& Get(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& parameterName);
/**
* Iterate over a list of parameters and their values.
* Callback function is called with the parameter metadata, its value
@@ -59,8 +72,7 @@ class GD_CORE_API ParameterMetadataTools {
*/
static void IterateOverParametersWithIndex(
const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer, FunctionCallNode &node,
const gd::ObjectsContainersList &objectsContainersList, FunctionCallNode &node,
std::function<void(const gd::ParameterMetadata &parameterMetadata,
std::unique_ptr<gd::ExpressionNode> &parameterNode,
size_t parameterIndex,
@@ -74,8 +86,8 @@ class GD_CORE_API ParameterMetadataTools {
static size_t GetObjectParameterIndexFor(
const std::vector<gd::ParameterMetadata>& parametersMetadata,
size_t parameterIndex);
private:
static const gd::ParameterMetadata badParameterMetadata;
};
} // namespace gd
#endif // ParameterMetadataTools_H
#endif

View File

@@ -121,6 +121,13 @@ class GD_CORE_API ValueTypeMetadata {
return gd::ValueTypeMetadata::IsTypeExpression("string", name);
}
/**
* \brief Return true if the type is a boolean.
*/
bool IsBoolean() const {
return gd::ValueTypeMetadata::IsTypeExpression("boolean", name);
}
/**
* \brief Return true if the type of the parameter is a number.
* \note If you had a new type of parameter, also add it in the IDE (
@@ -131,6 +138,24 @@ class GD_CORE_API ValueTypeMetadata {
return gd::ValueTypeMetadata::IsTypeExpression("variable", name);
}
/**
* \brief Return true if the type is a variable but from a specific scope
* (scene, project or object). In new code, prefer to use the more generic "variable"
* parameter (which accepts any variable coming from an object or from containers in the scope).
*/
bool IsLegacyPreScopedVariable() const {
return gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(name);
}
/**
* \brief Return true if the type is a variable but from a specific scope
* (scene, project or object). In new code, prefer to use the more generic "variable"
* parameter (which accepts any variable coming from an object or from containers in the scope).
*/
static bool IsTypeLegacyPreScopedVariable(const gd::String &type) {
return type == "scenevar" || type == "globalvar" || type == "objectvar";
}
/**
* \brief Return true if the type is representing one object
* (or more, i.e: an object group).
@@ -175,8 +200,13 @@ class GD_CORE_API ValueTypeMetadata {
parameterType == "externalLayoutName" ||
parameterType == "leaderboardId" ||
parameterType == "identifier";
} else if (type == "boolean") {
return parameterType == "yesorno" || parameterType == "trueorfalse";
} else if (type == "variable") {
return parameterType == "objectvar" || parameterType == "globalvar" ||
return
parameterType == "variable" || // Any variable.
// Old, "pre-scoped" variables:
parameterType == "objectvar" || parameterType == "globalvar" ||
parameterType == "scenevar";
} else if (type == "resource") {
return parameterType == "fontResource" ||
@@ -196,7 +226,7 @@ class GD_CORE_API ValueTypeMetadata {
* \brief Return the expression type from the parameter type.
* Declinations of "number" and "string" types (like "forceMultiplier" or
* "sceneName") are replaced by "number" and "string".
*
*
* \note It only maps string and number types.
*/
static const gd::String &GetExpressionPrimitiveValueType(const gd::String &parameterType);
@@ -205,7 +235,7 @@ class GD_CORE_API ValueTypeMetadata {
* \brief Return the primitive type from the parameter type.
* Declinations of "number" and "string" types (like "forceMultiplier" or
* "sceneName") are replaced by "number" and "string".
*
*
* \note It also maps variable and boolean types.
*/
static const gd::String &GetPrimitiveValueType(const gd::String &parameterType);

View File

@@ -11,6 +11,8 @@
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/Events/Expression.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/String.h"
using namespace std;
@@ -47,7 +49,14 @@ bool ArbitraryEventsWorker::VisitEvent(gd::BaseEvent& event) {
for (std::size_t j = 0; j < actionsVectors.size(); ++j)
VisitInstructionList(*actionsVectors[j], false);
return false;
auto allExpressionsWithMetadata = event.GetAllExpressionsWithMetadata();
for (auto& expressionAndMetadata : allExpressionsWithMetadata) {
shouldDelete |= VisitEventExpression(
*expressionAndMetadata.first, expressionAndMetadata.second);
}
return shouldDelete;
}
bool ArbitraryEventsWorker::VisitLinkEvent(gd::LinkEvent& linkEvent) {
@@ -75,6 +84,11 @@ bool ArbitraryEventsWorker::VisitInstruction(gd::Instruction& instruction,
return DoVisitInstruction(instruction, isCondition);
}
bool ArbitraryEventsWorker::VisitEventExpression(gd::Expression& expression,
const gd::ParameterMetadata& metadata) {
return DoVisitEventExpression(expression, metadata);
}
ArbitraryEventsWorkerWithContext::~ArbitraryEventsWorkerWithContext() {}
@@ -141,6 +155,12 @@ void ReadOnlyArbitraryEventsWorker::VisitInstruction(const gd::Instruction& inst
DoVisitInstruction(instruction, isCondition);
}
void ReadOnlyArbitraryEventsWorker::VisitEventExpression(const gd::Expression& expression,
const gd::ParameterMetadata& metadata) {
DoVisitEventExpression(expression, metadata);
}
void ReadOnlyArbitraryEventsWorker::StopAnyEventIteration() {
shouldStopIteration = true;
}

View File

@@ -10,6 +10,7 @@
#include <vector>
#include "GDCore/Events/InstructionsList.h"
#include "GDCore/Events/EventVisitor.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
namespace gd {
class Instruction;
@@ -17,6 +18,8 @@ class BaseEvent;
class LinkEvent;
class EventsList;
class ObjectsContainer;
class Expression;
class ParameterMetadata;
} // namespace gd
namespace gd {
@@ -47,6 +50,7 @@ class GD_CORE_API ArbitraryEventsWorker : private EventVisitor {
void VisitInstructionList(gd::InstructionsList& instructions,
bool areConditions);
bool VisitInstruction(gd::Instruction& instruction, bool isCondition);
bool VisitEventExpression(gd::Expression& expression, const gd::ParameterMetadata& metadata);
/**
* Called to do some work on an event list.
@@ -62,9 +66,9 @@ class GD_CORE_API ArbitraryEventsWorker : private EventVisitor {
/**
* Called to do some work on a link event.
*
*
* Note that DoVisitEvent is also called with this event.
*
*
* \return true if the event must be deleted from the events list, false
* otherwise (default).
*/
@@ -85,6 +89,16 @@ class GD_CORE_API ArbitraryEventsWorker : private EventVisitor {
bool isCondition) {
return false;
};
/**
* Called to do some work on an expression of an event.
* \return true if the event must be deleted from the list, false
* otherwise (default).
*/
virtual bool DoVisitEventExpression(gd::Expression& expression,
const gd::ParameterMetadata& metadata) {
return false;
}
};
/**
@@ -99,8 +113,7 @@ class GD_CORE_API ArbitraryEventsWorkerWithContext
: public ArbitraryEventsWorker {
public:
ArbitraryEventsWorkerWithContext()
: currentGlobalObjectsContainer(nullptr),
currentObjectsContainer(nullptr){};
: projectScopedContainers(nullptr){};
virtual ~ArbitraryEventsWorkerWithContext();
/**
@@ -108,30 +121,27 @@ class GD_CORE_API ArbitraryEventsWorkerWithContext
* giving the objects container on which the events are applying to.
*/
void Launch(gd::EventsList& events,
const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_) {
currentGlobalObjectsContainer = &globalObjectsContainer_;
currentObjectsContainer = &objectsContainer_;
const gd::ProjectScopedContainers& projectScopedContainers_) {
projectScopedContainers = &projectScopedContainers_;
ArbitraryEventsWorker::Launch(events);
};
void Launch(gd::EventsList& events) = delete;
protected:
const gd::ObjectsContainer& GetGlobalObjectsContainer() {
const gd::ProjectScopedContainers& GetProjectScopedContainers() {
// Pointers are guaranteed to be not nullptr after
// Launch was called.
return *currentGlobalObjectsContainer;
return *projectScopedContainers;
};
const gd::ObjectsContainer& GetObjectsContainer() {
const gd::ObjectsContainersList& GetObjectsContainersList() {
// Pointers are guaranteed to be not nullptr after
// Launch was called.
return *currentObjectsContainer;
return projectScopedContainers->GetObjectsContainersList();
};
private:
const gd::ObjectsContainer* currentGlobalObjectsContainer;
const gd::ObjectsContainer* currentObjectsContainer;
const gd::ProjectScopedContainers* projectScopedContainers;
};
/**
@@ -162,6 +172,7 @@ protected:
void VisitInstructionList(const gd::InstructionsList& instructions,
bool areConditions);
void VisitInstruction(const gd::Instruction& instruction, bool isCondition);
void VisitEventExpression(const gd::Expression& expression, const gd::ParameterMetadata& metadata);
/**
* Called to do some work on an event list.
@@ -175,7 +186,7 @@ protected:
/**
* Called to do some work on a link event.
*
*
* Note that DoVisitEvent is also called with this event.
*/
virtual void DoVisitLinkEvent(const gd::LinkEvent& linkEvent) {};
@@ -192,6 +203,13 @@ protected:
virtual void DoVisitInstruction(const gd::Instruction& instruction,
bool isCondition) {};
/**
* Called to do some work on an expression of an event.
*/
virtual void DoVisitEventExpression(const gd::Expression& expression,
const gd::ParameterMetadata& metadata) {
}
bool shouldStopIteration;
};
@@ -207,8 +225,7 @@ class GD_CORE_API ReadOnlyArbitraryEventsWorkerWithContext
: public ReadOnlyArbitraryEventsWorker {
public:
ReadOnlyArbitraryEventsWorkerWithContext()
: currentGlobalObjectsContainer(nullptr),
currentObjectsContainer(nullptr){};
: projectScopedContainers(nullptr){};
virtual ~ReadOnlyArbitraryEventsWorkerWithContext();
/**
@@ -216,30 +233,22 @@ class GD_CORE_API ReadOnlyArbitraryEventsWorkerWithContext
* giving the objects container on which the events are applying to.
*/
void Launch(const gd::EventsList& events,
const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_) {
currentGlobalObjectsContainer = &globalObjectsContainer_;
currentObjectsContainer = &objectsContainer_;
const gd::ProjectScopedContainers& projectScopedContainers_) {
projectScopedContainers = &projectScopedContainers_;
ReadOnlyArbitraryEventsWorker::Launch(events);
};
void Launch(gd::EventsList& events) = delete;
protected:
const gd::ObjectsContainer& GetGlobalObjectsContainer() {
const gd::ProjectScopedContainers& GetProjectScopedContainers() {
// Pointers are guaranteed to be not nullptr after
// Launch was called.
return *currentGlobalObjectsContainer;
};
const gd::ObjectsContainer& GetObjectsContainer() {
// Pointers are guaranteed to be not nullptr after
// Launch was called.
return *currentObjectsContainer;
return *projectScopedContainers;
};
private:
const gd::ObjectsContainer* currentGlobalObjectsContainer;
const gd::ObjectsContainer* currentObjectsContainer;
const gd::ProjectScopedContainers* projectScopedContainers;
};
} // namespace gd

View File

@@ -4,7 +4,6 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/WholeProjectRefactorer.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"

View File

@@ -4,7 +4,6 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/WholeProjectRefactorer.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"

View File

@@ -31,14 +31,10 @@ namespace gd {
class GD_CORE_API ExpressionBehaviorRenamer
: public ExpressionParser2NodeWorker {
public:
ExpressionBehaviorRenamer(const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_,
const gd::String& objectName_,
ExpressionBehaviorRenamer(const gd::String& objectName_,
const gd::String& oldBehaviorName_,
const gd::String& newBehaviorName_)
: hasDoneRenaming(false),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
objectName(objectName_),
oldBehaviorName(oldBehaviorName_),
newBehaviorName(newBehaviorName_){};
@@ -97,8 +93,6 @@ class GD_CORE_API ExpressionBehaviorRenamer
private:
bool hasDoneRenaming;
const gd::ObjectsContainer& globalObjectsContainer;
const gd::ObjectsContainer& objectsContainer;
const gd::String& objectName; // The object name for which the behavior
// must be replaced.
const gd::String& oldBehaviorName;
@@ -132,9 +126,7 @@ bool EventsBehaviorRenamer::DoVisitInstruction(gd::Instruction& instruction,
} else {
auto node = parameterValue.GetRootNode();
if (node) {
ExpressionBehaviorRenamer renamer(GetGlobalObjectsContainer(),
GetObjectsContainer(),
objectName,
ExpressionBehaviorRenamer renamer(objectName,
oldBehaviorName,
newBehaviorName);
node->Visit(renamer);

View File

@@ -19,6 +19,8 @@
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
namespace gd {
@@ -33,13 +35,11 @@ class GD_CORE_API ExpressionObjectsAnalyzer
public:
ExpressionObjectsAnalyzer(
const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String &rootType_,
EventsContext& context_) :
platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
context(context_){};
virtual ~ExpressionObjectsAnalyzer(){};
@@ -58,6 +58,25 @@ 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);
if (gd::ParameterMetadata::IsExpression("variable", type)) {
// Nothing to do (this can't reference an object)
} else {
projectScopedContainers.MatchIdentifierWithName<void>(node.name, [&]() {
// This is an object variable.
context.AddObjectName(projectScopedContainers, node.name);
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
@@ -69,26 +88,41 @@ class GD_CORE_API ExpressionObjectsAnalyzer
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers.GetObjectsContainersList(), rootType, node);
if (gd::ParameterMetadata::IsObject(type)) {
context.AddObjectName(node.identifierName);
context.AddObjectName(projectScopedContainers, node.identifierName);
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
// Nothing to do (identifier is a variable but not an object).
} else {
projectScopedContainers.MatchIdentifierWithName<void>(node.identifierName, [&]() {
// This is an object variable.
context.AddObjectName(projectScopedContainers, node.identifierName);
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (!node.objectName.empty()) {
context.AddObjectName(node.objectName);
context.AddObjectName(projectScopedContainers, node.objectName);
if (!node.behaviorFunctionName.empty()) {
context.AddBehaviorName(node.objectName, node.objectFunctionOrBehaviorName);
context.AddBehaviorName(projectScopedContainers, node.objectName, node.objectFunctionOrBehaviorName);
}
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (!node.objectName.empty()) {
context.AddObjectName(node.objectName);
context.AddObjectName(projectScopedContainers, node.objectName);
if (!node.behaviorName.empty()) {
context.AddBehaviorName(node.objectName, node.behaviorName);
context.AddBehaviorName(projectScopedContainers, node.objectName, node.behaviorName);
}
}
for (auto& parameter : node.parameters) {
@@ -99,8 +133,7 @@ class GD_CORE_API ExpressionObjectsAnalyzer
private:
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
EventsContext& context;
@@ -121,8 +154,7 @@ bool EventsContextAnalyzer::DoVisitInstruction(gd::Instruction& instruction,
const gd::Expression& parameterValue,
const gd::String& lastObjectName) {
AnalyzeParameter(platform,
project,
layout,
GetProjectScopedContainers(),
parameterMetadata,
parameterValue,
context,
@@ -134,8 +166,7 @@ bool EventsContextAnalyzer::DoVisitInstruction(gd::Instruction& instruction,
void EventsContextAnalyzer::AnalyzeParameter(
const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
const gd::ProjectScopedContainers& projectScopedContainers,
const gd::ParameterMetadata& metadata,
const gd::Expression& parameter,
EventsContext& context,
@@ -143,59 +174,39 @@ void EventsContextAnalyzer::AnalyzeParameter(
const auto& value = parameter.GetPlainString();
const auto& type = metadata.GetType();
if (ParameterMetadata::IsObject(type)) {
context.AddObjectName(value);
context.AddObjectName(projectScopedContainers, value);
} else if (ParameterMetadata::IsExpression("number", type)) {
auto node = parameter.GetRootNode();
ExpressionObjectsAnalyzer analyzer(platform, project, layout, "number", context);
ExpressionObjectsAnalyzer analyzer(platform, projectScopedContainers, "number", context);
node->Visit(analyzer);
} else if (ParameterMetadata::IsExpression("string", type)) {
auto node = parameter.GetRootNode();
ExpressionObjectsAnalyzer analyzer(platform, project, layout, "string", context);
ExpressionObjectsAnalyzer analyzer(platform, projectScopedContainers, "string", context);
node->Visit(analyzer);
} else if (ParameterMetadata::IsBehavior(type)) {
context.AddBehaviorName(lastObjectName, value);
context.AddBehaviorName(projectScopedContainers, lastObjectName, value);
}
}
void EventsContext::AddObjectName(const gd::String& objectOrGroupName) {
for (auto& realObjectName : ExpandObjectsName(objectOrGroupName)) {
void EventsContext::AddObjectName(const gd::ProjectScopedContainers& projectScopedContainers,
const gd::String& objectOrGroupName) {
auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
for (auto& realObjectName : objectsContainersList.ExpandObjectName(objectOrGroupName)) {
objectNames.insert(realObjectName);
}
referencedObjectOrGroupNames.insert(objectOrGroupName);
}
void EventsContext::AddBehaviorName(const gd::String& objectOrGroupName,
void EventsContext::AddBehaviorName(const gd::ProjectScopedContainers& projectScopedContainers,
const gd::String& objectOrGroupName,
const gd::String& behaviorName) {
for (auto& realObjectName : ExpandObjectsName(objectOrGroupName)) {
auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
for (auto& realObjectName : objectsContainersList.ExpandObjectName(objectOrGroupName)) {
objectOrGroupBehaviorNames[realObjectName].insert(behaviorName);
}
objectOrGroupBehaviorNames[objectOrGroupName].insert(behaviorName);
}
std::vector<gd::String> EventsContext::ExpandObjectsName(
const gd::String& objectName) {
// Note: this logic is duplicated in EventsCodeGenerator::ExpandObjectsName
std::vector<gd::String> realObjects;
if (project.GetObjectGroups().Has(objectName))
realObjects =
project.GetObjectGroups().Get(objectName).GetAllObjectsNames();
else if (layout.GetObjectGroups().Has(objectName))
realObjects = layout.GetObjectGroups().Get(objectName).GetAllObjectsNames();
else
realObjects.push_back(objectName);
// Ensure that all returned objects actually exists.
for (std::size_t i = 0; i < realObjects.size();) {
if (!layout.HasObjectNamed(realObjects[i]) &&
!project.HasObjectNamed(realObjects[i]))
realObjects.erase(realObjects.begin() + i);
else
++i;
}
return realObjects;
}
} // namespace gd

View File

@@ -15,6 +15,7 @@ namespace gd {
class BaseEvent;
class Platform;
class ObjectsContainer;
class ObjectsContainersList;
class Project;
class Layout;
class EventsList;
@@ -29,12 +30,13 @@ namespace gd {
*/
class GD_CORE_API EventsContext {
public:
EventsContext(gd::ObjectsContainer& project_, gd::ObjectsContainer& layout_)
: project(project_), layout(layout_){};
EventsContext(){};
virtual ~EventsContext(){};
void AddObjectName(const gd::String& objectOrGroupName);
void AddBehaviorName(const gd::String& objectOrGroupName,
void AddObjectName(const gd::ProjectScopedContainers& projectScopedContainers,
const gd::String& objectOrGroupName);
void AddBehaviorName(const gd::ProjectScopedContainers& projectScopedContainers,
const gd::String& objectOrGroupName,
const gd::String& behaviorName);
/**
@@ -59,13 +61,9 @@ class GD_CORE_API EventsContext {
}
private:
std::vector<gd::String> ExpandObjectsName(const gd::String& objectOrGroupName);
std::set<gd::String> referencedObjectOrGroupNames;
std::set<gd::String> objectNames;
std::map<gd::String, std::set<gd::String>> objectOrGroupBehaviorNames;
gd::ObjectsContainer& project;
gd::ObjectsContainer& layout;
};
/**
@@ -73,15 +71,10 @@ class GD_CORE_API EventsContext {
*
* \ingroup IDE
*/
class GD_CORE_API EventsContextAnalyzer : public ArbitraryEventsWorker {
class GD_CORE_API EventsContextAnalyzer : public ArbitraryEventsWorkerWithContext {
public:
EventsContextAnalyzer(const gd::Platform& platform_,
gd::ObjectsContainer& project_,
gd::ObjectsContainer& layout_)
: platform(platform_),
project(project_),
layout(layout_),
context(project, layout){};
EventsContextAnalyzer(const gd::Platform& platform_)
: platform(platform_) {};
virtual ~EventsContextAnalyzer(){};
/**
@@ -90,8 +83,7 @@ class GD_CORE_API EventsContextAnalyzer : public ArbitraryEventsWorker {
const EventsContext& GetEventsContext() { return context; }
static void AnalyzeParameter(const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
const gd::ProjectScopedContainers& projectScopedContainers,
const gd::ParameterMetadata& metadata,
const gd::Expression& parameter,
EventsContext& context,
@@ -102,8 +94,6 @@ class GD_CORE_API EventsContextAnalyzer : public ArbitraryEventsWorker {
bool isCondition);
const gd::Platform& platform;
gd::ObjectsContainer& project;
gd::ObjectsContainer& layout;
EventsContext context;
};

View File

@@ -34,14 +34,12 @@ class GD_CORE_API IdentifierFinderExpressionNodeWorker
public:
IdentifierFinderExpressionNodeWorker(std::set<gd::String>& results_,
const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String& identifierType_,
const gd::String& objectName_ = "")
: results(results_),
platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
identifierType(identifierType_),
objectName(objectName_){};
virtual ~IdentifierFinderExpressionNodeWorker(){};
@@ -79,14 +77,14 @@ class GD_CORE_API IdentifierFinderExpressionNodeWorker
const gd::ExpressionMetadata &metadata = isObjectFunction ?
MetadataProvider::GetObjectAnyExpressionMetadata(
platform,
GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName),
projectScopedContainers.GetObjectsContainersList().GetTypeOfObject(objectName),
node.functionName):
MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
return;
}
size_t parameterIndex = 0;
for (size_t metadataIndex = (isObjectFunction ? 1 : 0); metadataIndex < metadata.parameters.size()
&& parameterIndex < node.parameters.size(); ++metadataIndex) {
@@ -111,8 +109,7 @@ class GD_CORE_API IdentifierFinderExpressionNodeWorker
private:
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
std::set<gd::String>& results; ///< Reference to the std::set where argument
///< values must be stored.
@@ -166,8 +163,7 @@ class GD_CORE_API IdentifierFinderEventWorker
IdentifierFinderExpressionNodeWorker searcher(
results,
platform,
GetGlobalObjectsContainer(),
GetObjectsContainer(),
GetProjectScopedContainers(),
identifierType,
objectName);
node->Visit(searcher);
@@ -227,7 +223,8 @@ void EventsIdentifiersFinder::FindArgumentsInEventsAndDependencies(
platform,
identifierType,
objectName);
eventWorker.Launch(layout.GetEvents(), project, layout);
eventWorker.Launch(layout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout);
dependenciesAnalyzer.Analyze();
@@ -238,7 +235,8 @@ void EventsIdentifiersFinder::FindArgumentsInEventsAndDependencies(
platform,
identifierType,
objectName);
eventWorker.Launch(externalEvents.GetEvents(), project, layout);
eventWorker.Launch(externalEvents.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
}
for (const gd::String& sceneName : dependenciesAnalyzer.GetScenesDependencies()) {
const gd::Layout& dependencyLayout = project.GetLayout(sceneName);
@@ -247,7 +245,8 @@ void EventsIdentifiersFinder::FindArgumentsInEventsAndDependencies(
platform,
identifierType,
objectName);
eventWorker.Launch(dependencyLayout.GetEvents(), project, dependencyLayout);
eventWorker.Launch(dependencyLayout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, dependencyLayout));
}
}

View File

@@ -0,0 +1,277 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/EventsPropertyReplacer.h"
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace gd {
/**
* \brief Go through the nodes and rename properties,
* or signal if the instruction must be renamed if a removed property is used.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionPropertyReplacer
: public ExpressionParser2NodeWorker {
public:
ExpressionPropertyReplacer(
const gd::Platform& platform_,
const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::PropertiesContainer& targetPropertiesContainer_,
const std::unordered_map<gd::String, gd::String>& oldToNewPropertyNames_,
const std::unordered_set<gd::String>& removedPropertyNames_)
: hasDoneRenaming(false),
removedPropertyUsed(false),
platform(platform_),
projectScopedContainers(projectScopedContainers_),
targetPropertiesContainer(targetPropertiesContainer_),
oldToNewPropertyNames(oldToNewPropertyNames_),
removedPropertyNames(removedPropertyNames_){};
virtual ~ExpressionPropertyReplacer(){};
bool HasDoneRenaming() const { return hasDoneRenaming; }
bool IsRemovedPropertyUsed() const { return removedPropertyUsed; }
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
node.expression->Visit(*this);
}
void OnVisitOperatorNode(OperatorNode& node) override {
node.leftHandSide->Visit(*this);
node.rightHandSide->Visit(*this);
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
node.factor->Visit(*this);
}
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
auto& propertiesContainersList =
projectScopedContainers.GetPropertiesContainersList();
// The node represents a variable or an object name on which a variable
// will be accessed, or a property with a child.
// Match the potential *new* name of the property, because refactorings are
// done after changes in the variables container.
projectScopedContainers.MatchIdentifierWithName<void>(
GetPotentialNewName(node.name),
[&]() {
// Do nothing, it's an object variable.
if (node.child) node.child->Visit(*this);
}, [&]() {
// Do nothing, it's a variable.
if (node.child) node.child->Visit(*this);
}, [&]() {
// This is a property, check if it's coming from the target container with
// properties to replace.
if (propertiesContainersList.HasPropertiesContainer(
targetPropertiesContainer)) {
// The node represents a property, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemovePropertyOfTargetPropertyContainer(node.name);
}
if (node.child) node.child->Visit(*this);
}, [&]() {
// Do nothing, it's a parameter.
if (node.child) node.child->Visit(*this);
}, [&]() {
// This is something else - potentially a deleted property.
// Check if it's coming from the target container with
// properties to replace.
if (propertiesContainersList.HasPropertiesContainer(
targetPropertiesContainer)) {
// The node represents a property, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemovePropertyOfTargetPropertyContainer(node.name);
}
if (node.child) node.child->Visit(*this);
});
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
node.expression->Visit(*this);
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto& propertiesContainersList =
projectScopedContainers.GetPropertiesContainersList();
// Match the potential *new* name of the property, because refactorings are
// done after changes in the variables container.
projectScopedContainers.MatchIdentifierWithName<void>(
GetPotentialNewName(node.identifierName),
[&]() {
// Do nothing, it's an object variable.
}, [&]() {
// Do nothing, it's a variable.
}, [&]() {
// This is a property, check if it's coming from the target container with
// properties to replace.
if (propertiesContainersList.HasPropertiesContainer(
targetPropertiesContainer)) {
// The node represents a property, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemovePropertyOfTargetPropertyContainer(node.identifierName);
}
}, [&]() {
// Do nothing, it's a parameter.
}, [&]() {
// This is something else - potentially a deleted property.
// Check if it's coming from the target container with
// properties to replace.
if (propertiesContainersList.HasPropertiesContainer(
targetPropertiesContainer)) {
// The node represents a property, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemovePropertyOfTargetPropertyContainer(node.identifierName);
}
});
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
for (auto& parameter : node.parameters) {
parameter->Visit(*this);
}
}
void OnVisitEmptyNode(EmptyNode& node) override {}
private:
bool hasDoneRenaming;
bool removedPropertyUsed;
const gd::String& GetPotentialNewName(const gd::String& oldName) {
return oldToNewPropertyNames.count(oldName) >= 1
? oldToNewPropertyNames.find(oldName)->second
: oldName;
}
bool RenameOrRemovePropertyOfTargetPropertyContainer(
gd::String& propertyName) {
if (oldToNewPropertyNames.count(propertyName) >= 1) {
propertyName = oldToNewPropertyNames.find(propertyName)->second;
hasDoneRenaming = true;
return true;
} else if (removedPropertyNames.count(propertyName) >= 1) {
removedPropertyUsed = true;
return true;
}
return false; // Nothing was changed or done.
}
// Scope:
const gd::Platform& platform;
const gd::ProjectScopedContainers& projectScopedContainers;
// Renaming or removing to do:
const gd::PropertiesContainer& targetPropertiesContainer;
const std::unordered_map<gd::String, gd::String>& oldToNewPropertyNames;
const std::unordered_set<gd::String>& removedPropertyNames;
gd::String objectNameToUseForVariableAccessor;
};
bool EventsPropertyReplacer::DoVisitInstruction(gd::Instruction& instruction,
bool isCondition) {
const auto& metadata = isCondition
? gd::MetadataProvider::GetConditionMetadata(
platform, instruction.GetType())
: gd::MetadataProvider::GetActionMetadata(
platform, instruction.GetType());
bool shouldDeleteInstruction = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
const gd::String& type = parameterMetadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) &&
!gd::ParameterMetadata::IsExpression("number", type) &&
!gd::ParameterMetadata::IsExpression("string", type))
return; // Not an expression that can contain properties.
auto node = parameterValue.GetRootNode();
if (node) {
ExpressionPropertyReplacer renamer(platform,
GetProjectScopedContainers(),
targetPropertiesContainer,
oldToNewPropertyNames,
removedPropertyNames);
node->Visit(renamer);
if (renamer.IsRemovedPropertyUsed()) {
shouldDeleteInstruction = true;
} else if (renamer.HasDoneRenaming()) {
instruction.SetParameter(
parameterIndex, ExpressionParser2NodePrinter::PrintNode(*node));
}
}
});
return shouldDeleteInstruction;
}
bool EventsPropertyReplacer::DoVisitEventExpression(
gd::Expression& expression, const gd::ParameterMetadata& metadata) {
const gd::String& type = metadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) &&
!gd::ParameterMetadata::IsExpression("number", type) &&
!gd::ParameterMetadata::IsExpression("string", type))
return false; // Not an expression that can contain properties.
auto node = expression.GetRootNode();
if (node) {
ExpressionPropertyReplacer renamer(platform,
GetProjectScopedContainers(),
targetPropertiesContainer,
oldToNewPropertyNames,
removedPropertyNames);
node->Visit(renamer);
if (renamer.IsRemovedPropertyUsed()) {
return true;
} else if (renamer.HasDoneRenaming()) {
expression = ExpressionParser2NodePrinter::PrintNode(*node);
}
}
return false;
}
EventsPropertyReplacer::~EventsPropertyReplacer() {}
} // namespace gd

View File

@@ -0,0 +1,56 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/String.h"
namespace gd {
class BaseEvent;
class PropertiesContainer;
class EventsList;
class Platform;
} // namespace gd
namespace gd {
/**
* \brief Replace in expressions and in parameters of actions or conditions,
* references to the name of a property by another.
*
* \ingroup IDE
*/
class GD_CORE_API EventsPropertyReplacer
: public ArbitraryEventsWorkerWithContext {
public:
EventsPropertyReplacer(
const gd::Platform &platform_,
const gd::PropertiesContainer &targetPropertiesContainer_,
const std::unordered_map<gd::String, gd::String> &oldToNewPropertyNames_,
const std::unordered_set<gd::String> &removedPropertyNames_)
: platform(platform_),
targetPropertiesContainer(targetPropertiesContainer_),
oldToNewPropertyNames(oldToNewPropertyNames_),
removedPropertyNames(removedPropertyNames_){};
virtual ~EventsPropertyReplacer();
private:
bool DoVisitInstruction(gd::Instruction &instruction,
bool isCondition) override;
bool DoVisitEventExpression(gd::Expression &expression,
const gd::ParameterMetadata &metadata) override;
const gd::Platform &platform;
const gd::PropertiesContainer &targetPropertiesContainer;
const std::unordered_map<gd::String, gd::String> &oldToNewPropertyNames;
const std::unordered_set<gd::String> &removedPropertyNames;
};
} // namespace gd

View File

@@ -20,6 +20,7 @@
#include "GDCore/IDE/Events/InstructionSentenceFormatter.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
using namespace std;
@@ -36,14 +37,12 @@ const gd::String EventsRefactorer::searchIgnoredCharacters = ";:,#()";
class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
public:
ExpressionObjectRenamer(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::String &rootType_,
const gd::String& objectName_,
const gd::String& objectNewName_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
hasDoneRenaming(false),
objectName(objectName_),
@@ -51,14 +50,13 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
virtual ~ExpressionObjectRenamer(){};
static bool Rename(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ProjectScopedContainers &projectScopedContainers,
const gd::String &rootType,
gd::ExpressionNode& node,
const gd::String& objectName,
const gd::String& objectNewName) {
if (gd::ExpressionValidator::HasNoErrors(platform, globalObjectsContainer, objectsContainer, rootType, node)) {
ExpressionObjectRenamer renamer(platform, globalObjectsContainer, objectsContainer, rootType, objectName, objectNewName);
if (gd::ExpressionValidator::HasNoErrors(platform, projectScopedContainers, rootType, node)) {
ExpressionObjectRenamer renamer(platform, projectScopedContainers, rootType, objectName, objectNewName);
node.Visit(renamer);
return renamer.HasDoneRenaming();
@@ -83,6 +81,29 @@ 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);
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
} else {
if (node.name == objectName) {
projectScopedContainers.MatchIdentifierWithName<void>(node.name, [&]() {
// This is an object variable.
hasDoneRenaming = true;
node.name = objectNewName;
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
}
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
@@ -94,11 +115,29 @@ 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, globalObjectsContainer, objectsContainer, rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers.GetObjectsContainersList(), rootType, node);
if (gd::ParameterMetadata::IsObject(type) &&
node.identifierName == objectName) {
hasDoneRenaming = true;
node.identifierName = objectNewName;
} else if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
} else {
if (node.identifierName == objectName) {
projectScopedContainers.MatchIdentifierWithName<void>(node.identifierName, [&]() {
// This is an object variable.
hasDoneRenaming = true;
node.identifierName = objectNewName;
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
@@ -124,8 +163,7 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
const gd::String& objectNewName;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
};
@@ -138,26 +176,23 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
public:
ExpressionObjectFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String &rootType_,
const gd::String& objectName_)
const gd::String& searchedObjectName_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
hasObject(false),
objectName(objectName_){};
searchedObjectName(searchedObjectName_){};
virtual ~ExpressionObjectFinder(){};
static bool CheckIfHasObject(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ProjectScopedContainers &projectScopedContainers,
const gd::String &rootType,
gd::ExpressionNode& node,
const gd::String& objectName) {
if (gd::ExpressionValidator::HasNoErrors(platform, globalObjectsContainer, objectsContainer, rootType, node)) {
ExpressionObjectFinder finder(platform, globalObjectsContainer, objectsContainer, rootType, objectName);
if (gd::ExpressionValidator::HasNoErrors(platform, projectScopedContainers, rootType, node)) {
ExpressionObjectFinder finder(platform, projectScopedContainers, rootType, objectName);
node.Visit(finder);
return finder.HasFoundObject();
@@ -182,6 +217,28 @@ 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);
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
} else {
if (node.name == searchedObjectName) {
projectScopedContainers.MatchIdentifierWithName<void>(node.name, [&]() {
// This is an object variable.
hasObject = true;
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
}
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
@@ -193,19 +250,36 @@ 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, globalObjectsContainer, objectsContainer, rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers.GetObjectsContainersList(), rootType, node);
if (gd::ParameterMetadata::IsObject(type) &&
node.identifierName == objectName) {
node.identifierName == searchedObjectName) {
hasObject = true;
} else if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
} else {
if (node.identifierName == searchedObjectName) {
projectScopedContainers.MatchIdentifierWithName<void>(node.identifierName, [&]() {
// This is an object variable.
hasObject = true;
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (node.objectName == objectName) {
if (node.objectName == searchedObjectName) {
hasObject = true;
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (node.objectName == objectName) {
if (node.objectName == searchedObjectName) {
hasObject = true;
}
for (auto& parameter : node.parameters) {
@@ -216,17 +290,15 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
private:
bool hasObject;
const gd::String& objectName;
const gd::String& searchedObjectName;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
};
bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& actions,
gd::String oldName,
gd::String newName) {
@@ -245,7 +317,7 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
"number", instrInfos.parameters[pNb].GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "number", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "number", *node, oldName, newName)) {
actions[aId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -255,7 +327,7 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
"string", instrInfos.parameters[pNb].GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "string", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "string", *node, oldName, newName)) {
actions[aId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -265,8 +337,7 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
if (!actions[aId].GetSubInstructions().empty())
somethingModified =
RenameObjectInActions(platform,
project,
layout,
projectScopedContainers,
actions[aId].GetSubInstructions(),
oldName,
newName) ||
@@ -278,8 +349,7 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
bool EventsRefactorer::RenameObjectInConditions(
const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& conditions,
gd::String oldName,
gd::String newName) {
@@ -299,7 +369,7 @@ bool EventsRefactorer::RenameObjectInConditions(
"number", instrInfos.parameters[pNb].GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "number", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "number", *node, oldName, newName)) {
conditions[cId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -309,7 +379,7 @@ bool EventsRefactorer::RenameObjectInConditions(
"string", instrInfos.parameters[pNb].GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "string", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "string", *node, oldName, newName)) {
conditions[cId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -319,8 +389,7 @@ bool EventsRefactorer::RenameObjectInConditions(
if (!conditions[cId].GetSubInstructions().empty())
somethingModified =
RenameObjectInConditions(platform,
project,
layout,
projectScopedContainers,
conditions[cId].GetSubInstructions(),
oldName,
newName) ||
@@ -332,8 +401,7 @@ bool EventsRefactorer::RenameObjectInConditions(
bool EventsRefactorer::RenameObjectInEventParameters(
const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::Expression& expression,
gd::ParameterMetadata parameterMetadata,
gd::String oldName,
@@ -348,7 +416,7 @@ bool EventsRefactorer::RenameObjectInEventParameters(
parameterMetadata.GetType())) {
auto node = expression.GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "number", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "number", *node, oldName, newName)) {
expression = ExpressionParser2NodePrinter::PrintNode(*node);
}
}
@@ -357,7 +425,7 @@ bool EventsRefactorer::RenameObjectInEventParameters(
parameterMetadata.GetType())) {
auto node = expression.GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "string", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "string", *node, oldName, newName)) {
expression = ExpressionParser2NodePrinter::PrintNode(*node);
}
}
@@ -366,8 +434,7 @@ bool EventsRefactorer::RenameObjectInEventParameters(
}
void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::EventsList& events,
gd::String oldName,
gd::String newName) {
@@ -376,14 +443,14 @@ void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform,
events[i].GetAllConditionsVectors();
for (std::size_t j = 0; j < conditionsVectors.size(); ++j) {
bool somethingModified = RenameObjectInConditions(
platform, project, layout, *conditionsVectors[j], oldName, newName);
platform, projectScopedContainers, *conditionsVectors[j], oldName, newName);
}
vector<gd::InstructionsList*> actionsVectors =
events[i].GetAllActionsVectors();
for (std::size_t j = 0; j < actionsVectors.size(); ++j) {
bool somethingModified = RenameObjectInActions(
platform, project, layout, *actionsVectors[j], oldName, newName);
platform, projectScopedContainers, *actionsVectors[j], oldName, newName);
}
vector<pair<gd::Expression*, gd::ParameterMetadata>>
@@ -393,8 +460,7 @@ void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform,
gd::ParameterMetadata parameterMetadata =
expressionsWithMetadata[j].second;
bool somethingModified = RenameObjectInEventParameters(platform,
project,
layout,
projectScopedContainers,
*expression,
parameterMetadata,
oldName,
@@ -403,8 +469,7 @@ void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform,
if (events[i].CanHaveSubEvents())
RenameObjectInEvents(platform,
project,
layout,
projectScopedContainers,
events[i].GetSubEvents(),
oldName,
newName);
@@ -412,8 +477,7 @@ void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform,
}
bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
gd::ObjectsContainer& globalObjectsContainer,
gd::ObjectsContainer& objectsContainer,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& actions,
gd::String name) {
bool somethingModified = false;
@@ -435,7 +499,7 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
"number", instrInfos.parameters[pNb].GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, globalObjectsContainer, objectsContainer, "number", *node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "number", *node, name)) {
deleteMe = true;
break;
}
@@ -445,7 +509,7 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
"string", instrInfos.parameters[pNb].GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, globalObjectsContainer, objectsContainer, "string", *node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "string", *node, name)) {
deleteMe = true;
break;
}
@@ -459,8 +523,7 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
} else if (!actions[aId].GetSubInstructions().empty())
somethingModified =
RemoveObjectInActions(platform,
globalObjectsContainer,
objectsContainer,
projectScopedContainers,
actions[aId].GetSubInstructions(),
name) ||
somethingModified;
@@ -471,8 +534,7 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
bool EventsRefactorer::RemoveObjectInConditions(
const gd::Platform& platform,
gd::ObjectsContainer& globalObjectsContainer,
gd::ObjectsContainer& objectsContainer,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& conditions,
gd::String name) {
bool somethingModified = false;
@@ -495,7 +557,7 @@ bool EventsRefactorer::RemoveObjectInConditions(
"number", instrInfos.parameters[pNb].GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, globalObjectsContainer, objectsContainer, "number", *node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "number", *node, name)) {
deleteMe = true;
break;
}
@@ -505,7 +567,7 @@ bool EventsRefactorer::RemoveObjectInConditions(
"string", instrInfos.parameters[pNb].GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, globalObjectsContainer, objectsContainer, "string", *node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "string", *node, name)) {
deleteMe = true;
break;
}
@@ -519,8 +581,7 @@ bool EventsRefactorer::RemoveObjectInConditions(
} else if (!conditions[cId].GetSubInstructions().empty())
somethingModified =
RemoveObjectInConditions(platform,
globalObjectsContainer,
objectsContainer,
projectScopedContainers,
conditions[cId].GetSubInstructions(),
name) ||
somethingModified;
@@ -530,8 +591,7 @@ bool EventsRefactorer::RemoveObjectInConditions(
}
void EventsRefactorer::RemoveObjectInEvents(const gd::Platform& platform,
gd::ObjectsContainer& globalObjectsContainer,
gd::ObjectsContainer& objectsContainer,
gd::ProjectScopedContainers& projectScopedContainers,
gd::EventsList& events,
gd::String name) {
for (std::size_t i = 0; i < events.size(); ++i) {
@@ -539,19 +599,19 @@ void EventsRefactorer::RemoveObjectInEvents(const gd::Platform& platform,
events[i].GetAllConditionsVectors();
for (std::size_t j = 0; j < conditionsVectors.size(); ++j) {
bool conditionsModified = RemoveObjectInConditions(
platform, globalObjectsContainer, objectsContainer, *conditionsVectors[j], name);
platform, projectScopedContainers, *conditionsVectors[j], name);
}
vector<gd::InstructionsList*> actionsVectors =
events[i].GetAllActionsVectors();
for (std::size_t j = 0; j < actionsVectors.size(); ++j) {
bool actionsModified = RemoveObjectInActions(
platform, globalObjectsContainer, objectsContainer, *actionsVectors[j], name);
platform, projectScopedContainers, *actionsVectors[j], name);
}
if (events[i].CanHaveSubEvents())
RemoveObjectInEvents(
platform, globalObjectsContainer, objectsContainer, events[i].GetSubEvents(), name);
platform, projectScopedContainers, events[i].GetSubEvents(), name);
}
}
@@ -587,20 +647,20 @@ std::vector<EventsSearchResult> EventsRefactorer::ReplaceStringInEvents(
for (std::size_t i = 0; i < events.size(); ++i) {
bool eventModified = false;
std::vector<gd::Expression*> allObjectExpressions =
events[i].GetAllObjectExpressions();
for (std::size_t j = 0; j < allObjectExpressions.size(); ++j) {
auto allExpressionsWithMetadata = events[i].GetAllExpressionsWithMetadata();
for (auto& expressionAndMetadata : allExpressionsWithMetadata) {
gd::Expression* expression = expressionAndMetadata.first;
gd::String newExpressionPlainString =
matchCase ? allObjectExpressions[j]->GetPlainString().FindAndReplace(
matchCase ? expression->GetPlainString().FindAndReplace(
toReplace, newString, true)
: ReplaceAllOccurrencesCaseInsensitive(
allObjectExpressions[j]->GetPlainString(),
expression->GetPlainString(),
toReplace,
newString);
if (newExpressionPlainString !=
allObjectExpressions[j]->GetPlainString()) {
*allObjectExpressions[j] = gd::Expression(newExpressionPlainString);
if (newExpressionPlainString != expression->GetPlainString()) {
*expression = gd::Expression(newExpressionPlainString);
if (!eventModified) {
modifiedEvents.push_back(EventsSearchResult(
@@ -815,14 +875,14 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
for (std::size_t i = 0; i < events.size(); ++i) {
bool eventAddedInResults = false;
std::vector<gd::Expression*> allObjectExpressions =
events[i].GetAllObjectExpressions();
for (std::size_t j = 0; j < allObjectExpressions.size(); ++j) {
auto allExpressionsWithMetadata = events[i].GetAllExpressionsWithMetadata();
for (auto& expressionAndMetadata : allExpressionsWithMetadata) {
gd::Expression* expression = expressionAndMetadata.first;
size_t foundPosition =
matchCase
? allObjectExpressions[j]->GetPlainString().find(search)
: allObjectExpressions[j]->GetPlainString().FindCaseInsensitive(
search);
? expression->GetPlainString().find(search)
: expression->GetPlainString().FindCaseInsensitive(search);
if (foundPosition != gd::String::npos && !eventAddedInResults) {
results.push_back(EventsSearchResult(

View File

@@ -14,6 +14,8 @@
namespace gd {
class EventsList;
class ObjectsContainer;
class ObjectsContainersList;
class ProjectScopedContainers;
class Platform;
class ExternalEvents;
class BaseEvent;
@@ -79,8 +81,7 @@ class GD_CORE_API EventsRefactorer {
* events ).
*/
static void RenameObjectInEvents(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::EventsList& events,
gd::String oldName,
gd::String newName);
@@ -89,8 +90,7 @@ class GD_CORE_API EventsRefactorer {
* Remove all actions or conditions using an object
*/
static void RemoveObjectInEvents(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::EventsList& events,
gd::String name);
@@ -136,8 +136,7 @@ class GD_CORE_API EventsRefactorer {
* \return true if something was modified.
*/
static bool RenameObjectInActions(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& instructions,
gd::String oldName,
gd::String newName);
@@ -149,8 +148,7 @@ class GD_CORE_API EventsRefactorer {
* \return true if something was modified.
*/
static bool RenameObjectInConditions(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& instructions,
gd::String oldName,
gd::String newName);
@@ -163,8 +161,7 @@ class GD_CORE_API EventsRefactorer {
*/
static bool RenameObjectInEventParameters(
const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::Expression& expression,
gd::ParameterMetadata parameterMetadata,
gd::String oldName,
@@ -176,8 +173,7 @@ class GD_CORE_API EventsRefactorer {
* \return true if something was modified.
*/
static bool RemoveObjectInConditions(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& conditions,
gd::String name);
@@ -187,8 +183,7 @@ class GD_CORE_API EventsRefactorer {
* \return true if something was modified.
*/
static bool RemoveObjectInActions(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& conditions,
gd::String name);

View File

@@ -0,0 +1,404 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/EventsVariableReplacer.h"
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/IDE/Events/ExpressionVariableOwnerFinder.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace gd {
/**
* \brief Go through the nodes and rename variables,
* or signal if the instruction must be renamed if a removed variable is used.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionVariableReplacer
: public ExpressionParser2NodeWorker {
public:
ExpressionVariableReplacer(
const gd::Platform& platform_,
const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::VariablesContainer& targetVariablesContainer_,
const std::unordered_map<gd::String, gd::String>& oldToNewVariableNames_,
const std::unordered_set<gd::String>& removedVariableNames_)
: hasDoneRenaming(false),
removedVariableUsed(false),
platform(platform_),
projectScopedContainers(projectScopedContainers_),
forcedInitialVariablesContainer(nullptr),
targetVariablesContainer(targetVariablesContainer_),
oldToNewVariableNames(oldToNewVariableNames_),
removedVariableNames(removedVariableNames_){};
virtual ~ExpressionVariableReplacer(){};
void SetForcedInitialVariablesContainer(
const gd::VariablesContainer* forcedInitialVariablesContainer_) {
forcedInitialVariablesContainer = forcedInitialVariablesContainer_;
}
bool HasDoneRenaming() const { return hasDoneRenaming; }
bool IsRemovedVariableUsed() const { return removedVariableUsed; }
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
node.expression->Visit(*this);
}
void OnVisitOperatorNode(OperatorNode& node) override {
node.leftHandSide->Visit(*this);
node.rightHandSide->Visit(*this);
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
node.factor->Visit(*this);
}
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
// The node represents a variable or an object name on which a variable
// will be accessed.
if (forcedInitialVariablesContainer) {
// A scope was forced. Honor it: it means this node represents a variable
// of the forced variables container.
if (forcedInitialVariablesContainer == &targetVariablesContainer) {
RenameOrRemoveVariableOfTargetVariableContainer(node.name);
}
if (node.child) node.child->Visit(*this);
return;
}
// Match the potential *new* name of the variable, because refactorings are
// done after changes in the variables container.
projectScopedContainers.MatchIdentifierWithName<void>(
GetPotentialNewName(node.name),
[&]() {
// This represents an object.
// Remember the object name.
objectNameToUseForVariableAccessor = node.name;
if (node.child) node.child->Visit(*this);
objectNameToUseForVariableAccessor = "";
},
[&]() {
// This is a variable.
if (projectScopedContainers.GetVariablesContainersList()
.HasVariablesContainer(targetVariablesContainer)) {
// The node represents a variable, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemoveVariableOfTargetVariableContainer(node.name);
}
if (node.child) node.child->Visit(*this);
},
[&]() {
// This is a property.
if (node.child) node.child->Visit(*this);
},
[&]() {
// This is a parameter.
if (node.child) node.child->Visit(*this);
},
[&]() {
// This is something else - potentially a deleted variable.
if (projectScopedContainers.GetVariablesContainersList()
.HasVariablesContainer(targetVariablesContainer)) {
// The node represents a variable, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemoveVariableOfTargetVariableContainer(node.name);
}
if (node.child) node.child->Visit(*this);
});
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
if (!objectNameToUseForVariableAccessor.empty()) {
if (objectsContainersList.HasVariablesContainer(
objectNameToUseForVariableAccessor, targetVariablesContainer)) {
// The node represents an object variable, and this object variables are
// the target. Do the replacement or removals:
RenameOrRemoveVariableOfTargetVariableContainer(node.name);
}
}
objectNameToUseForVariableAccessor = "";
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
objectNameToUseForVariableAccessor = "";
node.expression->Visit(*this);
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
// The node represents a variable or an object variable in an expression
// (and if it's a variable reference or a value does not have any importance
// here).
if (forcedInitialVariablesContainer) {
// A scope was forced. Honor it: it means this node represents a variable
// of the forced variables container.
if (forcedInitialVariablesContainer == &targetVariablesContainer) {
RenameOrRemoveVariableOfTargetVariableContainer(node.identifierName);
}
return;
}
// Match the potential *new* name of the variable, because refactorings are
// done after changes in the variables container.
projectScopedContainers.MatchIdentifierWithName<void>(
GetPotentialNewName(node.identifierName),
[&]() {
// This represents an object.
if (objectsContainersList.HasVariablesContainer(
node.identifierName, targetVariablesContainer)) {
// The node represents an object variable, and this object variables
// are the target. Do the replacement or removals:
RenameOrRemoveVariableOfTargetVariableContainer(
node.childIdentifierName);
}
},
[&]() {
// This is a variable.
if (projectScopedContainers.GetVariablesContainersList()
.HasVariablesContainer(targetVariablesContainer)) {
// The node represents a variable, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemoveVariableOfTargetVariableContainer(
node.identifierName);
}
},
[&]() {
// This is a property.
},
[&]() {
// This is a parameter.
},
[&]() {
// This is something else - potentially a deleted variable.
if (projectScopedContainers.GetVariablesContainersList()
.HasVariablesContainer(targetVariablesContainer)) {
// The node represents a variable, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemoveVariableOfTargetVariableContainer(
node.identifierName);
}
});
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
const gd::ExpressionMetadata& metadata =
MetadataProvider::GetFunctionCallMetadata(
platform, projectScopedContainers.GetObjectsContainersList(), node);
for (size_t parameterIndex = 0; parameterIndex < node.parameters.size();
++parameterIndex) {
const gd::ParameterMetadata* parameterMetadata =
MetadataProvider::GetFunctionCallParameterMetadata(
platform,
projectScopedContainers.GetObjectsContainersList(),
node,
parameterIndex);
// Handle legacy pre-scoped variable parameters: in this case, we
// force the "scope" at which starts the evalution of variables.
if (parameterMetadata && parameterMetadata->GetValueTypeMetadata()
.IsLegacyPreScopedVariable()) {
const gd::VariablesContainer* oldForcedInitialVariablesContainer =
forcedInitialVariablesContainer;
forcedInitialVariablesContainer = nullptr;
if (parameterMetadata->GetType() == "globalvar") {
forcedInitialVariablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer();
} else if (parameterMetadata->GetType() == "scenevar") {
forcedInitialVariablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
} else if (parameterMetadata->GetType() == "objectvar") {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform,
projectScopedContainers.GetObjectsContainersList(),
node.objectName,
*node.parameters[parameterIndex].get());
forcedInitialVariablesContainer =
projectScopedContainers.GetObjectsContainersList()
.GetObjectOrGroupVariablesContainer(objectName);
}
node.parameters[parameterIndex]->Visit(*this);
forcedInitialVariablesContainer = oldForcedInitialVariablesContainer;
} else {
// For any other parameter, there is no special treatment being needed.
node.parameters[parameterIndex]->Visit(*this);
}
}
}
void OnVisitEmptyNode(EmptyNode& node) override {}
private:
bool hasDoneRenaming;
bool removedVariableUsed;
const gd::String& GetPotentialNewName(const gd::String& oldName) {
return oldToNewVariableNames.count(oldName) >= 1
? oldToNewVariableNames.find(oldName)->second
: oldName;
}
bool RenameOrRemoveVariableOfTargetVariableContainer(
gd::String& variableName) {
if (oldToNewVariableNames.count(variableName) >= 1) {
variableName = oldToNewVariableNames.find(variableName)->second;
hasDoneRenaming = true;
return true;
} else if (removedVariableNames.count(variableName) >= 1) {
removedVariableUsed = true;
return true;
}
return false; // Nothing was changed or done.
}
// Scope:
const gd::Platform& platform;
const gd::ProjectScopedContainers& projectScopedContainers;
const gd::VariablesContainer* forcedInitialVariablesContainer;
// Renaming or removing to do:
const gd::VariablesContainer& targetVariablesContainer;
const std::unordered_map<gd::String, gd::String>& oldToNewVariableNames;
const std::unordered_set<gd::String>& removedVariableNames;
gd::String objectNameToUseForVariableAccessor;
};
const gd::VariablesContainer*
EventsVariableReplacer::FindForcedVariablesContainerIfAny(
const gd::String& type, const gd::String& lastObjectName) {
// Handle legacy pre-scoped variable parameters: in this case, we
// force the "scope" at which starts the evalution of variables.
if (type == "objectvar") {
return GetProjectScopedContainers()
.GetObjectsContainersList()
.GetObjectOrGroupVariablesContainer(lastObjectName);
} else if (type == "globalvar") {
return GetProjectScopedContainers()
.GetVariablesContainersList()
.GetTopMostVariablesContainer();
} else if (type == "scenevar") {
return GetProjectScopedContainers()
.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
}
return nullptr;
}
bool EventsVariableReplacer::DoVisitInstruction(gd::Instruction& instruction,
bool isCondition) {
const auto& metadata = isCondition
? gd::MetadataProvider::GetConditionMetadata(
platform, instruction.GetType())
: gd::MetadataProvider::GetActionMetadata(
platform, instruction.GetType());
bool shouldDeleteInstruction = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
const gd::String& type = parameterMetadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) &&
!gd::ParameterMetadata::IsExpression("number", type) &&
!gd::ParameterMetadata::IsExpression("string", type))
return; // Not an expression that can contain variables.
auto node = parameterValue.GetRootNode();
if (node) {
ExpressionVariableReplacer renamer(platform,
GetProjectScopedContainers(),
targetVariablesContainer,
oldToNewVariableNames,
removedVariableNames);
renamer.SetForcedInitialVariablesContainer(
FindForcedVariablesContainerIfAny(type, lastObjectName));
node->Visit(renamer);
if (renamer.IsRemovedVariableUsed()) {
shouldDeleteInstruction = true;
} else if (renamer.HasDoneRenaming()) {
instruction.SetParameter(
parameterIndex, ExpressionParser2NodePrinter::PrintNode(*node));
}
}
});
return shouldDeleteInstruction;
}
bool EventsVariableReplacer::DoVisitEventExpression(
gd::Expression& expression, const gd::ParameterMetadata& metadata) {
const gd::String& type = metadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) &&
!gd::ParameterMetadata::IsExpression("number", type) &&
!gd::ParameterMetadata::IsExpression("string", type))
return false; // Not an expression that can contain variables.
auto node = expression.GetRootNode();
if (node) {
ExpressionVariableReplacer renamer(platform,
GetProjectScopedContainers(),
targetVariablesContainer,
oldToNewVariableNames,
removedVariableNames);
renamer.SetForcedInitialVariablesContainer(
FindForcedVariablesContainerIfAny(type, ""));
node->Visit(renamer);
if (renamer.IsRemovedVariableUsed()) {
return true;
} else if (renamer.HasDoneRenaming()) {
expression = ExpressionParser2NodePrinter::PrintNode(*node);
}
}
return false;
}
EventsVariableReplacer::~EventsVariableReplacer() {}
} // namespace gd

View File

@@ -0,0 +1,60 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/String.h"
namespace gd {
class BaseEvent;
class VariablesContainer;
class EventsList;
class Platform;
} // namespace gd
namespace gd {
/**
* \brief Replace in expressions and in parameters of actions or conditions,
* references to the name of a variable by another.
*
* \ingroup IDE
*/
class GD_CORE_API EventsVariableReplacer
: public ArbitraryEventsWorkerWithContext {
public:
EventsVariableReplacer(
const gd::Platform &platform_,
const gd::VariablesContainer &targetVariablesContainer_,
const std::unordered_map<gd::String, gd::String> &oldToNewVariableNames_,
const std::unordered_set<gd::String> &removedVariableNames_)
: platform(platform_),
targetVariablesContainer(targetVariablesContainer_),
oldToNewVariableNames(oldToNewVariableNames_),
removedVariableNames(removedVariableNames_){};
virtual ~EventsVariableReplacer();
private:
bool DoVisitInstruction(gd::Instruction &instruction,
bool isCondition) override;
bool DoVisitEventExpression(gd::Expression &expression,
const gd::ParameterMetadata &metadata) override;
const gd::VariablesContainer *FindForcedVariablesContainerIfAny(
const gd::String &type, const gd::String &lastObjectName);
const gd::Platform &platform;
const gd::VariablesContainer &targetVariablesContainer;
gd::String objectName;
const std::unordered_map<gd::String, gd::String> &oldToNewVariableNames;
const std::unordered_set<gd::String> &removedVariableNames;
};
} // namespace gd

View File

@@ -17,6 +17,7 @@
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/IDE/DependenciesAnalyzer.h"
@@ -34,14 +35,12 @@ class GD_CORE_API VariableFinderExpressionNodeWorker
public:
VariableFinderExpressionNodeWorker(std::set<gd::String>& results_,
const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String& parameterType_,
const gd::String& objectName_ = "")
: results(results_),
platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
parameterType(parameterType_),
objectName(objectName_){};
virtual ~VariableFinderExpressionNodeWorker(){};
@@ -60,6 +59,9 @@ class GD_CORE_API VariableFinderExpressionNodeWorker
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
// We don't check variables or object variables here, because object variables only work
// if the variable is already declared.
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
@@ -70,7 +72,10 @@ class GD_CORE_API VariableFinderExpressionNodeWorker
node.expression->Visit(*this);
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {}
void OnVisitIdentifierNode(IdentifierNode& node) override {
// We don't check object variables here, because object variables only work
// if the variable is already declared.
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
bool considerFunction = objectName.empty() || node.objectName == objectName;
@@ -79,7 +84,7 @@ class GD_CORE_API VariableFinderExpressionNodeWorker
const gd::ExpressionMetadata &metadata = isObjectFunction ?
MetadataProvider::GetObjectAnyExpressionMetadata(
platform,
GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName),
projectScopedContainers.GetObjectsContainersList().GetTypeOfObject(objectName),
node.functionName):
MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName);
@@ -110,8 +115,7 @@ class GD_CORE_API VariableFinderExpressionNodeWorker
private:
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
std::set<gd::String>& results; ///< Reference to the std::set where argument
///< values must be stored.
@@ -163,8 +167,7 @@ class GD_CORE_API VariableFinderEventWorker
VariableFinderExpressionNodeWorker searcher(
results,
platform,
GetGlobalObjectsContainer(),
GetObjectsContainer(),
GetProjectScopedContainers(),
parameterType,
objectName);
node->Visit(searcher);
@@ -252,7 +255,8 @@ void EventsVariablesFinder::FindArgumentsInEventsAndDependencies(
platform,
parameterType,
objectName);
eventWorker.Launch(layout.GetEvents(), project, layout);
eventWorker.Launch(layout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout);
dependenciesAnalyzer.Analyze();
@@ -263,7 +267,8 @@ void EventsVariablesFinder::FindArgumentsInEventsAndDependencies(
platform,
parameterType,
objectName);
eventWorker.Launch(externalEvents.GetEvents(), project, layout);
eventWorker.Launch(externalEvents.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
}
for (const gd::String& sceneName : dependenciesAnalyzer.GetScenesDependencies()) {
const gd::Layout& dependencyLayout = project.GetLayout(sceneName);
@@ -272,7 +277,8 @@ void EventsVariablesFinder::FindArgumentsInEventsAndDependencies(
platform,
parameterType,
objectName);
eventWorker.Launch(dependencyLayout.GetEvents(), project, dependencyLayout);
eventWorker.Launch(dependencyLayout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, dependencyLayout));
}
}

View File

@@ -10,21 +10,15 @@ namespace gd {
const gd::ParameterMetadata
ExpressionCompletionDescription::badParameterMetadata;
const gd::ObjectConfiguration
ExpressionCompletionDescription::badObjectConfiguration;
/**
* \brief Turn an ExpressionCompletionDescription to a string.
*/
std::ostream& operator<<(std::ostream& os,
ExpressionCompletionDescription const& value) {
os << "{ " << value.GetCompletionKind() << ", " << value.GetType() << ", "
<< value.GetPrefix() << ", " << value.GetObjectName() << ", "
<< value.GetBehaviorName() << ", "
<< (value.IsExact() ? "exact" : "non-exact") << ", "
<< (value.IsLastParameter() ? "last parameter" : "not last parameter")
<< ", "
<< (value.HasParameterMetadata()
? gd::String::From(&value.GetParameterMetadata())
: "no parameter metadata")
<< " }";
os << value.ToString();
return os;
}

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,7 @@
namespace gd {
class Expression;
class ObjectsContainer;
class ObjectsContainersList;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
@@ -40,11 +41,10 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
* operations.
*/
static const gd::String GetType(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ObjectsContainersList &objectsContainersList,
gd::ExpressionNode& node) {
gd::ExpressionLeftSideTypeFinder typeFinder(
platform, globalObjectsContainer, objectsContainer);
platform, objectsContainersList);
node.Visit(typeFinder);
return typeFinder.GetType();
}
@@ -53,11 +53,9 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
protected:
ExpressionLeftSideTypeFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_)
const gd::ObjectsContainersList &objectsContainersList_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
objectsContainersList(objectsContainersList_),
type("unknown") {};
const gd::String &GetType() {
@@ -85,7 +83,7 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, globalObjectsContainer, objectsContainer, node);
platform, objectsContainersList, node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
type = "unknown";
}
@@ -113,8 +111,7 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
gd::String type;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ObjectsContainersList &objectsContainersList;
const gd::String rootType;
};

View File

@@ -21,6 +21,7 @@
namespace gd {
class Expression;
class ObjectsContainer;
class ObjectsContainersList;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
@@ -31,7 +32,7 @@ namespace gd {
/**
* \brief Find the type of the expression or sub-expression that a given node
* represents.
*
*
* The type returned by this worker is a mix of:
* - an expected type looking up like a parameter declaration
* - an actual type looking down, but only looking at the most left branch
@@ -50,12 +51,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::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ObjectsContainersList &objectsContainersList,
const gd::String &rootType,
gd::ExpressionNode& node) {
gd::ExpressionTypeFinder typeFinder(
platform, globalObjectsContainer, objectsContainer, rootType);
platform, objectsContainersList, rootType);
node.Visit(typeFinder);
return typeFinder.GetType();
}
@@ -64,12 +64,10 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
protected:
ExpressionTypeFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ObjectsContainersList &objectsContainersList_,
const gd::String &rootType_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
objectsContainersList(objectsContainersList_),
rootType(rootType_),
type(ExpressionTypeFinder::unknownType),
child(nullptr) {};
@@ -114,9 +112,8 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
type = ExpressionTypeFinder::unknownType;
}
auto leftSideType = gd::ExpressionLeftSideTypeFinder::GetType(
platform,
globalObjectsContainer,
objectsContainer,
platform,
objectsContainersList,
node);
if (leftSideType == ExpressionTypeFinder::numberType
|| leftSideType == ExpressionTypeFinder::stringType) {
@@ -129,7 +126,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (child == nullptr) {
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, globalObjectsContainer, objectsContainer, node);
platform, objectsContainersList, node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
VisitParent(node);
}
@@ -141,8 +138,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
const gd::ParameterMetadata* parameterMetadata =
gd::MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
objectsContainersList,
node,
*child);
if (parameterMetadata == nullptr || parameterMetadata->GetType().empty()) {
@@ -162,9 +158,8 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
}
else if (rootType == ExpressionTypeFinder::numberOrStringType) {
auto leftSideType = gd::ExpressionLeftSideTypeFinder::GetType(
platform,
globalObjectsContainer,
objectsContainer,
platform,
objectsContainersList,
node);
if (leftSideType == ExpressionTypeFinder::numberType
|| leftSideType == ExpressionTypeFinder::stringType) {
@@ -188,8 +183,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
ExpressionNode *child;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ObjectsContainersList &objectsContainersList;
const gd::String rootType;
};

View File

@@ -11,16 +11,20 @@
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Expression.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/Variable.h"
#include "GDCore/Project/VariablesContainersList.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Tools/MakeUnique.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
using namespace std;
@@ -59,61 +63,167 @@ size_t GetMaximumParametersNumber(
} // namespace
ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::FunctionCallNode& function) {
bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
const gd::IdentifierNode& identifier) {
auto validateVariableTypeForExpression =
[this, &identifier](gd::Variable::Type type) {
// Collections type can't be used directly in expressions, a child
// must be accessed.
if (type == Variable::Structure) {
RaiseTypeError(_("You need to specify the name of the child variable "
"to access. For example: `MyVariable.child`."),
identifier.identifierNameLocation);
} else if (type == Variable::Array) {
RaiseTypeError(_("You need to specify the name of the child variable "
"to access. For example: `MyVariable[0]`."),
identifier.identifierNameLocation);
} else {
// Number, string or boolean variables can be used in expressions.
return;
}
};
const auto& variablesContainersList = projectScopedContainers.GetVariablesContainersList();
const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
const auto& propertiesContainersList = projectScopedContainers.GetPropertiesContainersList();
const auto& parametersVectorsList = projectScopedContainers.GetParametersVectorsList();
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.
}
if (!objectsContainersList.HasObjectOrGroupWithVariableNamed(identifier.identifierName, identifier.childIdentifierName)) {
RaiseTypeError(_("This variable does not exist on this object or group."),
identifier.identifierNameLocation);
return true; // We should have found a variable.
}
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);
if (identifier.childIdentifierName.empty()) {
// Just the root variable is accessed, check it can be used in an
// expression.
validateVariableTypeForExpression(variable.GetType());
return true; // We found a variable.
} else {
// A child variable is accessed, check it can be used in an expression.
if (!variable.HasChild(identifier.childIdentifierName)) {
RaiseTypeError(_("No child variable with this name found."),
identifier.childIdentifierNameLocation);
return true; // We should have found a variable.
}
const gd::Variable& childVariable =
variable.GetChild(identifier.childIdentifierName);
return true; // We found a variable.
}
}, [&]() {
// This is a property.
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.
}
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()) {
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.
}
return true; // We found a parameter.
}, [&]() {
// This is something else.
return false;
});
}
ExpressionValidator::Type ExpressionValidator::ValidateFunction(
const gd::FunctionCallNode& function) {
ReportAnyError(function);
gd::String objectType = function.objectName.empty() ? "" :
GetTypeOfObject(globalObjectsContainer, objectsContainer, function.objectName);
gd::String behaviorType = function.behaviorName.empty() ? "" :
GetTypeOfBehavior(globalObjectsContainer, objectsContainer, function.behaviorName);
const gd::ExpressionMetadata &metadata = function.behaviorName.empty() ?
function.objectName.empty() ?
MetadataProvider::GetAnyExpressionMetadata(platform, function.functionName) :
MetadataProvider::GetObjectAnyExpressionMetadata(
platform, objectType, function.functionName) :
MetadataProvider::GetBehaviorAnyExpressionMetadata(
platform, behaviorType, function.functionName);
auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
gd::String objectType =
function.objectName.empty()
? ""
: objectsContainersList.GetTypeOfObject(function.objectName);
gd::String behaviorType = function.behaviorName.empty()
? ""
: objectsContainersList.GetTypeOfBehavior(function.behaviorName);
const gd::ExpressionMetadata& metadata =
function.behaviorName.empty()
? function.objectName.empty()
? MetadataProvider::GetAnyExpressionMetadata(
platform, function.functionName)
: MetadataProvider::GetObjectAnyExpressionMetadata(
platform, objectType, function.functionName)
: MetadataProvider::GetBehaviorAnyExpressionMetadata(
platform, behaviorType, function.functionName);
Type returnType = StringToType(metadata.GetReturnType());
if (!function.objectName.empty() &&
!globalObjectsContainer.HasObjectNamed(function.objectName) &&
!globalObjectsContainer.GetObjectGroups().Has(function.objectName) &&
!objectsContainer.HasObjectNamed(function.objectName) &&
!objectsContainer.GetObjectGroups().Has(function.objectName)) {
!objectsContainersList.HasObjectOrGroupNamed(function.objectName)) {
RaiseTypeError(_("This object doesn't exist."),
function.objectNameLocation, /*isFatal=*/false);
function.objectNameLocation,
/*isFatal=*/false);
return returnType;
}
if (!function.behaviorName.empty() &&
!gd::HasBehaviorInObjectOrGroup(globalObjectsContainer, objectsContainer,
function.objectName,
function.behaviorName)) {
!objectsContainersList.HasBehaviorInObjectOrGroup(function.objectName,
function.behaviorName)) {
RaiseTypeError(_("This behavior is not attached to this object."),
function.behaviorNameLocation, /*isFatal=*/false);
function.behaviorNameLocation,
/*isFatal=*/false);
return returnType;
}
if (!function.objectName.empty() &&
// If the function needs a capability on the object that may not be covered
// by all objects, check it now.
!metadata.GetRequiredBaseObjectCapability().empty()) {
const gd::ObjectMetadata &objectMetadata =
// If the function needs a capability on the object that may not be
// covered by all objects, check it now.
!metadata.GetRequiredBaseObjectCapability().empty()) {
const gd::ObjectMetadata& objectMetadata =
MetadataProvider::GetObjectMetadata(platform, objectType);
}
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
RaiseError(
"invalid_function_name",
_("Cannot find an expression with this name: ") +
function.functionName + "\n" +
_("Double check that you've not made any typo in the name."),
function.location);
return returnType;
RaiseError("invalid_function_name",
_("Cannot find an expression with this name: ") +
function.functionName + "\n" +
_("Double check that you've not made any typo in the name."),
function.location);
return returnType;
}
// Validate the type of the function
@@ -128,9 +238,9 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
} else if (parentType != Type::Number &&
parentType != Type::NumberOrString) {
RaiseTypeError(_("You tried to use an expression that returns a "
"number, but another type is expected:") +
" " + TypeToString(parentType),
function.location);
"number, but another type is expected:") +
" " + TypeToString(parentType),
function.location);
return returnType;
}
} else if (returnType == Type::String) {
@@ -144,16 +254,16 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
} else if (parentType != Type::String &&
parentType != Type::NumberOrString) {
RaiseTypeError(_("You tried to use an expression that returns a "
"string, but another type is expected:") +
" " + TypeToString(parentType),
function.location);
"string, but another type is expected:") +
" " + TypeToString(parentType),
function.location);
return returnType;
}
} else {
if (parentType != returnType) {
RaiseTypeError(
_("You tried to use an expression with the wrong return type:") + " " +
TypeToString(returnType),
_("You tried to use an expression with the wrong return type:") +
" " + TypeToString(returnType),
function.location);
return returnType;
}
@@ -162,10 +272,12 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
// Validate parameters count
size_t minParametersCount = GetMinimumParametersNumber(
metadata.parameters,
ExpressionParser2::WrittenParametersFirstIndex(function.objectName, function.behaviorName));
ExpressionParser2::WrittenParametersFirstIndex(function.objectName,
function.behaviorName));
size_t maxParametersCount = GetMaximumParametersNumber(
metadata.parameters,
ExpressionParser2::WrittenParametersFirstIndex(function.objectName, function.behaviorName));
ExpressionParser2::WrittenParametersFirstIndex(function.objectName,
function.behaviorName));
if (function.parameters.size() < minParametersCount ||
function.parameters.size() > maxParametersCount) {
gd::String expectedCountMessage =
@@ -179,28 +291,29 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
if (function.parameters.size() < minParametersCount) {
RaiseError(
"too_few_parameters",
_("You have not entered enough parameters for the expression.") + " " +
expectedCountMessage,
_("You have not entered enough parameters for the expression.") +
" " + expectedCountMessage,
function.location);
} else {
RaiseError(
"extra_parameter",
_("This parameter was not expected by this expression. Remove it "
"or verify that you've entered the proper expression name.") + " " +
expectedCountMessage,
ExpressionParserLocation(
function.parameters[maxParametersCount]->location.GetStartPosition(),
function.location.GetEndPosition() - 1));
"or verify that you've entered the proper expression name.") +
" " + expectedCountMessage,
ExpressionParserLocation(function.parameters[maxParametersCount]
->location.GetStartPosition(),
function.location.GetEndPosition() - 1));
}
return returnType;
}
// TODO: reverse the order of diagnostic?
size_t writtenParametersFirstIndex =
ExpressionParser2::WrittenParametersFirstIndex(
function.objectName, function.behaviorName);
ExpressionParser2::WrittenParametersFirstIndex(function.objectName,
function.behaviorName);
int metadataIndex = writtenParametersFirstIndex;
for (int parameterIndex = 0; parameterIndex < function.parameters.size(); parameterIndex++) {
for (int parameterIndex = 0; parameterIndex < function.parameters.size();
parameterIndex++) {
auto& parameter = function.parameters[parameterIndex];
while (metadata.GetParameters()[metadataIndex].IsCodeOnly()) {
// The sizes are already checked above.
@@ -208,45 +321,45 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
}
auto& parameterMetadata = metadata.GetParameters()[metadataIndex];
if (!parameterMetadata.IsOptional() || dynamic_cast<EmptyNode*>(parameter.get()) == nullptr) {
if (!parameterMetadata.IsOptional() ||
dynamic_cast<EmptyNode*>(parameter.get()) == nullptr) {
auto currentParentType = parentType;
parentType = StringToType(parameterMetadata.GetType());
parameter->Visit(*this);
parentType = currentParentType;
const gd::String &expectedParameterType = parameterMetadata.GetType();
const gd::String& expectedParameterType = parameterMetadata.GetType();
if (gd::ParameterMetadata::IsExpression(
ExpressionValidator::variableTypeString, expectedParameterType)) {
if (dynamic_cast<IdentifierNode *>(parameter.get()) == nullptr
&& dynamic_cast<VariableNode *>(parameter.get()) == nullptr) {
RaiseError(
"malformed_variable_parameter",
_("A variable name was expected but something else was "
"written. Enter just the name of the variable for this "
"parameter."),
parameter->location);
ExpressionValidator::variableTypeString, expectedParameterType)) {
if (dynamic_cast<IdentifierNode*>(parameter.get()) == nullptr &&
dynamic_cast<VariableNode*>(parameter.get()) == nullptr) {
RaiseError("malformed_variable_parameter",
_("A variable name was expected but something else was "
"written. Enter just the name of the variable for this "
"parameter."),
parameter->location);
}
} else if (gd::ParameterMetadata::IsObject(expectedParameterType)) {
if (dynamic_cast<IdentifierNode *>(parameter.get()) == nullptr) {
RaiseError(
"malformed_object_parameter",
_("An object name was expected but something else was "
"written. Enter just the name of the object for this "
"parameter."),
parameter->location);
if (dynamic_cast<IdentifierNode*>(parameter.get()) == nullptr) {
RaiseError("malformed_object_parameter",
_("An object name was expected but something else was "
"written. Enter just the name of the object for this "
"parameter."),
parameter->location);
}
}
// String and number are already checked in children.
else if (!gd::ParameterMetadata::IsExpression(
ExpressionValidator::numberTypeString, expectedParameterType)
&& !gd::ParameterMetadata::IsExpression(
ExpressionValidator::stringTypeString, expectedParameterType)) {
RaiseError(
"unknown_parameter_type",
_("This function is improperly set up. Reach out to the "
"extension developer or a GDevelop maintainer to fix "
"this issue"),
parameter->location);
ExpressionValidator::numberTypeString,
expectedParameterType) &&
!gd::ParameterMetadata::IsExpression(
ExpressionValidator::stringTypeString,
expectedParameterType)) {
RaiseError("unknown_parameter_type",
_("This function is improperly set up. Reach out to the "
"extension developer or a GDevelop maintainer to fix "
"this issue"),
parameter->location);
}
}
metadataIndex++;
@@ -254,55 +367,60 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
return returnType;
}
// TODO factorize in a file with an enum and helpers?
const gd::String ExpressionValidator::unknownTypeString = "unknown";
const gd::String ExpressionValidator::numberTypeString = "number";
const gd::String ExpressionValidator::stringTypeString = "string";
const gd::String ExpressionValidator::numberOrStringTypeString = "number|string";
const gd::String ExpressionValidator::variableTypeString = "variable";
const gd::String ExpressionValidator::objectTypeString = "object";
const gd::String ExpressionValidator::emptyTypeString = "empty";
// TODO factorize in a file with an enum and helpers?
const gd::String ExpressionValidator::unknownTypeString = "unknown";
const gd::String ExpressionValidator::numberTypeString = "number";
const gd::String ExpressionValidator::stringTypeString = "string";
const gd::String ExpressionValidator::numberOrStringTypeString =
"number|string";
const gd::String ExpressionValidator::variableTypeString = "variable";
const gd::String ExpressionValidator::objectTypeString = "object";
const gd::String ExpressionValidator::emptyTypeString = "empty";
const gd::String &ExpressionValidator::TypeToString(Type type) {
switch (type) {
case Type::Unknown:
const gd::String& ExpressionValidator::TypeToString(Type type) {
switch (type) {
case Type::Unknown:
return unknownTypeString;
case Type::Number:
case Type::Number:
return numberTypeString;
case Type::String:
case Type::String:
return stringTypeString;
case Type::NumberOrString:
case Type::NumberOrString:
return numberOrStringTypeString;
case Type::Variable:
case Type::Variable:
return variableTypeString;
case Type::Object:
case Type::Object:
return objectTypeString;
case Type::Empty:
case Type::Empty:
return emptyTypeString;
}
return unknownTypeString;
}
return unknownTypeString;
}
ExpressionValidator::Type ExpressionValidator::StringToType(const gd::String &type) {
if (type == ExpressionValidator::numberTypeString
|| gd::ParameterMetadata::IsExpression(ExpressionValidator::numberTypeString, type)) {
return Type::Number;
}
if (type == ExpressionValidator::stringTypeString
|| gd::ParameterMetadata::IsExpression(ExpressionValidator::stringTypeString, type)) {
return Type::String;
}
if (type == ExpressionValidator::numberOrStringTypeString) {
return Type::NumberOrString;
}
if (type == ExpressionValidator::variableTypeString
|| gd::ParameterMetadata::IsExpression(ExpressionValidator::variableTypeString, type)) {
return Type::Variable;
}
if (type == ExpressionValidator::objectTypeString
|| gd::ParameterMetadata::IsObject(type)) {
return Type::Object;
}
return Type::Unknown;
ExpressionValidator::Type ExpressionValidator::StringToType(
const gd::String& type) {
if (type == ExpressionValidator::numberTypeString ||
gd::ParameterMetadata::IsExpression(ExpressionValidator::numberTypeString,
type)) {
return Type::Number;
}
if (type == ExpressionValidator::stringTypeString ||
gd::ParameterMetadata::IsExpression(ExpressionValidator::stringTypeString,
type)) {
return Type::String;
}
if (type == ExpressionValidator::numberOrStringTypeString) {
return Type::NumberOrString;
}
if (type == ExpressionValidator::variableTypeString ||
gd::ParameterMetadata::IsExpression(
ExpressionValidator::variableTypeString, type)) {
return Type::Variable;
}
if (type == ExpressionValidator::objectTypeString ||
gd::ParameterMetadata::IsObject(type)) {
return Type::Object;
}
return Type::Unknown;
}
} // namespace gd

View File

@@ -13,13 +13,18 @@
#include "GDCore/Tools/MakeUnique.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/VariablesContainersList.h"
namespace gd {
class Expression;
class ObjectsContainer;
class VariablesContainer;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
class VariablesContainersList;
class ProjectScopedContainers;
} // namespace gd
namespace gd {
@@ -33,14 +38,13 @@ namespace gd {
class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
public:
ExpressionValidator(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers & projectScopedContainers_,
const gd::String &rootType_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
parentType(StringToType(gd::ParameterMetadata::GetExpressionValueType(rootType_))),
childType(Type::Unknown) {};
childType(Type::Unknown),
forbidsUsageOfBracketsBecauseParentIsObject(false) {};
virtual ~ExpressionValidator(){};
/**
@@ -48,11 +52,10 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
* any error including non-fatal ones.
*/
static bool HasNoErrors(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ProjectScopedContainers & projectScopedContainers,
const gd::String &rootType,
gd::ExpressionNode& node) {
gd::ExpressionValidator validator(platform, globalObjectsContainer, objectsContainer, rootType);
gd::ExpressionValidator validator(platform, projectScopedContainers, rootType);
node.Visit(validator);
return validator.GetAllErrors().empty();
}
@@ -82,7 +85,7 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
}
void OnVisitOperatorNode(OperatorNode& node) override {
ReportAnyError(node);
node.leftHandSide->Visit(*this);
const Type leftType = childType;
@@ -185,28 +188,69 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
}
void OnVisitVariableNode(VariableNode& node) override {
ReportAnyError(node);
if (node.child) {
node.child->Visit(*this);
}
childType = Type::Variable;
if (parentType == Type::String) {
RaiseTypeError(_("Variables must be surrounded by VariableString()."),
node.location);
} else if (parentType == Type::Number) {
RaiseTypeError(_("Variables must be surrounded by Variable()."),
node.location);
} else if (parentType == Type::NumberOrString) {
RaiseTypeError(
_("Variables must be surrounded by Variable() or VariableString()."),
node.location);
} else if (parentType != Type::Variable) {
if (parentType == Type::Variable) {
childType = Type::Variable;
if (node.child) {
node.child->Visit(*this);
}
} else if (parentType == Type::String || parentType == Type::Number || parentType == Type::NumberOrString) {
// The node represents a variable or an object variable in an expression waiting for its *value* to be returned.
childType = parentType;
const auto& variablesContainersList = projectScopedContainers.GetVariablesContainersList();
const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
const auto& propertiesContainerList = projectScopedContainers.GetPropertiesContainersList();
forbidsUsageOfBracketsBecauseParentIsObject = false;
projectScopedContainers.MatchIdentifierWithName<void>(node.name,
[&]() {
// This represents an object.
// While understood by the parser, it's forbidden to use the bracket notation just after
// an object name (`MyObject["MyVariable"]`).
forbidsUsageOfBracketsBecauseParentIsObject = true;
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
// Being in this node implies that there is at least a child - which is not supported for properties.
RaiseTypeError(_("Accessing a child variable of a property is not possible - just write the property name."),
node.location);
}, [&]() {
// This is a parameter.
// Being in this node implies that there is at least a child - which is not supported for parameters.
RaiseTypeError(_("Accessing a child variable of a parameter is not possible - just write the parameter name."),
node.location);
}, [&]() {
// This is something else.
RaiseTypeError(_("No object, variable or property with this name found."),
node.location);
});
if (node.child) {
node.child->Visit(*this);
}
forbidsUsageOfBracketsBecauseParentIsObject = false;
} else {
RaiseTypeError(_("You entered a variable, but this type was expected:") +
" " + TypeToString(parentType),
node.location);
if (node.child) {
node.child->Visit(*this);
}
}
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
ReportAnyError(node);
// In the case we accessed an object variable (`MyObject.MyVariable`),
// brackets can now be used (`MyObject.MyVariable["MyChildVariable"]` is now valid).
forbidsUsageOfBracketsBecauseParentIsObject = false;
if (node.child) {
node.child->Visit(*this);
}
@@ -215,6 +259,15 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
VariableBracketAccessorNode& node) override {
ReportAnyError(node);
if (forbidsUsageOfBracketsBecauseParentIsObject) {
RaiseError("brackets_not_allowed_for_objects",
_("You can't use the brackets to access an object variable. "
"Use a dot followed by the variable name, like this: "
"`MyObject.MyVariable`."),
node.location);
}
forbidsUsageOfBracketsBecauseParentIsObject = false;
Type currentParentType = parentType;
parentType = Type::NumberOrString;
node.expression->Visit(*this);
@@ -227,19 +280,29 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
void OnVisitIdentifierNode(IdentifierNode& node) override {
ReportAnyError(node);
if (parentType == Type::String) {
RaiseTypeError(_("You must wrap your text inside double quotes "
"(example: \"Hello world\")."),
node.location);
if (!ValidateObjectVariableOrVariableOrProperty(node)) {
// The identifier is not a variable, so either the variable is not properly declared
// or it's a text without quotes.
RaiseTypeError(_("You must wrap your text inside double quotes "
"(example: \"Hello world\")."),
node.location);
}
}
else if (parentType == Type::Number) {
RaiseTypeError(
_("You must enter a number."), node.location);
if (!ValidateObjectVariableOrVariableOrProperty(node)) {
// The identifier is not a variable, so the variable is not properly declared.
RaiseTypeError(
_("You must enter a number."), node.location);
}
}
else if (parentType == Type::NumberOrString) {
RaiseTypeError(
_("You must enter a number or a text, wrapped inside double quotes "
"(example: \"Hello world\")."),
node.location);
if (!ValidateObjectVariableOrVariableOrProperty(node)) {
// The identifier is not a variable, so either the variable is not properly declared
// or it's a text without quotes.
RaiseTypeError(
_("You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name."),
node.location);
}
}
else if (parentType != Type::Object && parentType != Type::Variable) {
// It can't happen.
@@ -278,6 +341,7 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
private:
enum Type {Unknown = 0, Number, String, NumberOrString, Variable, Object, Empty};
Type ValidateFunction(const gd::FunctionCallNode& function);
bool ValidateObjectVariableOrVariableOrProperty(const gd::IdentifierNode& identifier);
void ReportAnyError(const ExpressionNode& node, bool isFatal = true) {
if (node.diagnostic && node.diagnostic->IsError()) {
@@ -291,7 +355,7 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
}
}
void RaiseError(const gd::String &type,
void RaiseError(const gd::String &type,
const gd::String &message, const ExpressionParserLocation &location, bool isFatal = true) {
auto diagnostic = gd::make_unique<ExpressionParserError>(
type, message, location);
@@ -329,11 +393,11 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
std::vector<ExpressionParserDiagnostic*> fatalErrors;
std::vector<ExpressionParserDiagnostic*> allErrors;
std::vector<std::unique_ptr<ExpressionParserDiagnostic>> supplementalErrors;
Type childType;
Type parentType;
Type childType; ///< The type "discovered" down the tree and passed up.
Type parentType; ///< The type "required" by the top of the tree.
bool forbidsUsageOfBracketsBecauseParentIsObject;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
};
} // namespace gd

View File

@@ -15,6 +15,7 @@
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Tools/Localization.h"
namespace gd {
@@ -31,6 +32,10 @@ namespace gd {
* \brief Find the object name that should be used as a context of the
* expression or sub-expression that a given node represents.
*
* This is needed because of the legacy convention where a "objectvar"
* parameter represents a variable of the object represented by the previous "object"
* parameter.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWorker {
@@ -41,12 +46,11 @@ class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWo
* context of the expression or sub-expression that a given node represents.
*/
static const gd::String GetObjectName(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ObjectsContainersList &objectsContainersList,
const gd::String& rootObjectName,
gd::ExpressionNode& node) {
gd::ExpressionVariableOwnerFinder typeFinder(
platform, globalObjectsContainer, objectsContainer, rootObjectName);
platform, objectsContainersList, rootObjectName);
node.Visit(typeFinder);
return typeFinder.GetObjectName();
}
@@ -55,12 +59,10 @@ class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWo
protected:
ExpressionVariableOwnerFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ObjectsContainersList &objectsContainersList_,
const gd::String& rootObjectName_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
objectsContainersList(objectsContainersList_),
rootObjectName(rootObjectName_),
objectName(""),
variableNode(nullptr) {};
@@ -126,9 +128,8 @@ class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWo
}
const gd::ParameterMetadata* parameterMetadata =
MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
platform,
objectsContainersList,
functionCall,
parameterIndex);
if (parameterMetadata == nullptr
@@ -142,9 +143,8 @@ class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWo
for (int previousIndex = parameterIndex - 1; previousIndex >= 0; previousIndex--) {
const gd::ParameterMetadata* previousParameterMetadata =
MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
platform,
objectsContainersList,
functionCall,
previousIndex);
if (previousParameterMetadata != nullptr
@@ -162,8 +162,7 @@ class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWo
gd::ExpressionNode *variableNode;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ObjectsContainersList &objectsContainersList;
const gd::String &rootObjectName;
};

View File

@@ -31,16 +31,14 @@ namespace gd {
class GD_CORE_API ExpressionParameterMover
: public ExpressionParser2NodeWorker {
public:
ExpressionParameterMover(const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_,
ExpressionParameterMover(const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::String& behaviorType_,
const gd::String& objectType_,
const gd::String& functionName_,
std::size_t oldIndex_,
std::size_t newIndex_)
: hasDoneMoving(false),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
behaviorType(behaviorType_),
objectType(objectType_),
functionName(functionName_),
@@ -98,16 +96,16 @@ class GD_CORE_API ExpressionParameterMover
// This refactor only applies on events object functions
// and events object functions doesn't exist yet.
// This is a dead code.
const gd::String& thisObjectType = gd::GetTypeOfObject(
globalObjectsContainer, objectsContainer, node.objectName);
const gd::String& thisObjectType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfObject(node.objectName);
if (thisObjectType == objectType) {
moveParameter(node.parameters, 1);
hasDoneMoving = true;
}
} else if (!behaviorType.empty() && !node.behaviorName.empty()) {
// Move parameter of a behavior function
const gd::String& thisBehaviorType = gd::GetTypeOfBehavior(
globalObjectsContainer, objectsContainer, node.behaviorName);
const gd::String& thisBehaviorType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfBehavior(node.behaviorName);
if (thisBehaviorType == behaviorType) {
moveParameter(node.parameters, 2);
hasDoneMoving = true;
@@ -126,8 +124,7 @@ class GD_CORE_API ExpressionParameterMover
private:
bool hasDoneMoving;
const gd::ObjectsContainer& globalObjectsContainer;
const gd::ObjectsContainer& objectsContainer;
const gd::ProjectScopedContainers& projectScopedContainers;
const gd::String& behaviorType; // The behavior type of the function which
// must have a parameter moved (optional).
const gd::String& objectType; // The object type of the function which
@@ -155,8 +152,7 @@ bool ExpressionsParameterMover::DoVisitInstruction(gd::Instruction& instruction,
auto node = expression.GetRootNode();
if (node) {
ExpressionParameterMover mover(GetGlobalObjectsContainer(),
GetObjectsContainer(),
ExpressionParameterMover mover(GetProjectScopedContainers(),
behaviorType,
objectType,
functionName,

View File

@@ -23,22 +23,20 @@
namespace gd {
/**
* \brief Go through the nodes and change the given object name to a new one.
* \brief Go through the nodes and change the given function name to a new name.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionFunctionRenamer
: public ExpressionParser2NodeWorker {
public:
ExpressionFunctionRenamer(const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_,
ExpressionFunctionRenamer(const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::String& behaviorType_,
const gd::String& objectType_,
const gd::String& oldFunctionName_,
const gd::String& newFunctionName_)
: hasDoneRenaming(false),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
behaviorType(behaviorType_),
objectType(objectType_),
oldFunctionName(oldFunctionName_),
@@ -71,16 +69,18 @@ class GD_CORE_API ExpressionFunctionRenamer
node.expression->Visit(*this);
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {}
void OnVisitIdentifierNode(IdentifierNode& node) override {
// Nothing to do as this is either a variable, an object variable a property or something else
// but not an expression.
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (!node.behaviorFunctionName.empty()) {
// Behavior function name
if (!behaviorType.empty() &&
node.behaviorFunctionName == oldFunctionName) {
const gd::String& thisBehaviorType =
gd::GetTypeOfBehavior(globalObjectsContainer,
objectsContainer,
node.objectFunctionOrBehaviorName);
const gd::String& thisBehaviorType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfBehavior(
node.objectFunctionOrBehaviorName);
if (thisBehaviorType == behaviorType) {
node.behaviorFunctionName = newFunctionName;
hasDoneRenaming = true;
@@ -90,8 +90,8 @@ class GD_CORE_API ExpressionFunctionRenamer
// Object function name
if (behaviorType.empty() && !objectType.empty() &&
node.objectFunctionOrBehaviorName == oldFunctionName) {
const gd::String& thisObjectType = gd::GetTypeOfObject(
globalObjectsContainer, objectsContainer, node.objectName);
const gd::String& thisObjectType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfObject(node.objectName);
if (thisObjectType == objectType) {
node.objectFunctionOrBehaviorName = newFunctionName;
hasDoneRenaming = true;
@@ -104,16 +104,16 @@ class GD_CORE_API ExpressionFunctionRenamer
if (behaviorType.empty() && !objectType.empty() &&
!node.objectName.empty()) {
// Replace an object function
const gd::String& thisObjectType = gd::GetTypeOfObject(
globalObjectsContainer, objectsContainer, node.objectName);
const gd::String& thisObjectType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfObject(node.objectName);
if (thisObjectType == objectType) {
node.functionName = newFunctionName;
hasDoneRenaming = true;
}
} else if (!behaviorType.empty() && !node.behaviorName.empty()) {
// Replace a behavior function
const gd::String& thisBehaviorType = gd::GetTypeOfBehavior(
globalObjectsContainer, objectsContainer, node.behaviorName);
const gd::String& thisBehaviorType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfBehavior(node.behaviorName);
if (thisBehaviorType == behaviorType) {
node.functionName = newFunctionName;
hasDoneRenaming = true;
@@ -132,8 +132,7 @@ class GD_CORE_API ExpressionFunctionRenamer
private:
bool hasDoneRenaming;
const gd::ObjectsContainer& globalObjectsContainer;
const gd::ObjectsContainer& objectsContainer;
const gd::ProjectScopedContainers& projectScopedContainers;
const gd::String& behaviorType; // The behavior type for which the expression
// must be replaced (optional).
const gd::String& objectType; // The object type for which the expression
@@ -159,8 +158,7 @@ bool ExpressionsRenamer::DoVisitInstruction(gd::Instruction& instruction,
auto node = expression.GetRootNode();
if (node) {
ExpressionFunctionRenamer renamer(GetGlobalObjectsContainer(),
GetObjectsContainer(),
ExpressionFunctionRenamer renamer(GetProjectScopedContainers(),
behaviorType,
objectType,
oldFunctionName,

View File

@@ -34,13 +34,12 @@ class GD_CORE_API ExpressionIdentifierStringFinder
public:
ExpressionIdentifierStringFinder(
const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::String &expressionPlainString_,
const gd::String &parameterType_, const gd::String &objectName_,
const gd::String &layerName_, const gd::String &oldName_)
: platform(platform_), globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
: platform(platform_),
projectScopedContainers(projectScopedContainers_),
expressionPlainString(expressionPlainString_),
parameterType(parameterType_), objectName(objectName_),
layerName(layerName_), oldName(oldName_){};
@@ -82,7 +81,7 @@ protected:
void OnVisitFunctionCallNode(FunctionCallNode &node) override {
gd::String lastLayerName;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
platform, globalObjectsContainer, objectsContainer, node,
platform, projectScopedContainers.GetObjectsContainersList(), node,
[&](const gd::ParameterMetadata &parameterMetadata,
std::unique_ptr<gd::ExpressionNode> &parameterNode,
size_t parameterIndex, const gd::String &lastObjectName) {
@@ -123,8 +122,7 @@ protected:
private:
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
/// It's used to extract parameter content.
const gd::String &expressionPlainString;
const gd::String &oldName;
@@ -176,7 +174,7 @@ bool ProjectElementRenamer::DoVisitInstruction(gd::Instruction &instruction,
auto node = parameterValue.GetRootNode();
if (node) {
ExpressionIdentifierStringFinder finder(
platform, GetGlobalObjectsContainer(), GetObjectsContainer(),
platform, GetProjectScopedContainers(),
parameterValue.GetPlainString(), parameterType, objectName,
layerName, oldName);
node->Visit(finder);

View File

@@ -108,6 +108,33 @@ void UsedExtensionsFinder::OnVisitUnaryOperatorNode(UnaryOperatorNode& node) {
// Add variable extension and visit sub-expressions on variable nodes
void UsedExtensionsFinder::OnVisitVariableNode(VariableNode& node) {
result.GetUsedExtensions().insert("BuiltinVariables");
auto type = gd::ExpressionTypeFinder::GetType(
project.GetCurrentPlatform(), GetObjectsContainersList(), rootType, node);
if (gd::ParameterMetadata::IsExpression("variable", type)) {
// Nothing to do (this can't reference an object)
} else {
GetProjectScopedContainers().MatchIdentifierWithName<void>(node.name,
[&]() {
// This represents an object.
auto metadata = gd::MetadataProvider::GetExtensionAndObjectMetadata(
project.GetCurrentPlatform(), node.name);
result.GetUsedExtensions().insert(metadata.GetExtension().GetName());
for (auto &&includeFile : metadata.GetMetadata().includeFiles) {
result.GetUsedIncludeFiles().insert(includeFile);
}
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
if (node.child) node.child->Visit(*this);
};
@@ -127,9 +154,10 @@ void UsedExtensionsFinder::OnVisitVariableBracketAccessorNode(
// Add extensions bound to Objects/Behaviors/Functions
void UsedExtensionsFinder::OnVisitIdentifierNode(IdentifierNode &node) {
auto type = gd::ExpressionTypeFinder::GetType(
project.GetCurrentPlatform(), GetGlobalObjectsContainer(),
GetObjectsContainer(), rootType, node);
if (gd::ParameterMetadata::IsObject(type)) {
project.GetCurrentPlatform(), GetObjectsContainersList(), rootType, node);
if (gd::ParameterMetadata::IsObject(type) ||
GetObjectsContainersList().HasObjectOrGroupNamed(node.identifierName)) {
// An object or object variable is used.
auto metadata = gd::MetadataProvider::GetExtensionAndObjectMetadata(
project.GetCurrentPlatform(), node.identifierName);
result.GetUsedExtensions().insert(metadata.GetExtension().GetName());

View File

@@ -16,7 +16,9 @@
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
namespace gd {
@@ -86,15 +88,17 @@ void ProjectBrowserHelper::ExposeLayoutEvents(
void ProjectBrowserHelper::ExposeLayoutEvents(
gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorkerWithContext &worker) {
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
// Add layouts events
worker.Launch(layout.GetEvents(), project, layout);
worker.Launch(layout.GetEvents(), projectScopedContainers);
// Add external events events
for (std::size_t s = 0; s < project.GetExternalEventsCount(); s++) {
auto &externalEvents = project.GetExternalEvents(s);
if (externalEvents.GetAssociatedLayout() == layout.GetName()) {
worker.Launch(externalEvents.GetEvents(), project, layout);
worker.Launch(externalEvents.GetEvents(), projectScopedContainers);
}
}
}
@@ -108,15 +112,19 @@ void ProjectBrowserHelper::ExposeProjectEvents(
// Add layouts events
for (std::size_t s = 0; s < project.GetLayoutsCount(); s++) {
auto &layout = project.GetLayout(s);
worker.Launch(layout.GetEvents(), project, layout);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
worker.Launch(layout.GetEvents(), projectScopedContainers);
}
// Add external events events
for (std::size_t s = 0; s < project.GetExternalEventsCount(); s++) {
const auto &externalEvents = project.GetExternalEvents(s);
const gd::String &associatedLayout = externalEvents.GetAssociatedLayout();
if (project.HasLayoutNamed(associatedLayout)) {
worker.Launch(project.GetExternalEvents(s).GetEvents(), project,
project.GetLayout(associatedLayout));
auto &layout = project.GetLayout(associatedLayout);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
worker.Launch(project.GetExternalEvents(s).GetEvents(), projectScopedContainers);
}
}
// Add events based extensions
@@ -130,9 +138,11 @@ void ProjectBrowserHelper::ExposeProjectEvents(
gd::EventsFunctionTools::FreeEventsFunctionToObjectsContainer(
project, eventsFunctionsExtension, *eventsFunction,
globalObjectsAndGroups, objectsAndGroups);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsAndGroups, objectsAndGroups);
projectScopedContainers.AddParameters(eventsFunction->GetParameters());
worker.Launch(eventsFunction->GetEvents(), globalObjectsAndGroups,
objectsAndGroups);
worker.Launch(eventsFunction->GetEvents(), projectScopedContainers);
}
// Add (behavior) events functions
@@ -169,9 +179,13 @@ void ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents(
gd::EventsFunctionTools::BehaviorEventsFunctionToObjectsContainer(
project, eventsBasedBehavior, *eventsFunction, globalObjectsAndGroups,
objectsAndGroups);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsAndGroups, objectsAndGroups);
projectScopedContainers.AddPropertiesContainer(eventsBasedBehavior.GetSharedPropertyDescriptors());
projectScopedContainers.AddPropertiesContainer(eventsBasedBehavior.GetPropertyDescriptors());
projectScopedContainers.AddParameters(eventsFunction->GetParameters());
worker.Launch(eventsFunction->GetEvents(), globalObjectsAndGroups,
objectsAndGroups);
worker.Launch(eventsFunction->GetEvents(), projectScopedContainers);
}
}
@@ -185,9 +199,12 @@ void ProjectBrowserHelper::ExposeEventsBasedObjectEvents(
gd::EventsFunctionTools::ObjectEventsFunctionToObjectsContainer(
project, eventsBasedObject, *eventsFunction, globalObjectsAndGroups,
objectsAndGroups);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsAndGroups, objectsAndGroups);
projectScopedContainers.AddPropertiesContainer(eventsBasedObject.GetPropertyDescriptors());
projectScopedContainers.AddParameters(eventsFunction->GetParameters());
worker.Launch(eventsFunction->GetEvents(), globalObjectsAndGroups,
objectsAndGroups);
worker.Launch(eventsFunction->GetEvents(), projectScopedContainers);
}
}

View File

@@ -5,6 +5,8 @@
*/
#include "WholeProjectRefactorer.h"
#include <unordered_map>
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/PlatformExtension.h"
@@ -15,6 +17,8 @@
#include "GDCore/IDE/Events/CustomObjectTypeRenamer.h"
#include "GDCore/IDE/Events/EventsBehaviorRenamer.h"
#include "GDCore/IDE/Events/EventsRefactorer.h"
#include "GDCore/IDE/Events/EventsPropertyReplacer.h"
#include "GDCore/IDE/Events/EventsVariableReplacer.h"
#include "GDCore/IDE/Events/ExpressionsParameterMover.h"
#include "GDCore/IDE/Events/ExpressionsRenamer.h"
#include "GDCore/IDE/Events/InstructionsParameterMover.h"
@@ -46,6 +50,8 @@
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/ObjectGroup.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
@@ -132,6 +138,72 @@ void WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
}
}
VariablesChangeset WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
gd::Project &project,
const gd::SerializerElement &oldSerializedVariablesContainer,
const gd::VariablesContainer &newVariablesContainer) {
gd::VariablesChangeset changeset;
gd::VariablesContainer oldVariablesContainer;
oldVariablesContainer.UnserializeFrom(oldSerializedVariablesContainer);
if (oldVariablesContainer.GetPersistentUuid() !=
newVariablesContainer.GetPersistentUuid()) {
gd::LogWarning(
_("Called ComputeChangesetForVariablesContainer on variables containers "
"that are different - they can't be compared."));
return changeset;
}
std::unordered_map<gd::String, gd::String> removedUuidAndNames;
for (std::size_t i = 0; i < oldVariablesContainer.Count(); ++i) {
const auto &variable = oldVariablesContainer.Get(i);
const auto &variableName = oldVariablesContainer.GetNameAt(i);
// All variables are candidate to be removed.
removedUuidAndNames[variable.GetPersistentUuid()] = variableName;
}
for (std::size_t i = 0; i < newVariablesContainer.Count(); ++i) {
const auto &variable = newVariablesContainer.Get(i);
const auto &variableName = newVariablesContainer.GetNameAt(i);
auto existingOldVariableUuidAndName =
removedUuidAndNames.find(variable.GetPersistentUuid());
if (existingOldVariableUuidAndName == removedUuidAndNames.end()) {
// This is a new variable.
} else {
const gd::String &oldName = existingOldVariableUuidAndName->second;
if (oldName != variableName) {
// This is a renamed variable.
changeset.oldToNewVariableNames[oldName] = variableName;
}
// Renamed or not, this is not a removed variable.
removedUuidAndNames.erase(variable.GetPersistentUuid());
}
}
for (const auto &removedUuidAndName : removedUuidAndNames) {
changeset.removedVariableNames.insert(removedUuidAndName.second);
}
return changeset;
}
void WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
gd::Project &project,
const gd::VariablesContainer &newVariablesContainer,
const gd::VariablesChangeset& changeset) {
gd::EventsVariableReplacer eventsVariableReplacer(
project.GetCurrentPlatform(),
newVariablesContainer,
changeset.oldToNewVariableNames,
changeset.removedVariableNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
eventsVariableReplacer);
}
void WholeProjectRefactorer::UpdateExtensionNameInEventsBasedBehavior(
gd::Project &project,
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
@@ -671,6 +743,15 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty(
EventsBasedBehavior::GetPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(),
properties,
oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
@@ -698,7 +779,7 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
const gd::EventsBasedBehavior &eventsBasedBehavior,
const gd::String &oldPropertyName, const gd::String &newPropertyName) {
auto &properties = eventsBasedBehavior.GetPropertyDescriptors();
auto &properties = eventsBasedBehavior.GetSharedPropertyDescriptors();
if (!properties.Has(oldPropertyName))
return;
@@ -732,6 +813,15 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
EventsBasedBehavior::GetSharedPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(),
properties,
oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
@@ -780,6 +870,15 @@ void WholeProjectRefactorer::RenameEventsBasedObjectProperty(
EventsBasedObject::GetPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(),
properties,
oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
gd::PlatformExtension::GetObjectEventsFunctionFullType(
@@ -1299,10 +1398,12 @@ void WholeProjectRefactorer::DoRenameObject(
void WholeProjectRefactorer::ObjectOrGroupRemovedInLayout(
gd::Project &project, gd::Layout &layout, const gd::String &objectName,
bool isObjectGroup, bool removeEventsAndGroups) {
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
// Remove object in the current layout
if (removeEventsAndGroups) {
gd::EventsRefactorer::RemoveObjectInEvents(project.GetCurrentPlatform(),
project, layout,
projectScopedContainers,
layout.GetEvents(), objectName);
}
if (!isObjectGroup) { // Object groups can't have instances or be in other
@@ -1321,8 +1422,9 @@ void WholeProjectRefactorer::ObjectOrGroupRemovedInLayout(
for (auto &externalEventsName :
GetAssociatedExternalEvents(project, layout.GetName())) {
auto &externalEvents = project.GetExternalEvents(externalEventsName);
gd::EventsRefactorer::RemoveObjectInEvents(
project.GetCurrentPlatform(), project, layout,
project.GetCurrentPlatform(), projectScopedContainers,
externalEvents.GetEvents(), objectName);
}
}
@@ -1344,9 +1446,12 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInLayout(
const gd::String &newName, bool isObjectGroup) {
if (oldName == newName || newName.empty() || oldName.empty())
return;
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
// Rename object in the current layout
gd::EventsRefactorer::RenameObjectInEvents(
project.GetCurrentPlatform(), project, layout, layout.GetEvents(),
project.GetCurrentPlatform(), projectScopedContainers, layout.GetEvents(),
oldName, newName);
if (!isObjectGroup) { // Object groups can't have instances or be in other
@@ -1362,7 +1467,7 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInLayout(
GetAssociatedExternalEvents(project, layout.GetName())) {
auto &externalEvents = project.GetExternalEvents(externalEventsName);
gd::EventsRefactorer::RenameObjectInEvents(
project.GetCurrentPlatform(), project, layout,
project.GetCurrentPlatform(), projectScopedContainers,
externalEvents.GetEvents(), oldName, newName);
}
@@ -1512,10 +1617,13 @@ void WholeProjectRefactorer::ObjectOrGroupRemovedInEventsFunction(
gd::ObjectsContainer &globalObjectsContainer,
gd::ObjectsContainer &objectsContainer, const gd::String &objectName,
bool isObjectGroup, bool removeEventsAndGroups) {
// Remove object in the current layout
// In theory we should pass a ProjectScopedContainers to this function so it does not have to construct one.
// In practice, this is ok because we only deal with objects.
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsContainer, objectsContainer);
if (removeEventsAndGroups) {
gd::EventsRefactorer::RemoveObjectInEvents(
project.GetCurrentPlatform(), globalObjectsContainer, objectsContainer,
project.GetCurrentPlatform(), projectScopedContainers,
eventsFunction.GetEvents(), objectName);
}
if (!isObjectGroup) { // Object groups can't be in other groups
@@ -1547,9 +1655,12 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction(
gd::ObjectsContainer &globalObjectsContainer,
gd::ObjectsContainer &objectsContainer, const gd::String &oldName,
const gd::String &newName, bool isObjectGroup) {
// Rename object in the current layout
// In theory we should pass a ProjectScopedContainers to this function so it does not have to construct one.
// In practice, this is ok because we only deal with objects.
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsContainer, objectsContainer);
gd::EventsRefactorer::RenameObjectInEvents(
project.GetCurrentPlatform(), globalObjectsContainer, objectsContainer,
project.GetCurrentPlatform(), projectScopedContainers,
eventsFunction.GetEvents(), oldName, newName);
if (!isObjectGroup) { // Object groups can't be in other groups
@@ -1594,7 +1705,7 @@ void WholeProjectRefactorer::GlobalObjectOrGroupRemoved(
if (layout.HasObjectNamed(objectName))
continue;
ObjectOrGroupRemovedInLayout(project, layout, objectName, isObjectGroup,
ObjectOrGroupRemovedInLayout(project, layout, objectName, isObjectGroup,
removeEventsAndGroups);
}
}

View File

@@ -7,7 +7,9 @@
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Platform;
class Project;
@@ -18,6 +20,7 @@ class String;
class EventsFunctionsExtension;
class EventsFunction;
class ObjectsContainer;
class VariablesContainer;
class EventsBasedBehavior;
class EventsBasedObject;
class ArbitraryEventsWorker;
@@ -30,10 +33,20 @@ class Behavior;
class BehaviorMetadata;
class UnfilledRequiredBehaviorPropertyProblem;
class ProjectBrowser;
class SerializerElement;
} // namespace gd
namespace gd {
struct VariablesChangeset {
std::unordered_set<gd::String> removedVariableNames;
std::unordered_map<gd::String, gd::String> oldToNewVariableNames;
bool HasRemovedVariables() { return !removedVariableNames.empty(); }
VariablesChangeset& ClearRemovedVariables() { removedVariableNames.clear(); return *this; }
};
/**
* \brief Tool functions to do refactoring on the whole project after
* changes like deletion or renaming of an object.
@@ -45,6 +58,23 @@ namespace gd {
class GD_CORE_API WholeProjectRefactorer {
public:
/**
* \brief Compute the changes made on the variables of a variable container.
*/
static VariablesChangeset ComputeChangesetForVariablesContainer(
gd::Project &project,
const gd::SerializerElement &oldSerializedVariablesContainer,
const gd::VariablesContainer &newVariablesContainer);
/**
* \brief Refactor the project according to the changes (renaming or deletion)
* made to variables.
*/
static void ApplyRefactoringForVariablesContainer(
gd::Project &project,
const gd::VariablesContainer &newVariablesContainer,
const gd::VariablesChangeset& changeset);
/**
* \brief Refactor the project **before** an events function extension is
* renamed.

View File

@@ -4,6 +4,7 @@
* reserved. This project is released under the MIT License.
*/
#include "AbstractEventsBasedEntity.h"
#include "EventsFunctionsContainer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/MakeUnique.h"
@@ -13,7 +14,10 @@ namespace gd {
AbstractEventsBasedEntity::AbstractEventsBasedEntity(
const gd::String& _name,
gd::EventsFunctionsContainer::FunctionOwner functionContainerSource)
: name(_name), fullName(""), eventsFunctionsContainer(functionContainerSource) {}
: name(_name),
fullName(""),
eventsFunctionsContainer(functionContainerSource),
propertyDescriptors(functionContainerSource) {}
void AbstractEventsBasedEntity::SerializeTo(SerializerElement& element) const {
element.SetAttribute("description", description);
@@ -27,8 +31,8 @@ void AbstractEventsBasedEntity::SerializeTo(SerializerElement& element) const {
"propertyDescriptor", element.AddChild("propertyDescriptors"));
}
void AbstractEventsBasedEntity::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
void AbstractEventsBasedEntity::UnserializeFrom(
gd::Project& project, const SerializerElement& element) {
description = element.GetStringAttribute("description");
name = element.GetStringAttribute("name");
fullName = element.GetStringAttribute("fullName");

View File

@@ -8,7 +8,7 @@
#include <vector>
#include "GDCore/Project/NamedPropertyDescriptor.h"
#include "GDCore/Tools/SerializableWithNameList.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/String.h"
namespace gd {
@@ -98,15 +98,14 @@ class GD_CORE_API AbstractEventsBasedEntity {
/**
* \brief Return a reference to the list of the properties.
*/
SerializableWithNameList<NamedPropertyDescriptor>& GetPropertyDescriptors() {
gd::PropertiesContainer& GetPropertyDescriptors() {
return propertyDescriptors;
}
/**
* \brief Return a const reference to the list of the properties.
*/
const SerializableWithNameList<NamedPropertyDescriptor>& GetPropertyDescriptors()
const {
const gd::PropertiesContainer& GetPropertyDescriptors() const {
return propertyDescriptors;
}
@@ -150,7 +149,7 @@ class GD_CORE_API AbstractEventsBasedEntity {
gd::String fullName;
gd::String description;
gd::EventsFunctionsContainer eventsFunctionsContainer;
SerializableWithNameList<NamedPropertyDescriptor> propertyDescriptors;
gd::PropertiesContainer propertyDescriptors;
gd::String extensionName;
};

View File

@@ -31,10 +31,10 @@ namespace gd {
*/
class GD_CORE_API BehaviorConfigurationContainer {
public:
BehaviorConfigurationContainer(){};
BehaviorConfigurationContainer(const gd::String& name_, const gd::String& type_)
: name(name_), type(type_){};
BehaviorConfigurationContainer() : folded(false){};
BehaviorConfigurationContainer(const gd::String& name_,
const gd::String& type_)
: name(name_), type(type_), folded(false){};
virtual ~BehaviorConfigurationContainer();
virtual BehaviorConfigurationContainer* Clone() const { return new BehaviorConfigurationContainer(*this); }
@@ -61,7 +61,7 @@ class GD_CORE_API BehaviorConfigurationContainer {
/**
* \brief Called when the IDE wants to know about the custom properties of the
* behavior.
*
*
* \return a std::map with properties names as key.
* \see gd::PropertyDescriptor
*/
@@ -104,6 +104,17 @@ class GD_CORE_API BehaviorConfigurationContainer {
const gd::SerializerElement& GetContent() const { return content; };
gd::SerializerElement& GetContent() { return content; };
/**
* \brief Set if the behavior configuration panel should be folded in the UI.
*/
void SetFolded(bool fold = true) { folded = fold; }
/**
* \brief True if the behavior configuration panel should be folded in the UI.
*/
bool IsFolded() const { return folded; }
protected:
/**
* \brief Called when the IDE wants to know about the custom properties of the
@@ -148,6 +159,7 @@ protected:
///< in the form "ExtensionName::BehaviorTypeName"
gd::SerializerElement content; // Storage for the behavior properties
bool folded;
};
} // namespace gd

View File

@@ -8,6 +8,7 @@
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Serialization/SerializerElement.h"
@@ -17,7 +18,7 @@
using namespace gd;
void CustomConfigurationHelper::InitializeContent(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
gd::SerializerElement &configurationContent) {
for (auto &&property : properties.GetInternalVector()) {
auto &element = configurationContent.AddChild(property->GetName());
@@ -35,7 +36,7 @@ void CustomConfigurationHelper::InitializeContent(
}
std::map<gd::String, gd::PropertyDescriptor> CustomConfigurationHelper::GetProperties(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
const gd::SerializerElement &configurationContent) {
auto behaviorProperties = std::map<gd::String, gd::PropertyDescriptor>();
@@ -71,7 +72,7 @@ std::map<gd::String, gd::PropertyDescriptor> CustomConfigurationHelper::GetPrope
}
bool CustomConfigurationHelper::UpdateProperty(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
gd::SerializerElement &configurationContent,
const gd::String &propertyName,
const gd::String &newValue) {

View File

@@ -9,6 +9,7 @@
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/EventsBasedBehavior.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Serialization/SerializerElement.h"
@@ -25,15 +26,15 @@ public:
CustomConfigurationHelper() {}
static void InitializeContent(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
gd::SerializerElement &behaviorContent);
static std::map<gd::String, gd::PropertyDescriptor> GetProperties(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
const gd::SerializerElement &behaviorContent);
static bool UpdateProperty(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
gd::SerializerElement &behaviorContent,
const gd::String &name,
const gd::String &value);

View File

@@ -13,7 +13,8 @@ namespace gd {
EventsBasedBehavior::EventsBasedBehavior()
: AbstractEventsBasedEntity(
"MyBehavior",
gd::EventsFunctionsContainer::FunctionOwner::Behavior) {}
gd::EventsFunctionsContainer::FunctionOwner::Behavior),
sharedPropertyDescriptors(gd::EventsFunctionsContainer::FunctionOwner::Behavior) {}
void EventsBasedBehavior::SerializeTo(SerializerElement& element) const {
AbstractEventsBasedEntity::SerializeTo(element);

View File

@@ -9,7 +9,7 @@
#include <vector>
#include "GDCore/Project/AbstractEventsBasedEntity.h"
#include "GDCore/Project/NamedPropertyDescriptor.h"
#include "GDCore/Tools/SerializableWithNameList.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/String.h"
namespace gd {
@@ -91,14 +91,14 @@ class GD_CORE_API EventsBasedBehavior: public AbstractEventsBasedEntity {
/**
* \brief Return a reference to the list of shared properties.
*/
SerializableWithNameList<NamedPropertyDescriptor>& GetSharedPropertyDescriptors() {
gd::PropertiesContainer& GetSharedPropertyDescriptors() {
return sharedPropertyDescriptors;
}
/**
* \brief Return a const reference to the list of shared properties.
*/
const SerializableWithNameList<NamedPropertyDescriptor>& GetSharedPropertyDescriptors()
const gd::PropertiesContainer& GetSharedPropertyDescriptors()
const {
return sharedPropertyDescriptors;
}
@@ -140,7 +140,7 @@ class GD_CORE_API EventsBasedBehavior: public AbstractEventsBasedEntity {
private:
gd::String objectType;
bool isPrivate = false;
SerializableWithNameList<NamedPropertyDescriptor> sharedPropertyDescriptors;
gd::PropertiesContainer sharedPropertyDescriptors;
};
} // namespace gd

View File

@@ -13,19 +13,75 @@
namespace gd {
class Effect;
}
namespace gd {
class Camera;
}
namespace gd {
class SerializerElement;
}
namespace gd {
class EffectsContainer;
}
namespace gd {
/**
* \brief A camera is used to render a specific area of a layout.
*
* \see gd::Layout
* \ingroup PlatformDefinition
*/
class GD_CORE_API Camera {
public:
Camera();
~Camera(){};
/**
* \brief Change the viewport, i.e the area of the window where the camera
* will be displayed. \note The coordinates must be between 0 and 1.
*/
void SetViewport(float x1_, float y1_, float x2_, float y2_) {
x1 = x1_;
x2 = x2_;
y1 = y1_;
y2 = y2_;
};
void SetViewportX1(float x1_) { x1 = x1_; };
void SetViewportY1(float y1_) { y1 = y1_; };
void SetViewportX2(float x2_) { x2 = x2_; };
void SetViewportY2(float y2_) { y2 = y2_; };
float GetViewportX1() const { return x1; };
float GetViewportY1() const { return y1; };
float GetViewportX2() const { return x2; };
float GetViewportY2() const { return y2; };
/**
* \brief Change the size of the rendered area of the scene, in pixels.
*/
void SetSize(float width_, float height_) {
width = width_;
height = height_;
};
float GetWidth() const { return width; };
float GetHeight() const { return height; };
void SetUseDefaultSize(bool useDefaultSize = true) {
defaultSize = useDefaultSize;
};
bool UseDefaultSize() const { return defaultSize; }
void SetUseDefaultViewport(bool useDefaultViewport = true) {
defaultViewport = useDefaultViewport;
};
bool UseDefaultViewport() const { return defaultViewport; }
private:
bool defaultSize; ///< True if the camera use the default window size
bool defaultViewport; ///< True if the camera use the default viewport size
float x1;
float y1;
float x2;
float y2;
float width; ///< The width of the rendered area
float height; ///< The height of the rendered area
};
/**
* \brief Represents a layer of a layout.
*
@@ -241,68 +297,6 @@ struct LayerHasName : public std::binary_function<gd::Layer, gd::String, bool> {
}
};
/**
* \brief A camera is used to render a specific area of a layout.
*
* \see gd::Layout
* \ingroup PlatformDefinition
*/
class GD_CORE_API Camera {
public:
Camera();
~Camera(){};
/**
* \brief Change the viewport, i.e the area of the window where the camera
* will be displayed. \note The coordinates must be between 0 and 1.
*/
void SetViewport(float x1_, float y1_, float x2_, float y2_) {
x1 = x1_;
x2 = x2_;
y1 = y1_;
y2 = y2_;
};
void SetViewportX1(float x1_) { x1 = x1_; };
void SetViewportY1(float y1_) { y1 = y1_; };
void SetViewportX2(float x2_) { x2 = x2_; };
void SetViewportY2(float y2_) { y2 = y2_; };
float GetViewportX1() const { return x1; };
float GetViewportY1() const { return y1; };
float GetViewportX2() const { return x2; };
float GetViewportY2() const { return y2; };
/**
* \brief Change the size of the rendered area of the scene, in pixels.
*/
void SetSize(float width_, float height_) {
width = width_;
height = height_;
};
float GetWidth() const { return width; };
float GetHeight() const { return height; };
void SetUseDefaultSize(bool useDefaultSize = true) {
defaultSize = useDefaultSize;
};
bool UseDefaultSize() const { return defaultSize; }
void SetUseDefaultViewport(bool useDefaultViewport = true) {
defaultViewport = useDefaultViewport;
};
bool UseDefaultViewport() const { return defaultViewport; }
private:
bool defaultSize; ///< True if the camera use the default window size
bool defaultViewport; ///< True if the camera use the default viewport size
float x1;
float y1;
float x2;
float y2;
float width; ///< The width of the rendered area
float height; ///< The height of the rendered area
};
} // namespace gd
#endif // GDCORE_LAYER_H

View File

@@ -742,7 +742,7 @@ gd::String GD_CORE_API GetTypeOfBehavior(const gd::ObjectsContainer& project,
vector<gd::String> GD_CORE_API
GetBehaviorsOfObject(const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::String name,
const gd::String& name,
bool searchInGroups) {
bool behaviorsAlreadyInserted = false;
vector<gd::String> behaviors;

View File

@@ -435,6 +435,7 @@ std::vector<gd::String> GetHiddenLayers(const Layout& layout);
* \note If a group contains only objects of a same type, then the group has
* this type. Otherwise, it is considered as an object without any specific
* type.
* \deprecated Use gd::ObjectsContainersList::GetTypeOfObject instead.
*
* @return Type of the object/group.
*/
@@ -444,6 +445,7 @@ gd::String GD_CORE_API GetTypeOfObject(const ObjectsContainer& game,
bool searchInGroups = true);
/**
* \brief Check if an object or all objects of a group has a behavior.
* \deprecated Use gd::ObjectsContainersList::HasBehaviorInObjectOrGroup instead.
*/
bool GD_CORE_API HasBehaviorInObjectOrGroup(const gd::ObjectsContainer &project,
const gd::ObjectsContainer &layout,
@@ -479,6 +481,7 @@ gd::String GD_CORE_API GetTypeOfBehaviorInObjectOrGroup(const gd::ObjectsContain
/**
* \brief Get a type from a behavior name
* @return Type of the behavior.
* @deprecated - Use GetTypeOfBehaviorInObjectOrGroup instead.
*/
gd::String GD_CORE_API GetTypeOfBehavior(const ObjectsContainer& game,
const ObjectsContainer& layout,
@@ -495,7 +498,7 @@ gd::String GD_CORE_API GetTypeOfBehavior(const ObjectsContainer& game,
std::vector<gd::String> GD_CORE_API
GetBehaviorsOfObject(const ObjectsContainer& game,
const ObjectsContainer& layout,
gd::String objectName,
const gd::String& objectName,
bool searchInGroups = true);
} // namespace gd

View File

@@ -5,7 +5,6 @@
*/
#include "LoadingScreen.h"
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {

View File

@@ -15,6 +15,7 @@
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Tools/Log.h"
#include "GDCore/Tools/UUID/UUID.h"
namespace gd {
@@ -35,6 +36,7 @@ Object::Object(const gd::String& name_,
}
void Object::Init(const gd::Object& object) {
persistentUuid = object.persistentUuid;
name = object.name;
assetStoreId = object.assetStoreId;
objectVariables = object.objectVariables;
@@ -127,6 +129,8 @@ gd::Behavior* Object::AddNewBehavior(const gd::Project& project,
void Object::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
persistentUuid = element.GetStringAttribute("persistentUuid");
SetType(element.GetStringAttribute("type"));
assetStoreId = element.GetStringAttribute("assetStoreId");
name = element.GetStringAttribute("name", name, "nom");
@@ -197,6 +201,9 @@ void Object::UnserializeFrom(gd::Project& project,
}
void Object::SerializeTo(SerializerElement& element) const {
if (!persistentUuid.empty())
element.SetStringAttribute("persistentUuid", persistentUuid);
element.SetAttribute("name", GetName());
element.SetAttribute("assetStoreId", GetAssetStoreId());
element.SetAttribute("type", GetType());
@@ -227,4 +234,18 @@ void Object::SerializeTo(SerializerElement& element) const {
configuration->SerializeTo(element);
}
Object& Object::ResetPersistentUuid() {
persistentUuid = UUID::MakeUuid4();
objectVariables.ResetPersistentUuid();
return *this;
}
Object& Object::ClearPersistentUuid() {
persistentUuid = "";
objectVariables.ClearPersistentUuid();
return *this;
}
} // namespace gd

View File

@@ -243,6 +243,18 @@ class GD_CORE_API Object {
* \see DoUnserializeFrom
*/
void UnserializeFrom(gd::Project& project, const SerializerElement& element);
/**
* \brief Reset the persistent UUID, used to recognize
* the same object between serialization.
*/
Object& ResetPersistentUuid();
/**
* \brief Remove the persistent UUID - when the object no
* longer need to be recognized between serializations.
*/
Object& ClearPersistentUuid();
///@}
protected:
@@ -259,6 +271,8 @@ class GD_CORE_API Object {
gd::String tags; ///< Comma-separated list of tags
gd::EffectsContainer
effectsContainer; ///< The effects container for the object.
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
///< useful for computing changesets.
/**
* Initialize object using another object. Used by copy-ctor and assign-op.

View File

@@ -162,4 +162,11 @@ void ObjectGroupsContainer::Move(std::size_t oldIndex, std::size_t newIndex) {
objectGroups.insert(objectGroups.begin() + newIndex, std::move(objectGroup));
}
void ObjectGroupsContainer::ForEachNameWithPrefix(const gd::String& prefix,
std::function<void(const gd::String& name)> fn) const {
for (const auto& objectGroup: objectGroups) {
if (objectGroup->GetName().find(prefix) == 0) fn(objectGroup->GetName());
}
}
} // namespace gd

View File

@@ -116,6 +116,11 @@ class GD_CORE_API ObjectGroupsContainer {
* \brief Clear all groups of the container.
*/
inline void Clear() { objectGroups.clear(); }
/**
* \brief Call the callback for each group name starting with the prefix passed in parameter.
*/
void ForEachNameWithPrefix(const gd::String& prefix, std::function<void(const gd::String& name)> fn) const;
///@}
/** \name Saving and loading

View File

@@ -0,0 +1,250 @@
#include "ObjectsContainersList.h"
#include <vector>
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace gd {
ObjectsContainersList
ObjectsContainersList::MakeNewObjectsContainersListForProjectAndLayout(
const gd::Project& project, const gd::Layout& layout) {
ObjectsContainersList objectsContainersList;
objectsContainersList.Add(project);
objectsContainersList.Add(layout);
return objectsContainersList;
}
ObjectsContainersList
ObjectsContainersList::MakeNewObjectsContainersListForContainers(
const gd::ObjectsContainer& globalObjectsContainer,
const gd::ObjectsContainer& objectsContainer) {
ObjectsContainersList objectsContainersList;
objectsContainersList.Add(globalObjectsContainer);
objectsContainersList.Add(objectsContainer);
return objectsContainersList;
}
bool ObjectsContainersList::HasObjectOrGroupNamed(
const gd::String& name) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(name) || (*it)->GetObjectGroups().Has(name))
return true;
}
return false;
}
bool ObjectsContainersList::HasObjectNamed(const gd::String& name) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(name)) return true;
}
return false;
}
bool ObjectsContainersList::HasObjectOrGroupWithVariableNamed(
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.Has(variableName);
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// Could be adapted if objects groups have variables in the future.
}
}
return false;
}
bool ObjectsContainersList::HasVariablesContainer(
const gd::String& objectOrGroupName,
const gd::VariablesContainer& variablesContainer) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
return &variablesContainer ==
&(*it)->GetObject(objectOrGroupName).GetVariables();
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// Could be adapted if objects groups have variables in the future.
}
}
return false;
}
const gd::VariablesContainer*
ObjectsContainersList::GetObjectOrGroupVariablesContainer(
const gd::String& objectOrGroupName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
return &(*it)->GetObject(objectOrGroupName).GetVariables();
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// Could be adapted if objects groups have variables in the future.
}
}
return nullptr;
}
void ObjectsContainersList::ForEachNameWithPrefix(
const gd::String& prefix,
std::function<void(const gd::String& name,
const gd::ObjectConfiguration* objectConfiguration)> fn)
const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
for (const auto& object : (*it)->GetObjects()) {
if (object->GetName().find(prefix) == 0)
fn(object->GetName(), &object->GetConfiguration());
}
(*it)->GetObjectGroups().ForEachNameWithPrefix(
prefix, [&](const gd::String& name) { fn(name, nullptr); });
}
}
std::vector<gd::String> ObjectsContainersList::ExpandObjectName(
const gd::String& objectOrGroupName,
const gd::String& onlyObjectToSelectIfPresent) const {
std::vector<gd::String> realObjects;
// Check progressively each object container to find the object or the group
// with the specified name.
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
// We found the object, it's a single object with this name.
realObjects.push_back(objectOrGroupName);
break;
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// We found a group with this name, expand the object names inside of it.
realObjects =
(*it)->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
break;
}
}
// If the "current object" is present, use it and only it.
if (!onlyObjectToSelectIfPresent.empty() &&
find(realObjects.begin(),
realObjects.end(),
onlyObjectToSelectIfPresent) != realObjects.end()) {
realObjects.clear();
realObjects.push_back(onlyObjectToSelectIfPresent);
}
// Ensure that all returned objects actually exists (i.e: if some groups have
// names refering to non existing objects, don't return them).
for (std::size_t i = 0; i < realObjects.size();) {
if (!HasObjectNamed(realObjects[i]))
realObjects.erase(realObjects.begin() + i);
else
++i;
}
return realObjects;
}
void ObjectsContainersList::ForEachObject(
std::function<void(const gd::Object& object)> fn) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
const auto& objectsContainer = **it;
for (const auto& object : objectsContainer.GetObjects()) {
fn(*object);
}
}
}
gd::String ObjectsContainersList::GetTypeOfObject(
const gd::String& objectName) const {
if (objectsContainers.size() != 2) {
std::cout << this << std::endl;
std::cout << objectsContainers.size() << std::endl;
// TODO: rework forwarded methods so they can work with any number of
// containers.
gd::LogFatalError(
"ObjectsContainersList::GetTypeOfObject called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
return gd::GetTypeOfObject(
*objectsContainers[0], *objectsContainers[1], objectName, true);
}
bool ObjectsContainersList::HasBehaviorInObjectOrGroup(
const gd::String& objectOrGroupName, const gd::String& behaviorName) const {
if (objectsContainers.size() != 2) {
// TODO: rework forwarded methods so they can work with any number of
// containers.
gd::LogFatalError(
"ObjectsContainersList::GetTypeOfObject called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
return gd::HasBehaviorInObjectOrGroup(*objectsContainers[0],
*objectsContainers[1],
objectOrGroupName,
behaviorName,
true);
}
gd::String ObjectsContainersList::GetTypeOfBehaviorInObjectOrGroup(
const gd::String& objectOrGroupName,
const gd::String& behaviorName,
bool searchInGroups) const {
if (objectsContainers.size() != 2) {
// TODO: rework forwarded methods so they can work with any number of
// containers.
gd::LogFatalError(
"ObjectsContainersList::GetTypeOfObject called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
return gd::GetTypeOfBehaviorInObjectOrGroup(*objectsContainers[0],
*objectsContainers[1],
objectOrGroupName,
behaviorName,
searchInGroups);
}
gd::String ObjectsContainersList::GetTypeOfBehavior(
const gd::String& behaviorName, bool searchInGroups) const {
if (objectsContainers.size() != 2) {
// TODO: rework forwarded methods so they can work with any number of
// containers.
gd::LogFatalError(
"ObjectsContainersList::GetTypeOfObject called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
return gd::GetTypeOfBehavior(
*objectsContainers[0], *objectsContainers[1], behaviorName, searchInGroups);
}
std::vector<gd::String> ObjectsContainersList::GetBehaviorsOfObject(
const gd::String& objectName, bool searchInGroups) const {
if (objectsContainers.size() != 2) {
// TODO: rework forwarded methods so they can work with any number of
// containers.
gd::LogFatalError(
"ObjectsContainersList::GetTypeOfObject called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
return gd::GetBehaviorsOfObject(
*objectsContainers[0], *objectsContainers[1], objectName, searchInGroups);
}
} // namespace gd

View File

@@ -0,0 +1,142 @@
#pragma once
#include <vector>
#include "Variable.h"
namespace gd {
class String;
class Project;
class Layout;
class ObjectsContainer;
class VariablesContainer;
class Object;
class ObjectConfiguration;
} // namespace gd
namespace gd {
/**
* \brief A list of objects containers, useful for accessing objects in a
* scoped way, along with methods to access them.
*
* \see gd::Object
* \see gd::ObjectsContainer
* \see gd::Project
* \see gd::Layout
*
* \ingroup PlatformDefinition
*/
class GD_CORE_API ObjectsContainersList {
public:
virtual ~ObjectsContainersList(){};
static ObjectsContainersList MakeNewObjectsContainersListForProjectAndLayout(
const gd::Project& project, const gd::Layout& layout);
static ObjectsContainersList MakeNewObjectsContainersListForContainers(
const gd::ObjectsContainer& globalObjectsContainer,
const gd::ObjectsContainer& objectsContainer);
/**
* \brief Check if the specified object or group exists.
*/
bool HasObjectOrGroupNamed(const gd::String& name) const;
/**
* \brief Check if the specified object or group has the specified variable in
* its declared variables.
*/
bool HasObjectOrGroupWithVariableNamed(const gd::String& objectOrGroupName,
const gd::String& variableName) const;
/**
* \brief Check if the specified object or group has the specified variables
* container.
*/
bool HasVariablesContainer(
const gd::String& objectOrGroupName,
const gd::VariablesContainer& variablesContainer) const;
/**
* \brief Return the container of the variables for the specified object or
* group of objects.
*/
const gd::VariablesContainer* GetObjectOrGroupVariablesContainer(
const gd::String& objectOrGroupName) 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
* this type. Otherwise, it is considered as an object without any specific
* type.
*
* @return Type of the object/group.
*/
gd::String GetTypeOfObject(const gd::String& objectName) const;
/**
* \brief Check if an object or all object of a group has a behavior.
*/
bool HasBehaviorInObjectOrGroup(const gd::String& objectOrGroupName,
const gd::String& behaviorName) const;
/**
* \brief Get the type of a behavior if an object or all objects of a group has it.
*/
gd::String GetTypeOfBehaviorInObjectOrGroup(const gd::String &objectOrGroupName,
const gd::String &behaviorName,
bool searchInGroups = true) const;
/**
* \brief Get a type from a behavior name
* @return Type of the behavior.
* @deprecated - Use GetTypeOfBehaviorInObjectOrGroup instead.
*/
gd::String GetTypeOfBehavior(const gd::String& behaviorName, bool searchInGroups = true) const;
/**
* \brief Get behaviors of an object/group
* \note The behaviors of a group are the behaviors which are found in common
* when looking all the objects of the group.
*
* @return Vector containing names of behaviors
*/
std::vector<gd::String>
GetBehaviorsOfObject(const gd::String& objectName,
bool searchInGroups = true) const;
/**
* \brief Return a list containing all objects refered to by the group.
* If an object name is passed, then only this object name is returned.
*
* If \a onlyObjectToSelectIfPresent is set and present in the group(s),
* only this object will be returned. This is useful for considering this
* object as the "currently selected" object, when generating a condition or
* an action.
*/
std::vector<gd::String> ExpandObjectName(
const gd::String& objectOrGroupName,
const gd::String& onlyObjectToSelectIfPresent = "") const;
void ForEachObject(std::function<void(const gd::Object& object)> fn) const;
/**
* \brief Call the callback for each object or group name starting with the prefix passed in parameter.
*/
void ForEachNameWithPrefix(const gd::String& prefix, std::function<void(const gd::String& name, const gd::ObjectConfiguration* objectConfiguration)> fn) const;
/** Do not use - should be private but accessible to let Emscripten create a
* temporary. */
ObjectsContainersList(){};
private:
bool HasObjectNamed(const gd::String& name) const;
void Add(const gd::ObjectsContainer& objectsContainer) {
objectsContainers.push_back(&objectsContainer);
};
std::vector<const gd::ObjectsContainer*> objectsContainers;
};
} // namespace gd

View File

@@ -0,0 +1,174 @@
#pragma once
#include <set>
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
#include "ObjectsContainersList.h"
#include "PropertiesContainersList.h"
#include "VariablesContainersList.h"
namespace gd {
class Project;
class ObjectsContainer;
class ObjectsContainersList;
class VariablesContainersList;
class PropertiesContainersList;
class NamedPropertyDescriptor;
} // namespace gd
namespace gd {
/**
* \brief Holds references to variables, objects, properties and other
* containers.
*
* This is useful to access elements of a project from a specific location,
* honoring the scope of each element.
*
* For example, in an expression, when an identifier is written, this class is
* used to know what this identifier refers too.
*/
class ProjectScopedContainers {
public:
ProjectScopedContainers(
const gd::ObjectsContainersList &objectsContainersList_,
const gd::VariablesContainersList &variablesContainersList_,
const gd::PropertiesContainersList &propertiesContainersList_)
: objectsContainersList(objectsContainersList_),
variablesContainersList(variablesContainersList_),
propertiesContainersList(propertiesContainersList_){};
virtual ~ProjectScopedContainers(){};
static ProjectScopedContainers
MakeNewProjectScopedContainersForProjectAndLayout(const gd::Project &project,
const gd::Layout &layout) {
ProjectScopedContainers projectScopedContainers(
ObjectsContainersList::MakeNewObjectsContainersListForProjectAndLayout(
project, layout),
VariablesContainersList::
MakeNewVariablesContainersListForProjectAndLayout(project, layout),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
return projectScopedContainers;
}
static ProjectScopedContainers MakeNewProjectScopedContainersFor(
const gd::ObjectsContainer &globalObjectsContainers,
const gd::ObjectsContainer &objectsContainers) {
ProjectScopedContainers projectScopedContainers(
ObjectsContainersList::MakeNewObjectsContainersListForContainers(
globalObjectsContainers, objectsContainers),
VariablesContainersList::MakeNewEmptyVariablesContainersList(),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
return projectScopedContainers;
}
ProjectScopedContainers &AddPropertiesContainer(
const gd::PropertiesContainer &container) {
propertiesContainersList.Add(container);
return *this;
}
ProjectScopedContainers &AddParameters(
const std::vector<gd::ParameterMetadata> &parameters) {
parametersVectorsList.push_back(&parameters);
return *this;
}
template <class ReturnType>
ReturnType MatchIdentifierWithName(
const gd::String &name,
std::function<ReturnType()> objectCallback,
std::function<ReturnType()> variableCallback,
std::function<ReturnType()> propertyCallback,
std::function<ReturnType()> parameterCallback,
std::function<ReturnType()> notFoundCallback) const {
if (objectsContainersList.HasObjectOrGroupNamed(name))
return objectCallback();
else if (variablesContainersList.Has(name))
return variableCallback();
else if (ParameterMetadataTools::Has(parametersVectorsList, name))
return parameterCallback();
else if (propertiesContainersList.Has(name))
return propertyCallback();
return notFoundCallback();
};
void ForEachIdentifierWithPrefix(
const gd::String &prefix,
std::function<void(const gd::String &name,
const ObjectConfiguration *objectConfiguration)>
objectCallback,
std::function<void(const gd::String &name, const gd::Variable &variable)>
variableCallback,
std::function<void(const gd::NamedPropertyDescriptor &property)>
propertyCallback,
std::function<void(const gd::ParameterMetadata &parameter)>
parameterCallback) const {
std::set<gd::String> namesAlreadySeen;
objectsContainersList.ForEachNameWithPrefix(
prefix,
[&](const gd::String &name,
const ObjectConfiguration *objectConfiguration) {
if (namesAlreadySeen.count(name) == 0) {
namesAlreadySeen.insert(name);
objectCallback(name, objectConfiguration);
}
});
variablesContainersList.ForEachVariableWithPrefix(
prefix, [&](const gd::String &name, const gd::Variable &variable) {
if (namesAlreadySeen.count(name) == 0) {
namesAlreadySeen.insert(name);
variableCallback(name, variable);
}
});
gd::ParameterMetadataTools::ForEachParameterWithPrefix(
parametersVectorsList,
prefix,
[&](const gd::ParameterMetadata &parameter) {
if (namesAlreadySeen.count(parameter.GetName()) == 0) {
namesAlreadySeen.insert(parameter.GetName());
parameterCallback(parameter);
}
});
propertiesContainersList.ForEachPropertyWithPrefix(
prefix, [&](const gd::NamedPropertyDescriptor &property) {
if (namesAlreadySeen.count(property.GetName()) == 0) {
namesAlreadySeen.insert(property.GetName());
propertyCallback(property);
}
});
};
const gd::ObjectsContainersList &GetObjectsContainersList() const {
return objectsContainersList;
};
const gd::VariablesContainersList &GetVariablesContainersList() const {
return variablesContainersList;
};
const gd::PropertiesContainersList &GetPropertiesContainersList() const {
return propertiesContainersList;
};
const std::vector<const std::vector<gd::ParameterMetadata> *> &GetParametersVectorsList() const {
return parametersVectorsList;
};
/** Do not use - should be private but accessible to let Emscripten create a
* temporary. */
ProjectScopedContainers(){};
private:
gd::ObjectsContainersList objectsContainersList;
gd::VariablesContainersList variablesContainersList;
gd::PropertiesContainersList propertiesContainersList;
std::vector<const std::vector<gd::ParameterMetadata> *> parametersVectorsList;
};
} // namespace gd

View File

@@ -0,0 +1,46 @@
#pragma once
#include "EventsFunctionsContainer.h"
#include "GDCore/Tools/SerializableWithNameList.h"
#include "NamedPropertyDescriptor.h"
namespace gd {
/**
* \brief A container of properties, used for custom behaviors, custom objects,
* extensions...
*
* \see gd::NamedPropertyDescriptor
*
* \ingroup PlatformDefinition
*/
class PropertiesContainer
: public SerializableWithNameList<NamedPropertyDescriptor> {
public:
PropertiesContainer(EventsFunctionsContainer::FunctionOwner owner)
: SerializableWithNameList<NamedPropertyDescriptor>(), owner(owner) {}
PropertiesContainer(const PropertiesContainer& other)
: SerializableWithNameList<NamedPropertyDescriptor>(other),
owner(other.owner) {}
PropertiesContainer& operator=(const PropertiesContainer& other) {
if (this != &other) {
SerializableWithNameList<NamedPropertyDescriptor>::operator=(other);
owner = other.owner;
}
return *this;
}
void ForEachPropertyWithPrefix(const gd::String& prefix, std::function<void(const gd::NamedPropertyDescriptor& property)> fn) const {
for (const auto& property: elements) {
if (property->GetName().find(prefix) == 0) fn(*property);
}
}
EventsFunctionsContainer::FunctionOwner GetOwner() const { return owner; }
private:
EventsFunctionsContainer::FunctionOwner owner;
};
} // namespace gd

View File

@@ -0,0 +1,67 @@
#include "PropertiesContainersList.h"
#include <functional>
#include <vector>
#include "PropertiesContainer.h"
namespace gd {
NamedPropertyDescriptor PropertiesContainersList::badNamedPropertyDescriptor;
PropertiesContainer PropertiesContainersList::badPropertiesContainer(gd::EventsFunctionsContainer::FunctionOwner::Extension);
PropertiesContainersList
PropertiesContainersList::MakeNewPropertiesContainersListFor(
const gd::PropertiesContainer& propertiesContainer) {
PropertiesContainersList propertiesContainersList;
propertiesContainersList.Add(propertiesContainer);
return propertiesContainersList;
}
PropertiesContainersList
PropertiesContainersList::MakeNewEmptyPropertiesContainersList() {
PropertiesContainersList propertiesContainersList;
return propertiesContainersList;
}
bool PropertiesContainersList::Has(const gd::String& name) const {
for (auto it = propertiesContainers.rbegin();
it != propertiesContainers.rend();
++it) {
if ((*it)->Has(name)) return true;
}
return false;
}
std::pair<std::reference_wrapper<const gd::PropertiesContainer>,
std::reference_wrapper<const NamedPropertyDescriptor>>
PropertiesContainersList::Get(const gd::String& name) const {
for (auto it = propertiesContainers.rbegin();
it != propertiesContainers.rend();
++it) {
if ((*it)->Has(name)) return {**it, (*it)->Get(name)};
}
return {badPropertiesContainer, badNamedPropertyDescriptor};
}
bool PropertiesContainersList::HasPropertiesContainer(const gd::PropertiesContainer& propertiesContainer) const {
for (auto it = propertiesContainers.rbegin(); it != propertiesContainers.rend();
++it) {
if (*it == &propertiesContainer) return true;
}
return false;
}
void PropertiesContainersList::ForEachPropertyWithPrefix(
const gd::String& prefix,
std::function<void(const gd::NamedPropertyDescriptor& property)> fn) const {
for (auto it = propertiesContainers.rbegin(); it != propertiesContainers.rend();
++it) {
(*it)->ForEachPropertyWithPrefix(prefix, fn);
}
}
} // namespace gd

View File

@@ -0,0 +1,84 @@
#pragma once
#include <functional>
#include <vector>
#include "PropertiesContainer.h"
namespace gd {
class String;
class Project;
class Layout;
class NamedPropertyDescriptor;
} // namespace gd
namespace gd {
/**
* \brief A list of property containers, useful for accessing properties in a
* scoped way.
*
* \see gd::NamedPropertyDescriptor
* \see gd::Project
* \see gd::Layout
*
* \ingroup PlatformDefinition
*/
class GD_CORE_API PropertiesContainersList {
public:
virtual ~PropertiesContainersList(){};
static PropertiesContainersList MakeNewPropertiesContainersListFor(
const gd::PropertiesContainer& propertiesContainer);
static PropertiesContainersList MakeNewEmptyPropertiesContainersList();
/**
* \brief Add a new container of properties in the list.
* Add containers in order from the most global one to the most local one.
*/
void Add(const gd::PropertiesContainer& propertiesContainer) {
propertiesContainers.push_back(&propertiesContainer);
};
/**
* \brief Return true if the specified property is in one of the containers.
*/
bool Has(const gd::String& name) const;
/**
* \brief Return a reference to the property called \a name, and the
* properties container holding it.
*/
std::pair<std::reference_wrapper<const gd::PropertiesContainer>,
std::reference_wrapper<const NamedPropertyDescriptor>>
Get(const gd::String& name) const;
/**
* \brief Return true if the specified properties container is present.
*/
bool HasPropertiesContainer(const gd::PropertiesContainer& propertiesContainer) const;
/**
* Get the properties container at the bottom of the scope (so the most
* "local" one).
*/
const PropertiesContainer* GetBottomMostPropertiesContainer() const {
if (propertiesContainers.empty()) return nullptr;
return propertiesContainers.back();
}
/**
* \brief Call the callback for each property having a name starting with the specified prefix.
*/
void ForEachPropertyWithPrefix(const gd::String& prefix, std::function<void(const gd::NamedPropertyDescriptor& property)> fn) const;
/** Do not use - should be private but accessible to let Emscripten create a
* temporary. */
PropertiesContainersList(){};
private:
std::vector<const gd::PropertiesContainer*> propertiesContainers;
static NamedPropertyDescriptor badNamedPropertyDescriptor;
static PropertiesContainer badPropertiesContainer;
};
} // namespace gd

View File

@@ -10,6 +10,7 @@
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/Tools/UUID/UUID.h"
using namespace std;
@@ -228,6 +229,9 @@ void Variable::SerializeTo(SerializerElement& element) const {
element.SetStringAttribute("type", TypeAsString(GetType()));
if (IsFolded()) element.SetBoolAttribute("folded", true);
if (!persistentUuid.empty())
element.SetStringAttribute("persistentUuid", persistentUuid);
if (type == Type::String) {
element.SetStringAttribute("value", GetString());
} else if (type == Type::Number) {
@@ -254,6 +258,8 @@ void Variable::SerializeTo(SerializerElement& element) const {
void Variable::UnserializeFrom(const SerializerElement& element) {
type = StringAsType(element.GetStringAttribute("type", "string"));
persistentUuid = element.GetStringAttribute("persistentUuid");
// Compatibility with GD <= 5.0.0-beta102
// Before, everything was stored as strings.
// We can unserialize primitives as string as they can be converted from/to
@@ -290,7 +296,12 @@ void Variable::UnserializeFrom(const SerializerElement& element) {
PushNew().UnserializeFrom(childElement);
}
}
} // namespace gd
}
Variable& Variable::ResetPersistentUuid() {
persistentUuid = UUID::MakeUuid4();
return *this;
}
std::vector<gd::String> Variable::GetAllChildrenNames() const {
std::vector<gd::String> names;
@@ -338,7 +349,8 @@ Variable::Variable(const Variable& other)
str(other.str),
folded(other.folded),
boolVal(other.boolVal),
type(other.type) {
type(other.type),
persistentUuid(other.persistentUuid) {
CopyChildren(other);
}
@@ -349,6 +361,7 @@ Variable& Variable::operator=(const Variable& other) {
folded = other.folded;
boolVal = other.boolVal;
type = other.type;
persistentUuid = other.persistentUuid;
CopyChildren(other);
}

View File

@@ -338,6 +338,24 @@ class GD_CORE_API Variable {
* \brief Unserialize the variable.
*/
void UnserializeFrom(const SerializerElement& element);
/**
* \brief Reset the persistent UUID used to recognize
* the same variable between serialization.
*/
Variable& ResetPersistentUuid();
/**
* \brief Remove the persistent UUID - when the variable no
* longer needs to be recognized between serializations.
*/
Variable& ClearPersistentUuid() { persistentUuid = ""; return *this; };
/**
* \brief Get the persistent UUID used to recognize
* the same variable between serialization.
*/
const gd::String& GetPersistentUuid() const { return persistentUuid; };
///@}
private:
@@ -361,6 +379,8 @@ class GD_CORE_API Variable {
mutable std::vector<std::shared_ptr<Variable>>
childrenArray; ///< Children, when the variable is considered as an
///< array.
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
///< useful for computing changesets.
/**
* Initialize children by copying them from another variable. Used by

View File

@@ -12,6 +12,7 @@
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/TinyXml/tinyxml.h"
#include "GDCore/Tools/UUID/UUID.h"
namespace gd {
@@ -161,7 +162,18 @@ void VariablesContainer::Move(std::size_t oldIndex, std::size_t newIndex) {
variables.insert(variables.begin() + newIndex, nameAndVariable);
}
void VariablesContainer::ForEachVariableWithPrefix(
const gd::String& prefix,
std::function<void(const gd::String& name, const gd::Variable& variable)> fn) const {
for (const auto& nameAndVariable: variables) {
if (nameAndVariable.first.find(prefix) == 0) fn(nameAndVariable.first, *nameAndVariable.second);
}
}
void VariablesContainer::SerializeTo(SerializerElement& element) const {
if (!persistentUuid.empty())
element.SetStringAttribute("persistentUuid", persistentUuid);
element.ConsiderAsArrayOf("variable");
for (std::size_t j = 0; j < variables.size(); j++) {
SerializerElement& variableElement = element.AddChild("variable");
@@ -171,6 +183,8 @@ void VariablesContainer::SerializeTo(SerializerElement& element) const {
}
void VariablesContainer::UnserializeFrom(const SerializerElement& element) {
persistentUuid = element.GetStringAttribute("persistentUuid");
Clear();
element.ConsiderAsArrayOf("variable", "Variable");
for (std::size_t j = 0; j < element.GetChildrenCount(); j++) {
@@ -183,6 +197,24 @@ void VariablesContainer::UnserializeFrom(const SerializerElement& element) {
}
}
VariablesContainer& VariablesContainer::ResetPersistentUuid() {
persistentUuid = UUID::MakeUuid4();
for(auto& variable: variables) {
variable.second->ResetPersistentUuid();
}
return *this;
}
VariablesContainer& VariablesContainer::ClearPersistentUuid() {
persistentUuid = "";
for(auto& variable: variables) {
variable.second->ClearPersistentUuid();
}
return *this;
}
VariablesContainer::VariablesContainer(const VariablesContainer& other) {
Init(other);
}
@@ -195,6 +227,7 @@ VariablesContainer& VariablesContainer::operator=(
}
void VariablesContainer::Init(const gd::VariablesContainer& other) {
persistentUuid = other.persistentUuid;
variables.clear();
for (auto& it : other.variables) {
variables.push_back(

View File

@@ -137,6 +137,11 @@ class GD_CORE_API VariablesContainer {
* \brief Clear all variables of the container.
*/
inline void Clear() { variables.clear(); }
/**
* \brief Call the callback for each variable with a name starting with the specified prefix.
*/
void ForEachVariableWithPrefix(const gd::String& prefix, std::function<void(const gd::String& name, const gd::Variable& variable)> fn) const;
///@}
/** \name Saving and loading
@@ -152,10 +157,30 @@ class GD_CORE_API VariablesContainer {
* \brief Unserialize the variable container.
*/
void UnserializeFrom(const SerializerElement& element);
/**
* \brief Reset the persistent UUID, used to recognize
* the same variables between serialization.
*/
VariablesContainer& ResetPersistentUuid();
/**
* \brief Remove the persistent UUID - when the variables no
* longer need to be recognized between serializations.
*/
VariablesContainer& ClearPersistentUuid();
/**
* \brief Get the persistent UUID used to recognize
* the same variables between serialization.
*/
const gd::String& GetPersistentUuid() const { return persistentUuid; };
///@}
private:
std::vector<std::pair<gd::String, std::shared_ptr<gd::Variable>>> variables;
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
///< useful for computing changesets.
static gd::Variable badVariable;
static gd::String badName;

View File

@@ -0,0 +1,64 @@
#include "VariablesContainersList.h"
#include <vector>
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/Variable.h"
namespace gd {
Variable VariablesContainersList::badVariable;
VariablesContainersList
VariablesContainersList::MakeNewVariablesContainersListForProjectAndLayout(
const gd::Project& project, const gd::Layout& layout) {
VariablesContainersList variablesContainersList;
variablesContainersList.Add(project.GetVariables());
variablesContainersList.Add(layout.GetVariables());
return variablesContainersList;
}
VariablesContainersList
VariablesContainersList::MakeNewEmptyVariablesContainersList() {
VariablesContainersList variablesContainersList;
return variablesContainersList;
}
bool VariablesContainersList::Has(const gd::String& name) const {
for (auto it = variablesContainers.rbegin(); it != variablesContainers.rend();
++it) {
if ((*it)->Has(name)) return true;
}
return false;
}
const Variable& VariablesContainersList::Get(const gd::String& name) const {
for (auto it = variablesContainers.rbegin(); it != variablesContainers.rend();
++it) {
if ((*it)->Has(name)) return (*it)->Get(name);
}
return badVariable;
}
bool VariablesContainersList::HasVariablesContainer(const gd::VariablesContainer& variablesContainer) const {
for (auto it = variablesContainers.rbegin(); it != variablesContainers.rend();
++it) {
if (*it == &variablesContainer) return true;
}
return false;
}
void VariablesContainersList::ForEachVariableWithPrefix(
const gd::String& prefix,
std::function<void(const gd::String& name, const gd::Variable& variable)> fn) const {
for (auto it = variablesContainers.rbegin(); it != variablesContainers.rend();
++it) {
(*it)->ForEachVariableWithPrefix(prefix, fn);
}
}
} // namespace gd

View File

@@ -0,0 +1,85 @@
#pragma once
#include <vector>
#include <functional>
namespace gd {
class String;
class Project;
class Layout;
class VariablesContainer;
class Variable;
} // namespace gd
namespace gd {
/**
* \brief A list of variables containers, useful for accessing variables in a
* scoped way.
*
* \see gd::Variable
* \see gd::Project
* \see gd::Layout
*
* \ingroup PlatformDefinition
*/
class GD_CORE_API VariablesContainersList {
public:
virtual ~VariablesContainersList(){};
static VariablesContainersList
MakeNewVariablesContainersListForProjectAndLayout(const gd::Project& project,
const gd::Layout& layout);
static VariablesContainersList MakeNewEmptyVariablesContainersList();
/**
* \brief Return true if the specified variable is in one of the containers.
*/
bool Has(const gd::String& name) const;
/**
* \brief Return a reference to the variable called \a name.
*/
const Variable& Get(const gd::String& name) const;
/**
* \brief Return true if the specified variable container is present.
*/
bool HasVariablesContainer(const gd::VariablesContainer& variablesContainer) const;
/**
* Get the variables container at the top of the scope (so the most "global" one).
* \brief Avoid using apart when a scope must be forced.
*/
const VariablesContainer* GetTopMostVariablesContainer() const {
if (variablesContainers.empty()) return nullptr;
return variablesContainers.front();
};
/**
* Get the variables container at the bottom of the scope (so the most "local" one).
* \brief Avoid using apart when a scope must be forced.
*/
const VariablesContainer* GetBottomMostVariablesContainer() const {
if (variablesContainers.empty()) return nullptr;
return variablesContainers.back();
}
/**
* \brief Call the callback for each variable having a name starting with the specified prefix.
*/
void ForEachVariableWithPrefix(const gd::String& prefix, std::function<void(const gd::String& name, const gd::Variable& variable)> fn) const;
/** Do not use - should be private but accessible to let Emscripten create a temporary. */
VariablesContainersList() {};
private:
void Add(const gd::VariablesContainer& variablesContainer) {
variablesContainers.push_back(&variablesContainer);
};
std::vector<const gd::VariablesContainer*> variablesContainers;
static Variable badVariable;
};
} // namespace gd

View File

@@ -251,26 +251,26 @@ String& String::replace_if(iterator i1, iterator i2, std::function<bool(char32_t
return *this;
}
String& String::RemoveConsecutiveOccurrences(iterator i1, iterator i2, const char c)
{
std::vector<std::pair<size_type, size_type>> ranges_to_remove;
for(iterator current_index = i1.base(); current_index < i2.base(); current_index++)
{
if (*current_index == c){
iterator current_subindex = current_index;
std::advance(current_subindex, 1);
if (*current_subindex == c) {
while(current_subindex < end() && *current_subindex == c)
{
current_subindex++;
}
replace(std::distance(begin(), current_index),
std::distance(current_index, current_subindex),
c);
std::advance(current_index, 1);
}
String &String::RemoveConsecutiveOccurrences(iterator i1,
iterator i2,
const char c) {
iterator end = i2;
for (iterator current_index = i1.base(); current_index < end.base();
current_index++) {
if (*current_index == c) {
iterator current_subindex = current_index;
current_subindex++;
while (current_subindex < end.base() && *current_subindex == c) {
current_subindex++;
}
difference_type difference_to_replace =
std::distance(current_index, current_subindex);
if (difference_to_replace > 1) {
replace(
std::distance(begin(), current_index), difference_to_replace, c);
std::advance(end, -(difference_to_replace - 1));
}
}
}
return *this;
}

View File

@@ -13,6 +13,8 @@
#include "GDCore/Project/Project.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Events/Builtin/StandardEvent.h"
#include "GDCore/Events/Builtin/ForEachChildVariableEvent.h"
#include "GDCore/Events/Builtin/RepeatEvent.h"
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterOptions.h"
#include "catch.hpp"
@@ -103,6 +105,8 @@ void SetupProjectWithDummyPlatform(gd::Project& project,
commonInstructionsExtension->SetExtensionInformation(
"BuiltinCommonInstructions", "instruction extension", "", "", "");
commonInstructionsExtension->AddEvent("Standard", "Standard event", "", "", "", std::make_shared<gd::StandardEvent>());
commonInstructionsExtension->AddEvent("ForEachChildVariable", "For each child variable event", "", "", "", std::make_shared<gd::ForEachChildVariableEvent>());
commonInstructionsExtension->AddEvent("Repeat", "Repeat event", "", "", "", std::make_shared<gd::RepeatEvent>());
std::shared_ptr<gd::PlatformExtension> baseObjectExtension =
std::shared_ptr<gd::PlatformExtension>(new gd::PlatformExtension);
@@ -243,6 +247,20 @@ void SetupProjectWithDummyPlatform(gd::Project& project,
.AddParameter("soundfile", "Parameter 3 (an audio resource)")
.SetFunctionName("doSomethingWithResources");
extension
->AddAction("DoSomethingWithLegacyPreScopedVariables",
"Do something with variables",
"This does something with variables",
"Do something with variables please",
"",
"",
"")
.AddParameter("scenevar", "Scene variable")
.AddParameter("globalvar", "Global variable")
.AddParameter("object", "Some object")
.AddParameter("objectvar", "Some variable of the object")
.SetFunctionName("doSomethingWithVariables");
extension->AddExpression("GetNumber", "Get me a number", "", "", "")
.SetFunctionName("getNumber");
extension
@@ -345,7 +363,7 @@ void SetupProjectWithDummyPlatform(gd::Project& project,
.AddParameter("object", _("Object"), "Sprite")
.AddParameter("expression", _("Number parameter"))
.AddParameter("string", _("String parameter"))
.AddParameter("", _("Identifier parameter"))
.AddParameter("expression", _("Identifier parameter"))
.SetFunctionName("getObjectStringWith3Param");
object
.AddStrExpression("GetObjectStringWith2ObjectParam",
@@ -560,15 +578,15 @@ void SetupProjectWithDummyPlatform(gd::Project& project,
extension
->AddExpression(
"LayerEffectParameter",
_("Effect parameter (number)"),
_("Return the value of a parameter of an effect."),
_("Effect property (number)"),
_("Return the value of a property of an effect."),
_("Effects"),
"")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer (base layer if empty)"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("layerEffectName", _("Effect name"))
.AddParameter("layerEffectParameterName", _("Parameter name"));
.AddParameter("layerEffectParameterName", _("Property name"));
}
{

View File

@@ -15,6 +15,7 @@
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "catch.hpp"
namespace {
@@ -35,6 +36,8 @@ TEST_CASE("EventsBehaviorRenamer (expressions)", "[common]") {
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &layout1 = project.InsertNewLayout("Layout1", 0);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
auto &object1 =
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
@@ -57,7 +60,7 @@ TEST_CASE("EventsBehaviorRenamer (expressions)", "[common]") {
// Rename the first behavior.
gd::EventsBehaviorRenamer behaviorRenamer(
platform, "Object1", "MyBehavior", "MyRenamedBehavior");
behaviorRenamer.Launch(layout1.GetEvents(), project, layout1);
behaviorRenamer.Launch(layout1.GetEvents(), projectScopedContainers);
// Verify the expression were updated.
REQUIRE(EnsureStandardEvent(layout1.GetEvents().GetEvent(0))
@@ -74,6 +77,8 @@ TEST_CASE("EventsBehaviorRenamer (instructions)", "[common]") {
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &layout1 = project.InsertNewLayout("Layout1", 0);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
auto &object1 =
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
@@ -109,7 +114,7 @@ TEST_CASE("EventsBehaviorRenamer (instructions)", "[common]") {
// Rename MyBehavior.
gd::EventsBehaviorRenamer behaviorRenamer(
platform, "Object1", "MyBehavior", "MyRenamedBehavior");
behaviorRenamer.Launch(layout1.GetEvents(), project, layout1);
behaviorRenamer.Launch(layout1.GetEvents(), projectScopedContainers);
// Ensure the action using MyBehavior has its parameter renamed.
REQUIRE(EnsureStandardEvent(layout1.GetEvents().GetEvent(0))

View File

@@ -20,6 +20,13 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
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");
layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
layout1.InsertNewObject(
project, "MyExtension::Sprite", "MyOtherSpriteObject", 1);
@@ -27,6 +34,15 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"MyExtension::FakeObjectWithDefaultBehavior",
"FakeObjectWithDefaultBehavior",
2);
// Also insert a variable having the same name as an object:
layout1.InsertNewObject(project, "MyExtension::Sprite", "ObjectWithNameReused", 3);
layout1.GetVariables().InsertNew("ObjectWithNameReused", 3).GetChild("MyChild");
// Also insert a global variable having the same name as a scene variable:
layout1.GetVariables().InsertNew("SceneVariableWithNameReused", 4);
project.GetVariables().InsertNew("SceneVariableWithNameReused", 0);
auto &group = layout1.GetObjectGroups().InsertNew("AllObjects");
group.AddObject("MySpriteObject");
group.AddObject("MyOtherSpriteObject");
@@ -269,7 +285,7 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
parser.ParseExpression("MySpriteObject.GetObjectStringWith3Param("
"MySpriteObject.GetObjectNumber() / 2.3, "
"MySpriteObject.GetObjectStringWith1Param("
"MyExtension::GetNumber()), test)");
"MyExtension::GetNumber()), UnknownObject.Unknown)");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
@@ -281,8 +297,7 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"MySpriteObject.getObjectStringWith3Param(MySpriteObject."
"getObjectNumber() ?? 0 / 2.3, "
"MySpriteObject.getObjectStringWith1Param(getNumber()) ?? \"\", "
"/* Error during generation, unrecognized identifier type: "
"unknown with value test */ \"test\") ?? \"\"");
"0) ?? \"\"");
}
SECTION("missing parameter") {
{
@@ -392,9 +407,178 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"123) ?? \"\"");
}
}
SECTION("Function name") {
SECTION("Properties (1 level)") {
gd::PropertiesContainer propertiesContainer(gd::EventsFunctionsContainer::Extension);
auto projectScopedContainersWithProperties = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithProperties.AddPropertiesContainer(propertiesContainer);
propertiesContainer.InsertNew("MyProperty");
propertiesContainer.InsertNew("MyProperty2");
gd::EventsCodeGenerator codeGeneratorWithProperties(platform, projectScopedContainersWithProperties);
{
auto node =
parser.ParseExpression("MyProperty + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGeneratorWithProperties,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getPropertyMyProperty() + 1");
}
{
auto node =
parser.ParseExpression("MyProperty + MyProperty2");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGeneratorWithProperties,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getPropertyMyProperty() + getPropertyMyProperty2()");
}
}
SECTION("Parameters (1 level)") {
std::vector<gd::ParameterMetadata> parameters;
gd::ParameterMetadata param1;
param1.SetName("MyParameter1");
param1.SetType("number");
gd::ParameterMetadata param2;
param2.SetName("MyParameter2");
param2.SetType("string");
parameters.push_back(param1);
parameters.push_back(param2);
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
gd::EventsCodeGenerator codeGeneratorWithProperties(platform, projectScopedContainersWithParameters);
{
auto node =
parser.ParseExpression("MyParameter1 + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGeneratorWithProperties,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getParameterMyParameter1() + 1");
}
{
auto node =
parser.ParseExpression("MyParameter1 + MyParameter2");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGeneratorWithProperties,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getParameterMyParameter1() + getParameterMyParameter2()");
}
}
SECTION("Scene variables (1 level)") {
{
auto node =
parser.ParseExpression("MySceneVariable + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneVariable).getAsNumber() + 1");
}
{
auto node =
parser.ParseExpression("MySceneVariable + MySceneVariable2");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneVariable).getAsNumber() + getLayoutVariable(MySceneVariable2).getAsNumber()");
}
}
SECTION("Scene variables (conflict with a global variable)") {
{
auto node =
parser.ParseExpression("SceneVariableWithNameReused + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(SceneVariableWithNameReused).getAsNumber() + 1");
}
}
SECTION("Scene variables (2 levels)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable.MyChild + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getAsNumber() + 1");
}
{
auto node =
parser.ParseExpression("MySceneStructureVariable.MyChild + MySceneStructureVariable2.MyChild");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getAsNumber() + getLayoutVariable(MySceneStructureVariable2).getChild(\"MyChild\").getAsNumber()");
}
}
SECTION("Scene variables (2 levels with bracket accessor)") {
{
auto node =
parser.ParseExpression("MySceneStructureVariable[\"MyChild\"] + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getAsNumber() + 1");
}
{
auto node =
parser.ParseExpression("MySceneStructureVariable[\"MyChild\"] + MySceneStructureVariable2[\"MyChild\"]");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getAsNumber() + getLayoutVariable(MySceneStructureVariable2).getChild(\"MyChild\").getAsNumber()");
}
}
SECTION("Object variable with non existing object (invalid)") {
auto node =
parser.ParseExpression("MySpriteObject.GetObjectNumber");
parser.ParseExpression("MyNonExistingSpriteObject.MyVariable");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
@@ -404,6 +588,124 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "0");
}
SECTION("Object variables (1 level)") {
{
auto node =
parser.ParseExpression("MySpriteObject.MyVariable + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getVariableForObject(MySpriteObject, MyVariable).getAsNumber() + 1");
}
{
auto node =
parser.ParseExpression("MySpriteObject.MyVariable + MySpriteObject.MyVariable2");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getVariableForObject(MySpriteObject, MyVariable).getAsNumber() + getVariableForObject(MySpriteObject, MyVariable2).getAsNumber()");
}
}
SECTION("Object variables (conflict with a scene variable)") {
{
auto node =
parser.ParseExpression("ObjectWithNameReused.MyVariable + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getVariableForObject(ObjectWithNameReused, MyVariable).getAsNumber() + 1");
}
}
SECTION("Object variables (1 level with bracket accessor) (invalid)") {
{
auto node =
parser.ParseExpression("MySpriteObject[\"BracketNotationCantBeUsedHere\"] + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "fakeBadVariable.getAsNumber() + 1");
}
{
auto node =
parser.ParseExpression("MySpriteObject.MyVariable + MySpriteObject.MyVariable2");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getVariableForObject(MySpriteObject, MyVariable).getAsNumber() + getVariableForObject(MySpriteObject, MyVariable2).getAsNumber()");
}
}
SECTION("Object variables (2 levels)") {
{
auto node =
parser.ParseExpression("MySpriteObject.MyVariable.MyChildVariable + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getVariableForObject(MySpriteObject, MyVariable).getChild(\"MyChildVariable\").getAsNumber() + 1");
}
{
auto node =
parser.ParseExpression("MySpriteObject.MyVariable.MyChildVariable + MySpriteObject.MyVariable2.MyChildVariable");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getVariableForObject(MySpriteObject, MyVariable).getChild(\"MyChildVariable\").getAsNumber() + getVariableForObject(MySpriteObject, MyVariable2).getChild(\"MyChildVariable\").getAsNumber()");
}
}
SECTION("Object variables (2 levels with bracket accessor)") {
{
auto node =
parser.ParseExpression("MySpriteObject.MyVariable[\"MyChildVariable\"] + 1");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getVariableForObject(MySpriteObject, MyVariable).getChild(\"MyChildVariable\").getAsNumber() + 1");
}
{
auto node =
parser.ParseExpression("MySpriteObject.MyVariable[\"MyChildVariable\"] + MySpriteObject.MyVariable2[\"MyChildVariable\"]");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getVariableForObject(MySpriteObject, MyVariable).getChild(\"MyChildVariable\").getAsNumber() + getVariableForObject(MySpriteObject, MyVariable2).getChild(\"MyChildVariable\").getAsNumber()");
}
}
SECTION("Invalid variables") {
SECTION("empty variable") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
@@ -459,7 +761,7 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"\"world\" ]", "")
== "getLayoutVariable(myVariable).getChild(\"hello\" + \"world\")");
}
SECTION("object variable") {
SECTION("object variable (legacy)") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "objectvar", "myVariable", "MySpriteObject")
== "getVariableForObject(MySpriteObject, myVariable)");

View File

@@ -5,6 +5,8 @@
*/
#include "GDCore/IDE/Events/ExpressionCompletionFinder.h"
#include <vector>
#include "DummyPlatform.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
@@ -12,6 +14,7 @@
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "catch.hpp"
TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
@@ -19,7 +22,14 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto& layout1 = project.InsertNewLayout("Layout1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
layout1.GetVariables().InsertNew("myVariable");
auto& object1 =
layout1.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
object1.GetVariables().InsertNew("myObjectVariable");
gd::ProjectScopedContainers projectScopedContainers =
gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
gd::ExpressionParser2 parser;
@@ -28,38 +38,49 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
size_t location) {
auto node = parser.ParseExpression(expression);
REQUIRE(node != nullptr);
return gd::ExpressionCompletionFinder::GetCompletionDescriptionsFor(
platform, project, layout1, type, *node, location);
auto completions =
gd::ExpressionCompletionFinder::GetCompletionDescriptionsFor(
platform, projectScopedContainers, type, *node, location);
std::vector<gd::String> completionsAsString;
for (const auto& completion : completions) {
completionsAsString.push_back(completion.ToString());
}
return completionsAsString;
};
const std::vector<gd::ExpressionCompletionDescription>
expectedEmptyCompletions;
const std::vector<gd::String> expectedEmptyCompletions;
SECTION("Identifier") {
SECTION("Object or expression completions when type is string") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("string", "My", 0, 2),
gd::ExpressionCompletionDescription::ForExpression(
"string", "My", 0, 2)};
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 0, string, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("string", "My", 0, 2).ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("string", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("string", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("string", "My", 2) == expectedEmptyCompletions);
}
SECTION("Object or expression completions when type is number") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("number", "My", 0, 2),
gd::ExpressionCompletionDescription::ForExpression(
"number", "My", 0, 2)};
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 0, number, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("number", "My", 0, 2).ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("number", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "My", 2) == expectedEmptyCompletions);
}
SECTION("Object or expression completions when type is number|string") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject(
"number|string", "My", 0, 2),
gd::ExpressionCompletionDescription::ForExpression(
"number|string", "My", 0, 2)};
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 0, number|string, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("number|string", "My", 0, 2).ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("number|string", "My", 0) ==
expectedCompletions);
REQUIRE(getCompletionsFor("number|string", "My", 1) ==
@@ -68,26 +89,51 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
expectedEmptyCompletions);
}
SECTION("Object or expression completions in a variable name") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("string", "My", 0, 2),
gd::ExpressionCompletionDescription::ForExpression(
"string", "My", 0, 2)};
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[\"abc\" + My", 52) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[\"abc\" + My", 53) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[\"abc\" + My", 54) == expectedEmptyCompletions);
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 0, string, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("string", "My", 0, 2).ToString()
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetVariableAsNumber(MyVariable[\"abc\" + My",
52) == expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetVariableAsNumber(MyVariable[\"abc\" + My",
53) == expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetVariableAsNumber(MyVariable[\"abc\" + My",
54) == expectedEmptyCompletions);
}
SECTION("Object or expression completions in a variable index") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("number", "My", 0, 2),
gd::ExpressionCompletionDescription::ForExpression(
"number", "My", 0, 2)};
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[12345 + My", 52) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[12345 + My", 53) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[12345 + My", 54) == expectedEmptyCompletions);
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 0, number, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("number", "My", 0, 2).ToString()
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetVariableAsNumber(MyVariable[12345 + My",
52) == expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetVariableAsNumber(MyVariable[12345 + My",
53) == expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetVariableAsNumber(MyVariable[12345 + My",
54) == expectedEmptyCompletions);
}
SECTION("Object when type is an object") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("object", "My", 0, 2)};
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 0, object, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("object", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("object", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("object", "My", 2) == expectedEmptyCompletions);
@@ -96,9 +142,11 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Object when type is an object (alternate type)") {
// Also test alternate types also considered as objects (but that can
// result in different code generation):
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject(
"objectPtr", "My", 0, 2)};
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 0, objectPtr, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("objectPtr", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("objectPtr", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("objectPtr", "My", 2) ==
@@ -121,13 +169,15 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Free function") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "Function", 0, 8)};
std::vector<gd::ExpressionCompletionDescription> expectedExactCompletions{
gd::ExpressionCompletionDescription::ForExpression(
std::vector<gd::String> expectedCompletions{
gd::ExpressionCompletionDescription::ForExpressionWithPrefix(
"string", "Function", 0, 8)
.SetIsExact(true)};
.ToString()};
std::vector<gd::String> expectedExactCompletions{
gd::ExpressionCompletionDescription::ForExpressionWithPrefix(
"string", "Function", 0, 8)
.SetIsExact(true)
.ToString()};
REQUIRE(getCompletionsFor("string", "Function(", 0) ==
expectedCompletions);
REQUIRE(getCompletionsFor("string", "Function(", 1) ==
@@ -147,43 +197,48 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
REQUIRE(getCompletionsFor("string", "Function(\"", 9) ==
expectedEmptyCompletions);
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("unknown", "a", 9, 10),
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "a", 9, 10)};
REQUIRE(getCompletionsFor("string", "Function(a", 9) ==
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 0, unknown, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("unknown", "My", 9, 10).ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("string", "Function(My", 10) ==
expectedCompletions);
}
SECTION("Function with a Variable as argument") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForVariable(
"scenevar", "myVar", 33, 38)};
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 1, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyExtension::GetVariableAsNumber(myVar",
33) == expectedCompletions);
}
SECTION("Object function with a Variable as argument") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForVariable(
"objectvar", "myVar", 35, 40, "MyObject")};
getCompletionsFor("number",
"MyObject.GetObjectVariableAsNumber(myVar",
35);
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 1, no prefix, myObjectVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyObject.GetObjectVariableAsNumber(myVar",
"MyObject.GetObjectVariableAsNumber(myObj",
35) == expectedCompletions);
}
SECTION("Function with a Layer as argument") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForText(
// clang-format off
std::vector<gd::String> expectedCompletions{
gd::ExpressionCompletionDescription::ForTextWithPrefix(
"layer",
gd::MetadataProvider::GetExpressionMetadata(platform,
"MyExtension::MouseX")
.GetParameter(0),
gd::MetadataProvider::GetExpressionMetadata(platform, "MyExtension::MouseX").GetParameter(0),
"",
20,
21,
false)};
false)
.ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("number", "MyExtension::MouseX(\"", 20) ==
expectedCompletions);
}
@@ -191,16 +246,15 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Partial object or behavior function") {
SECTION("Test with string type") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject(
"string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehavior(
"Func", 9, 13, "MyObject"),
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", 9, 13, "MyObject")};
// clang-format off
std::vector<gd::String> expectedObjectCompletions{
"{ 0, string, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }"
};
std::vector<gd::String> expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehaviorWithPrefix("Func", 9, 13, "MyObject").ToString(),
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("string", "Func", 9, 13, "MyObject").ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("string", "MyObject.Func", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func", 7) ==
@@ -215,16 +269,15 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
expectedEmptyCompletions);
}
SECTION("Test with 'number|string' type") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject(
"number|string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehavior(
"Func", 9, 13, "MyObject"),
gd::ExpressionCompletionDescription::ForExpression(
"number|string", "Func", 9, 13, "MyObject")};
// clang-format off
std::vector<gd::String> expectedObjectCompletions{
"{ 0, number|string, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }"
};
std::vector<gd::String> expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehaviorWithPrefix("Func", 9, 13, "MyObject").ToString(),
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("number|string", "Func", 9, 13, "MyObject").ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("number|string", "MyObject.Func", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("number|string", "MyObject.Func", 7) ==
@@ -242,21 +295,18 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Object function") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject(
"string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehavior(
"Func", 9, 13, "MyObject"),
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", 9, 13, "MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedExactFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", 9, 13, "MyObject")
.SetIsExact(true)};
// clang-format off
std::vector<gd::String> expectedObjectCompletions{
"{ 0, string, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }"
};
std::vector<gd::String> expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehaviorWithPrefix("Func", 9, 13, "MyObject").ToString(),
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("string", "Func", 9, 13, "MyObject").ToString()
};
std::vector<gd::String> expectedExactFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("string", "Func", 9, 13, "MyObject").SetIsExact(true).ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 7) ==
@@ -278,18 +328,16 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Partial behavior function") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject(
"string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehavior(
"MyBehavior", 9, 19, "MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", 21, 25, "MyObject", "MyBehavior")};
// clang-format off
std::vector<gd::String> expectedObjectCompletions{
"{ 0, string, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }"
};
std::vector<gd::String> expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehaviorWithPrefix("MyBehavior", 9, 19, "MyObject").ToString()};
std::vector<gd::String> expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("string", "Func", 21, 25, "MyObject", "MyBehavior").ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 7) ==
@@ -310,18 +358,17 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
expectedFunctionCompletions);
}
SECTION("Test 2") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject(
"string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehavior(
"MyBehavior", 9, 19, "MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "", 21, 21, "MyObject", "MyBehavior")};
// clang-format off
std::vector<gd::String> expectedObjectCompletions{
"{ 0, string, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }"
};
std::vector<gd::String> expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehaviorWithPrefix("MyBehavior", 9, 19, "MyObject").ToString()
};
std::vector<gd::String> expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("string", "", 21, 21, "MyObject", "MyBehavior").ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::", 7) ==
@@ -341,23 +388,20 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Behavior function") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject(
"string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehavior(
"MyBehavior", 9, 19, "MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", 21, 25, "MyObject", "MyBehavior")};
std::vector<gd::ExpressionCompletionDescription>
expectedExactFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", 21, 25, "MyObject", "MyBehavior")
.SetIsExact(true)};
// clang-format off
std::vector<gd::String> expectedObjectCompletions{
"{ 0, string, 1, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }"
};
std::vector<gd::String> expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehaviorWithPrefix("MyBehavior", 9, 19, "MyObject").ToString()
};
std::vector<gd::String> expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("string", "Func", 21, 25, "MyObject", "MyBehavior").ToString()
};
std::vector<gd::String> expectedExactFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("string", "Func", 21, 25, "MyObject", "MyBehavior").SetIsExact(true).ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 7) ==

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "catch.hpp"
TEST_CASE("ExpressionParser2 - Benchmarks", "[common][events]") {
@@ -21,14 +22,16 @@ TEST_CASE("ExpressionParser2 - Benchmarks", "[common][events]") {
auto &layout1 = project.InsertNewLayout("Layout1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
gd::ExpressionParser2 parser;
auto parseExpression = [&parser, &project, &platform, &layout1](const gd::String &expression) {
auto parseExpressionWithType = [&parser, &project, &platform, &layout1,
auto parseExpression = [&parser, &platform, &projectScopedContainers](const gd::String &expression) {
auto parseExpressionWithType = [&parser, &platform, &projectScopedContainers,
&expression](const gd::String &type) {
auto node = parser.ParseExpression(expression);
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, project, layout1, type);
gd::ExpressionValidator validator(platform, projectScopedContainers, type);
node->Visit(validator);
};

View File

@@ -17,13 +17,6 @@
using namespace gd;
namespace {
void SetupProject(gd::Project &project, gd::Platform &platform){
};
} // namespace
TEST_CASE("Layout", "[common]") {
SECTION("Find the type of a behavior in a object") {

39
Core/tests/String.cpp Normal file
View File

@@ -0,0 +1,39 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
/**
* @file Tests covering String class of GDevelop Core.
*/
#include "GDCore/String.h"
#include <algorithm>
#include <initializer_list>
#include <map>
#include "GDCore/CommonTools.h"
#include "catch.hpp"
TEST_CASE("String", "[common]") {
SECTION("ReplaceConsecutiveOccurrences") {
gd::String str = " ";
REQUIRE(str.RemoveConsecutiveOccurrences(str.begin(), str.end(), ' ') ==
" ");
str = "L'opacité de NewSprite = \" \"";
REQUIRE(str.RemoveConsecutiveOccurrences(str.begin(), str.end(), ' ') ==
"L'opacité de NewSprite = \" \"");
str = "ccccccccc";
REQUIRE(str.RemoveConsecutiveOccurrences(str.begin(), str.end(), 'c') ==
"c");
str = "c";
REQUIRE(str.RemoveConsecutiveOccurrences(str.begin(), str.end(), 'c') ==
"c");
str = "";
REQUIRE(str.RemoveConsecutiveOccurrences(str.begin(), str.end(), ' ') ==
"");
str = "Set animation of NewSprite to ";
REQUIRE(str.RemoveConsecutiveOccurrences(str.begin(), str.end(), ' ') ==
"Set animation of NewSprite to ");
}
}

View File

@@ -0,0 +1,569 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
/**
* @file Tests covering project refactoring
*/
#include <algorithm>
#include <stdexcept>
#include "DummyPlatform.h"
#include "GDCore/Events/Builtin/ForEachChildVariableEvent.h"
#include "GDCore/Events/Builtin/LinkEvent.h"
#include "GDCore/Events/Builtin/RepeatEvent.h"
#include "GDCore/Events/Builtin/StandardEvent.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/WholeProjectRefactorer.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/ExternalLayout.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/Variable.h"
#include "GDCore/Serialization/Serializer.h"
#include "catch.hpp"
TEST_CASE("WholeProjectRefactorer::ApplyRefactoringForVariablesContainer",
"[common]") {
SECTION("Variable renamed (project, layout, object)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &layout1 = project.InsertNewLayout("Layout1", 0);
gd::StandardEvent &event =
dynamic_cast<gd::StandardEvent &>(layout1.GetEvents().InsertNewEvent(
project, "BuiltinCommonInstructions::Standard"));
gd::ForEachChildVariableEvent &forEachChildVariableEvent =
dynamic_cast<gd::ForEachChildVariableEvent &>(
layout1.GetEvents().InsertNewEvent(
project, "BuiltinCommonInstructions::ForEachChildVariable"));
gd::RepeatEvent &repeatEvent =
dynamic_cast<gd::RepeatEvent &>(layout1.GetEvents().InsertNewEvent(
project, "BuiltinCommonInstructions::Repeat"));
// Declare global variables.
project.GetVariables().InsertNew("MyGlobalVariable", 0).SetValue(123);
project.GetVariables().InsertNew("MyGlobalVariable2", 0).SetValue(123);
project.GetVariables().InsertNew("SharedVariableName", 0).SetValue(123);
project.GetVariables()
.InsertNew("MyGlobalStructureVariable", 0)
.GetChild("MyChild")
.SetValue(123);
project.GetVariables()
.InsertNew("MyGlobalStructureVariable2", 0)
.GetChild("MyChild")
.SetValue(123);
// Declare variables in the scene.
layout1.GetVariables().InsertNew("MySceneVariable", 0).SetValue(123);
layout1.GetVariables().InsertNew("MySceneVariable2", 0).SetValue(123);
layout1.GetVariables().InsertNew("SharedVariableName", 0).SetValue(123);
layout1.GetVariables()
.InsertNew("MySceneStructureVariable", 0)
.GetChild("MyChild")
.SetValue(123);
layout1.GetVariables()
.InsertNew("MySceneStructureVariable2", 0)
.GetChild("MyChild")
.SetValue(123);
// Declare variables in objects.
auto &object1 =
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
object1.GetVariables().InsertNew("MyObjectVariable");
object1.GetVariables()
.InsertNew("MyObjectStructureVariable")
.GetChild("MyChild")
.SetValue(123);
auto &object2 =
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object2", 0);
object2.GetVariables().InsertNew("MyObjectVariable");
object2.GetVariables()
.InsertNew("MyObjectStructureVariable")
.GetChild("MyChild")
.SetValue(123);
// Create an event using the variables.
// clang-format off
{
gd::Instruction action;
action.SetType("MyExtension::DoSomething");
action.SetParametersCount(1);
action.SetParameter(
0,
gd::Expression(
"1 + "
"MySceneVariable + "
"MySceneVariable2 + "
"Object1.MyObjectVariable + "
"Object2.MyObjectVariable + "
"Object1.MyObjectStructureVariable.MyChild + "
"Object2.MyObjectStructureVariable.MyChild + "
"MySceneStructureVariable.MyChild + "
"MySceneStructureVariable2.MyChild + "
"MyGlobalVariable + "
"MyGlobalVariable2 + "
"MyGlobalStructureVariable.MyChild + "
"MyGlobalStructureVariable2.MyChild"));
event.GetActions().Insert(action);
}
// Expressions with "old" "scenevar", "globalvar", "objectvar":
{
gd::Instruction action;
action.SetType("MyExtension::DoSomething");
action.SetParametersCount(1);
action.SetParameter(
0,
gd::Expression(
// "objectvar" (in a free expression):
"1 + "
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object1, MyObjectVariable, Object2, MyObjectVariable) + "
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object1, MyObjectStructureVariable.MyChild, Object2, MyObjectStructureVariable.MyChild) + "
// "objectvar" (using the name of the object being called):
"Object1.GetObjectVariableAsNumber(MyObjectVariable) + "
"Object2.GetObjectVariableAsNumber(MyObjectVariable) + "
"Object1.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild) + "
"Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild) + "
"Object1.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild.GrandChild) + "
"Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild.GrandChild) + "
// "globalvar" and "scenevar":
"MyExtension::GetGlobalVariableAsNumber(MyGlobalVariable) + "
"MyExtension::GetGlobalVariableAsNumber(MyGlobalVariable2) + "
"MyExtension::GetGlobalVariableAsNumber(SharedVariableName) + "
"MyExtension::GetVariableAsNumber(MySceneVariable) + "
"MyExtension::GetVariableAsNumber(MySceneVariable2) + "
"MyExtension::GetVariableAsNumber(SharedVariableName) + "
"MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable.MyChild) + "
"MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable2.MyChild) + "
"MyExtension::GetVariableAsNumber(MySceneStructureVariable.MyChild) + "
"MyExtension::GetVariableAsNumber(MySceneStructureVariable2.MyChild) + "
"MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable.MyChild.GrandChild) + "
"MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable2.MyChild.GrandChild) + "
"MyExtension::GetVariableAsNumber(MySceneStructureVariable.MyChild.GrandChild) + "
"MyExtension::GetVariableAsNumber(MySceneStructureVariable2.MyChild.GrandChild)"));
event.GetActions().Insert(action);
}
{
gd::Instruction action;
action.SetType("MyExtension::DoSomethingWithLegacyPreScopedVariables");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("MySceneVariable"));
action.SetParameter(1, gd::Expression("MyGlobalVariable"));
action.SetParameter(2, gd::Expression("Object1"));
action.SetParameter(3, gd::Expression("MyObjectVariable"));
event.GetActions().Insert(action);
}
{
gd::Instruction action;
action.SetType("MyExtension::DoSomethingWithLegacyPreScopedVariables");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("SharedVariableName"));
action.SetParameter(1, gd::Expression("SharedVariableName"));
action.SetParameter(2, gd::Expression("Object2"));
action.SetParameter(3, gd::Expression("MyObjectVariable"));
event.GetActions().Insert(action);
}
forEachChildVariableEvent.SetIterableVariableName("MySceneStructureVariable");
repeatEvent.SetRepeatExpression("1 + MySceneVariable + Object1.MyObjectVariable + Object2.MyObjectVariable");
// clang-format on
// Do a copy of layout1 to ensure other scene is unchanged after the
// refactoring.
gd::Layout layout2 = layout1;
layout2.SetName("Layout2");
project.InsertLayout(layout2, 1);
gd::SerializerElement originalSerializedLayout2;
layout2.SerializeTo(originalSerializedLayout2);
// Do the changes and launch the refactoring.
project.GetVariables().ResetPersistentUuid();
layout1.GetVariables().ResetPersistentUuid();
object1.ResetPersistentUuid();
gd::SerializerElement originalSerializedProjectVariables;
project.GetVariables().SerializeTo(originalSerializedProjectVariables);
gd::SerializerElement originalSerializedLayoutVariables;
layout1.GetVariables().SerializeTo(originalSerializedLayoutVariables);
gd::SerializerElement originalSerializedObject1Variables;
object1.GetVariables().SerializeTo(originalSerializedObject1Variables);
project.GetVariables().Rename("MyGlobalVariable",
"MyRenamedGlobalVariable");
project.GetVariables().Rename("MyGlobalStructureVariable",
"MyRenamedGlobalStructureVariable");
project.GetVariables().Rename("SharedVariableName",
"RenamedGlobalVariableFromASharedName");
auto changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedProjectVariables,
project.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
project.GetVariables(),
changeset);
layout1.GetVariables().Rename("MySceneVariable", "MyRenamedSceneVariable");
layout1.GetVariables().Rename("MySceneStructureVariable",
"MyRenamedSceneStructureVariable");
changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedLayoutVariables,
layout1.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
layout1.GetVariables(),
changeset);
object1.GetVariables().Rename("MyObjectVariable",
"MyRenamedObjectVariable");
object1.GetVariables().Rename("MyObjectStructureVariable",
"MyRenamedObjectStructureVariable");
changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedObject1Variables,
object1.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
object1.GetVariables(),
changeset);
// Check the first layout is updated.
// clang-format off
{
// Updated direct access to variables:
REQUIRE(event.GetActions()[0].GetParameter(0).GetPlainString() ==
"1 + "
"MyRenamedSceneVariable + "
"MySceneVariable2 + "
"Object1.MyRenamedObjectVariable + "
"Object2.MyObjectVariable + "
"Object1.MyRenamedObjectStructureVariable.MyChild + "
"Object2.MyObjectStructureVariable.MyChild + "
"MyRenamedSceneStructureVariable.MyChild + "
"MySceneStructureVariable2.MyChild + "
"MyRenamedGlobalVariable + "
"MyGlobalVariable2 + "
"MyRenamedGlobalStructureVariable.MyChild + "
"MyGlobalStructureVariable2.MyChild"
);
// Updated access to variables using the legacy "pre-scoped" "scenevar",
// "globalvar" and "objectvar" parameters in expressions:
REQUIRE(event.GetActions()[1].GetParameter(0).GetPlainString() ==
"1 + "
// Multiple "objectvar" parameters in a free function:
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object1, MyRenamedObjectVariable, Object2, MyObjectVariable) + "
// Multiple "objectvar" parameters in a free function, with child
// variable:
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object1, MyRenamedObjectStructureVariable.MyChild, Object2, MyObjectStructureVariable.MyChild) + "
// Single "objectvar" from the object being accessed:
"Object1.GetObjectVariableAsNumber(MyRenamedObjectVariable) + "
"Object2.GetObjectVariableAsNumber(MyObjectVariable) + "
// Single "objectvar" from the object being accessed, with child
// variales:
"Object1.GetObjectVariableAsNumber(MyRenamedObjectStructureVariable.MyChild) + "
"Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild) + "
"Object1.GetObjectVariableAsNumber(MyRenamedObjectStructureVariable.MyChild.GrandChild) + "
"Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild.GrandChild) + "
// "globalvar" and "scenevar" in a free function:
"MyExtension::GetGlobalVariableAsNumber(MyRenamedGlobalVariable) + "
"MyExtension::GetGlobalVariableAsNumber(MyGlobalVariable2) + "
"MyExtension::GetGlobalVariableAsNumber(RenamedGlobalVariableFromASharedName) + "
"MyExtension::GetVariableAsNumber(MyRenamedSceneVariable) + "
"MyExtension::GetVariableAsNumber(MySceneVariable2) + "
"MyExtension::GetVariableAsNumber(SharedVariableName) + "
// "globalvar" and "scenevar" in a free function, with child
// variables:
"MyExtension::GetGlobalVariableAsNumber(MyRenamedGlobalStructureVariable.MyChild) + "
"MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable2.MyChild) + "
"MyExtension::GetVariableAsNumber(MyRenamedSceneStructureVariable.MyChild) + "
"MyExtension::GetVariableAsNumber(MySceneStructureVariable2.MyChild) + "
// "globalvar" and "scenevar" in a free function, with grand child
// variables:
"MyExtension::GetGlobalVariableAsNumber(MyRenamedGlobalStructureVariable.MyChild.GrandChild) + "
"MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable2.MyChild.GrandChild) + "
"MyExtension::GetVariableAsNumber(MyRenamedSceneStructureVariable.MyChild.GrandChild) + "
"MyExtension::GetVariableAsNumber(MySceneStructureVariable2.MyChild.GrandChild)");
// Updated "scenevar", "globalvar" and "object" parameters of an
// instruction:
REQUIRE(event.GetActions()[2].GetParameter(0).GetPlainString() ==
"MyRenamedSceneVariable");
REQUIRE(event.GetActions()[2].GetParameter(1).GetPlainString() ==
"MyRenamedGlobalVariable");
REQUIRE(event.GetActions()[2].GetParameter(2).GetPlainString() ==
"Object1");
REQUIRE(event.GetActions()[2].GetParameter(3).GetPlainString() ==
"MyRenamedObjectVariable");
// Updated "scenevar" and "globalvar" parameters of an instruction:
REQUIRE(event.GetActions()[3].GetParameter(0).GetPlainString() ==
"SharedVariableName");
REQUIRE(event.GetActions()[3].GetParameter(1).GetPlainString() ==
"RenamedGlobalVariableFromASharedName");
// Unchanged "object" and "objectvar" parameters:
REQUIRE(event.GetActions()[3].GetParameter(2).GetPlainString() ==
"Object2");
REQUIRE(event.GetActions()[3].GetParameter(3).GetPlainString() ==
"MyObjectVariable");
}
REQUIRE(forEachChildVariableEvent.GetIterableVariableName() == "MyRenamedSceneStructureVariable");
REQUIRE(repeatEvent.GetRepeatExpression() == "1 + MyRenamedSceneVariable + Object1.MyRenamedObjectVariable + Object2.MyObjectVariable");
// clang-format on
// Check the other layout is untouched.
{
gd::SerializerElement serializedLayout2;
layout2.SerializeTo(serializedLayout2);
REQUIRE(gd::Serializer::ToJSON(serializedLayout2) ==
gd::Serializer::ToJSON(originalSerializedLayout2));
}
}
SECTION("Variable removed (project, layout, object)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &layout1 = project.InsertNewLayout("Layout1", 0);
gd::StandardEvent &event =
dynamic_cast<gd::StandardEvent &>(layout1.GetEvents().InsertNewEvent(
project, "BuiltinCommonInstructions::Standard"));
// Declare global variables.
project.GetVariables().InsertNew("MyGlobalVariable", 0).SetValue(123);
project.GetVariables().InsertNew("MyGlobalVariable2", 0).SetValue(123);
project.GetVariables().InsertNew("SharedVariableName", 0).SetValue(123);
project.GetVariables()
.InsertNew("MyGlobalStructureVariable", 0)
.GetChild("MyChild")
.SetValue(123);
project.GetVariables()
.InsertNew("MyGlobalStructureVariable2", 0)
.GetChild("MyChild")
.SetValue(123);
// Declare variables in the scene.
layout1.GetVariables().InsertNew("MySceneVariable", 0).SetValue(123);
layout1.GetVariables().InsertNew("MySceneVariable2", 0).SetValue(123);
layout1.GetVariables().InsertNew("SharedVariableName", 0).SetValue(123);
layout1.GetVariables()
.InsertNew("MySceneStructureVariable", 0)
.GetChild("MyChild")
.SetValue(123);
layout1.GetVariables()
.InsertNew("MySceneStructureVariable2", 0)
.GetChild("MyChild")
.SetValue(123);
// Declare variables in objects.
auto &object1 =
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
object1.GetVariables().InsertNew("MyObjectVariable");
object1.GetVariables()
.InsertNew("MyObjectStructureVariable")
.GetChild("MyChild")
.SetValue(123);
auto &object2 =
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object2", 0);
object2.GetVariables().InsertNew("MyObjectVariable");
object2.GetVariables()
.InsertNew("MyObjectStructureVariable")
.GetChild("MyChild")
.SetValue(123);
auto makeTestAction = [](const gd::String &expression) {
gd::Instruction action;
action.SetType("MyExtension::DoSomething");
action.SetParametersCount(1);
action.SetParameter(0, expression);
return action;
};
// Create an event using the variables.
// clang-format off
event.GetActions().Insert(makeTestAction("1 + MySceneVariable"));
event.GetActions().Insert(makeTestAction("1 + MySceneVariable2"));
event.GetActions().Insert(makeTestAction("1 + Object1.MyObjectVariable"));
event.GetActions().Insert(makeTestAction("1 + Object2.MyObjectVariable"));
event.GetActions().Insert(makeTestAction("1 + Object1.MyObjectStructureVariable.MyChild"));
event.GetActions().Insert(makeTestAction("1 + Object2.MyObjectStructureVariable.MyChild"));
event.GetActions().Insert(makeTestAction("1 + MySceneStructureVariable.MyChild"));
event.GetActions().Insert(makeTestAction("1 + MySceneStructureVariable2.MyChild"));
event.GetActions().Insert(makeTestAction("1 + MyGlobalVariable"));
event.GetActions().Insert(makeTestAction("1 + MyGlobalVariable2"));
event.GetActions().Insert(makeTestAction("1 + MyGlobalStructureVariable.MyChild"));
event.GetActions().Insert(makeTestAction("1 + MyGlobalStructureVariable2.MyChild"));
// Expressions with "old" "scenevar", "globalvar", "objectvar":
// "objectvar" (in a free expression):
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object1, MyObjectVariable, Object2, MyObjectVariable)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object2, MyObjectVariable, Object2, MyObjectVariable)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object1, MyObjectStructureVariable.MyChild, Object2, MyObjectStructureVariable.MyChild)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object2, MyObjectStructureVariable.MyChild, Object2, MyObjectStructureVariable.MyChild)"));
// "objectvar" (using the name of the object being called):
event.GetActions().Insert(makeTestAction("1 + Object1.GetObjectVariableAsNumber(MyObjectVariable)"));
event.GetActions().Insert(makeTestAction("1 + Object2.GetObjectVariableAsNumber(MyObjectVariable)"));
event.GetActions().Insert(makeTestAction("1 + Object1.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild)"));
event.GetActions().Insert(makeTestAction("1 + Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild)"));
event.GetActions().Insert(makeTestAction("1 + Object1.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild.GrandChild)"));
event.GetActions().Insert(makeTestAction("1 + Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild.GrandChild)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetGlobalVariableAsNumber(MyGlobalVariable)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetGlobalVariableAsNumber(MyGlobalVariable2)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetGlobalVariableAsNumber(SharedVariableName)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetVariableAsNumber(MySceneVariable)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetVariableAsNumber(MySceneVariable2)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetVariableAsNumber(SharedVariableName)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable.MyChild)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable2.MyChild)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetVariableAsNumber(MySceneStructureVariable.MyChild)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetVariableAsNumber(MySceneStructureVariable2.MyChild)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable.MyChild.GrandChild)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable2.MyChild.GrandChild)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetVariableAsNumber(MySceneStructureVariable.MyChild.GrandChild)"));
event.GetActions().Insert(makeTestAction("1 + MyExtension::GetVariableAsNumber(MySceneStructureVariable2.MyChild.GrandChild)"));
{
gd::Instruction action;
action.SetType("MyExtension::DoSomethingWithLegacyPreScopedVariables");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("MySceneVariable")); // To remove
action.SetParameter(1, gd::Expression("MyGlobalVariable2"));
action.SetParameter(2, gd::Expression("Object2"));
action.SetParameter(3, gd::Expression("MyObjectVariable"));
event.GetActions().Insert(action);
}
{
gd::Instruction action;
action.SetType("MyExtension::DoSomethingWithLegacyPreScopedVariables");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("MySceneVariable2"));
action.SetParameter(1, gd::Expression("MyGlobalVariable")); // To remove
action.SetParameter(2, gd::Expression("Object2"));
action.SetParameter(3, gd::Expression("MyObjectVariable"));
event.GetActions().Insert(action);
}
{
gd::Instruction action;
action.SetType("MyExtension::DoSomethingWithLegacyPreScopedVariables");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("MySceneVariable2"));
action.SetParameter(1, gd::Expression("MyGlobalVariable2"));
action.SetParameter(2, gd::Expression("Object1"));
action.SetParameter(3, gd::Expression("MyObjectVariable")); // To remove
event.GetActions().Insert(action);
}
{
gd::Instruction action;
action.SetType("MyExtension::DoSomethingWithLegacyPreScopedVariables");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("MySceneVariable2"));
action.SetParameter(1, gd::Expression("MyGlobalVariable2"));
action.SetParameter(2, gd::Expression("Object2"));
action.SetParameter(3, gd::Expression("MyObjectVariable"));
event.GetActions().Insert(action);
}
// clang-format on
// Do a copy of layout1 to ensure other scene is unchanged after the
// refactoring.
gd::Layout layout2 = layout1;
layout2.SetName("Layout2");
project.InsertLayout(layout2, 1);
gd::SerializerElement originalSerializedLayout2;
layout2.SerializeTo(originalSerializedLayout2);
// Do the changes and launch the refactoring.
project.GetVariables().ResetPersistentUuid();
layout1.GetVariables().ResetPersistentUuid();
object1.ResetPersistentUuid();
gd::SerializerElement originalSerializedProjectVariables;
project.GetVariables().SerializeTo(originalSerializedProjectVariables);
gd::SerializerElement originalSerializedLayoutVariables;
layout1.GetVariables().SerializeTo(originalSerializedLayoutVariables);
gd::SerializerElement originalSerializedObject1Variables;
object1.GetVariables().SerializeTo(originalSerializedObject1Variables);
project.GetVariables().Remove("MyGlobalVariable");
project.GetVariables().Remove("MyGlobalStructureVariable");
project.GetVariables().Remove("SharedVariableName");
auto changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedProjectVariables,
project.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
project.GetVariables(),
changeset);
layout1.GetVariables().Remove("MySceneVariable");
layout1.GetVariables().Remove("MySceneStructureVariable");
changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedLayoutVariables,
layout1.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
layout1.GetVariables(),
changeset);
object1.GetVariables().Remove("MyObjectVariable");
object1.GetVariables().Remove("MyObjectStructureVariable");
changeset = gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
project,
originalSerializedObject1Variables,
object1.GetVariables());
gd::WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
project,
object1.GetVariables(),
changeset);
// Check the first layout is updated.
{
REQUIRE(event.GetActions().GetCount() == 19);
// clang-format off
// All the actions using the removed variables are gone.
REQUIRE(event.GetActions()[0].GetParameter(0).GetPlainString() == "1 + MySceneVariable2");
REQUIRE(event.GetActions()[1].GetParameter(0).GetPlainString() == "1 + Object2.MyObjectVariable");
REQUIRE(event.GetActions()[2].GetParameter(0).GetPlainString() == "1 + Object2.MyObjectStructureVariable.MyChild");
REQUIRE(event.GetActions()[3].GetParameter(0).GetPlainString() == "1 + MySceneStructureVariable2.MyChild");
REQUIRE(event.GetActions()[4].GetParameter(0).GetPlainString() == "1 + MyGlobalVariable2");
REQUIRE(event.GetActions()[5].GetParameter(0).GetPlainString() == "1 + MyGlobalStructureVariable2.MyChild");
REQUIRE(event.GetActions()[6].GetParameter(0).GetPlainString() == "1 + MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object2, MyObjectVariable, Object2, MyObjectVariable)");
REQUIRE(event.GetActions()[7].GetParameter(0).GetPlainString() == "1 + MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam(Object2, MyObjectStructureVariable.MyChild, Object2, MyObjectStructureVariable.MyChild)");
REQUIRE(event.GetActions()[8].GetParameter(0).GetPlainString() == "1 + Object2.GetObjectVariableAsNumber(MyObjectVariable)");
REQUIRE(event.GetActions()[9].GetParameter(0).GetPlainString() == "1 + Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild)");
REQUIRE(event.GetActions()[10].GetParameter(0).GetPlainString() == "1 + Object2.GetObjectVariableAsNumber(MyObjectStructureVariable.MyChild.GrandChild)");
REQUIRE(event.GetActions()[11].GetParameter(0).GetPlainString() == "1 + MyExtension::GetGlobalVariableAsNumber(MyGlobalVariable2)");
REQUIRE(event.GetActions()[12].GetParameter(0).GetPlainString() == "1 + MyExtension::GetVariableAsNumber(MySceneVariable2)");
REQUIRE(event.GetActions()[13].GetParameter(0).GetPlainString() == "1 + MyExtension::GetVariableAsNumber(SharedVariableName)");
REQUIRE(event.GetActions()[14].GetParameter(0).GetPlainString() == "1 + MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable2.MyChild)");
REQUIRE(event.GetActions()[15].GetParameter(0).GetPlainString() == "1 + MyExtension::GetVariableAsNumber(MySceneStructureVariable2.MyChild)");
REQUIRE(event.GetActions()[16].GetParameter(0).GetPlainString() == "1 + MyExtension::GetGlobalVariableAsNumber(MyGlobalStructureVariable2.MyChild.GrandChild)");
REQUIRE(event.GetActions()[17].GetParameter(0).GetPlainString() == "1 + MyExtension::GetVariableAsNumber(MySceneStructureVariable2.MyChild.GrandChild)");
REQUIRE(event.GetActions()[18].GetParameter(0).GetPlainString() == "MySceneVariable2");
REQUIRE(event.GetActions()[18].GetParameter(1).GetPlainString() == "MyGlobalVariable2");
REQUIRE(event.GetActions()[18].GetParameter(2).GetPlainString() == "Object2");
REQUIRE(event.GetActions()[18].GetParameter(3).GetPlainString() == "MyObjectVariable");
// clang-format on
}
// Check the other layout is untouched.
{
gd::SerializerElement serializedLayout2;
layout2.SerializeTo(serializedLayout2);
REQUIRE(gd::Serializer::ToJSON(serializedLayout2) ==
gd::Serializer::ToJSON(originalSerializedLayout2));
}
}
}

View File

@@ -306,7 +306,7 @@ const void SetupEvents(gd::EventsList &eventList) {
action.SetParameter(
0,
gd::Expression(
"ObjectWithMyBehavior.GetObjectNumber()"));
"ObjectWithMyBehavior.GetObjectNumber() + ObjectWithMyBehavior.MyVariable + ObjectWithMyBehavior.MyStructureVariable.Child"));
event.GetActions().Insert(action);
eventList.InsertEvent(event);
}
@@ -415,13 +415,13 @@ const void SetupEvents(gd::EventsList &eventList) {
if (eventList.GetEventsCount() != BehaviorSharedPropertyAction) {
throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount()));
}
// Create an event in the layout using "MyProperty" action
// Create an event in the layout using "MySharedProperty" action
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType(
"MyEventsExtension::MyEventsBasedBehavior::" +
gd::EventsBasedBehavior::GetSharedPropertyActionName("MyProperty"));
gd::EventsBasedBehavior::GetSharedPropertyActionName("MySharedProperty"));
event.GetActions().Insert(instruction);
eventList.InsertEvent(event);
}
@@ -429,13 +429,13 @@ const void SetupEvents(gd::EventsList &eventList) {
if (eventList.GetEventsCount() != BehaviorSharedPropertyCondition) {
throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount()));
}
// Create an event in the layout using "MyProperty" condition
// Create an event in the layout using "MySharedProperty" condition
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType(
"MyEventsExtension::MyEventsBasedBehavior::" +
gd::EventsBasedBehavior::GetSharedPropertyConditionName("MyProperty"));
gd::EventsBasedBehavior::GetSharedPropertyConditionName("MySharedProperty"));
event.GetConditions().Insert(instruction);
eventList.InsertEvent(event);
}
@@ -443,7 +443,7 @@ const void SetupEvents(gd::EventsList &eventList) {
if (eventList.GetEventsCount() != BehaviorSharedPropertyExpression) {
throw std::logic_error("Invalid events setup: " + std::to_string(eventList.GetEventsCount()));
}
// Create an event in the layout using "MyProperty" expression
// Create an event in the layout using "MySharedProperty" expression
{
gd::StandardEvent event;
gd::Instruction instruction;
@@ -452,7 +452,7 @@ const void SetupEvents(gd::EventsList &eventList) {
instruction.SetParameter(
0, gd::Expression("ObjectWithMyBehavior.MyBehavior::" +
gd::EventsBasedBehavior::GetSharedPropertyExpressionName(
"MyProperty") +
"MySharedProperty") +
"()"));
event.GetActions().Insert(instruction);
eventList.InsertEvent(event);
@@ -929,11 +929,15 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) {
.SetFunctionType(gd::EventsFunction::ActionWithOperator)
.SetGetterName("MyBehaviorEventsFunctionExpressionAndCondition");
// Add property
// Add a property:
eventsBasedBehavior.GetPropertyDescriptors()
.InsertNew("MyProperty", 0)
.SetType("Number");
// The same name is used for the shared property to ensure there is no name
// Add a shared property:
eventsBasedBehavior.GetSharedPropertyDescriptors()
.InsertNew("MySharedProperty", 0)
.SetType("Number");
// The same name is used for another shared property to ensure there is no name
// collision.
eventsBasedBehavior.GetSharedPropertyDescriptors()
.InsertNew("MyProperty", 0)
@@ -1068,6 +1072,8 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) {
auto &childObject = eventsBasedObject.InsertNewObject(
project, "MyExtension::Sprite", "ObjectWithMyBehavior", 0);
childObject.AddNewBehavior(project, "MyEventsExtension::MyEventsBasedBehavior", "MyBehavior");
childObject.GetVariables().InsertNew("MyVariable");
childObject.GetVariables().InsertNew("MyStructureVariable").CastTo(gd::Variable::Structure);
auto &group = eventsBasedObject.GetObjectGroups().InsertNew("GroupWithMyBehavior");
group.AddObject(childObject.GetName());
@@ -1154,6 +1160,8 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) {
auto &object = layout.InsertNewObject(project, "MyExtension::Sprite",
"ObjectWithMyBehavior", 0);
object.AddNewBehavior(project, "MyEventsExtension::MyEventsBasedBehavior", "MyBehavior");
object.GetVariables().InsertNew("MyVariable");
object.GetVariables().InsertNew("MyStructureVariable").CastTo(gd::Variable::Structure);
auto &group = layout.GetObjectGroups().InsertNew("GroupWithMyBehavior", 0);
group.AddObject("ObjectWithMyBehavior");
@@ -1455,10 +1463,10 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
REQUIRE(GetEventFirstActionFirstParameterString(eventsList->GetEvent(
FreeFunctionWithObjects)) == "RenamedObjectWithMyBehavior");
// Check object name has been renamed in expressions.
// Check object name has been renamed in expressions and in object variables.
REQUIRE(GetEventFirstActionFirstParameterString(
eventsList->GetEvent(FreeFunctionWithObjectExpression)) ==
"RenamedObjectWithMyBehavior.GetObjectNumber()");
"RenamedObjectWithMyBehavior.GetObjectNumber() + RenamedObjectWithMyBehavior.MyVariable + RenamedObjectWithMyBehavior.MyStructureVariable.Child");
}
}
}
@@ -1567,6 +1575,12 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
gd::ParameterMetadataTools::ParametersToObjectsContainer(
project, eventsFunction.GetParameters(), objectsContainer);
// Simulate a variable in ObjectWithMyBehavior, even if this is not
// supported by the editor.
auto& objectWithMyBehavior = objectsContainer.GetObject("ObjectWithMyBehavior");
objectWithMyBehavior.GetVariables().InsertNew("MyVariable");
objectWithMyBehavior.GetVariables().InsertNew("MyStructureVariable").CastTo(gd::Variable::Structure);
// Trigger the refactoring after the renaming of an object
gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction(
project, eventsFunction, globalObjectsContainer, objectsContainer,
@@ -1579,11 +1593,11 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
eventsFunction.GetEvents().GetEvent(FreeFunctionWithObjects)) ==
"RenamedObjectWithMyBehavior");
// Check object name has been renamed in expressions.
// Check object name has been renamed in expressions and object variables.
REQUIRE(GetEventFirstActionFirstParameterString(
eventsFunction.GetEvents().GetEvent(
FreeFunctionWithObjectExpression)) ==
"RenamedObjectWithMyBehavior.GetObjectNumber()");
"RenamedObjectWithMyBehavior.GetObjectNumber() + RenamedObjectWithMyBehavior.MyVariable + RenamedObjectWithMyBehavior.MyStructureVariable.Child");
}
}
@@ -1618,10 +1632,10 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
objectFunctionEvents.GetEvent(FreeFunctionWithObjects)) ==
"RenamedObjectWithMyBehavior");
// Check object name has been renamed in expressions.
// Check object name has been renamed in expressions and object variables.
REQUIRE(GetEventFirstActionFirstParameterString(
objectFunctionEvents.GetEvent(FreeFunctionWithObjectExpression)) ==
"RenamedObjectWithMyBehavior.GetObjectNumber()");
"RenamedObjectWithMyBehavior.GetObjectNumber() + RenamedObjectWithMyBehavior.MyVariable + RenamedObjectWithMyBehavior.MyStructureVariable.Child");
}
SECTION("Object deleted (in events function)") {
@@ -1742,7 +1756,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
eventsList->GetEvent(BehaviorActionWithOperator)) ==
"MyRenamedExtension::MyEventsBasedBehavior::"
"MyBehaviorEventsFunctionActionWithOperator");
// Check if events-based behaviors properties have been renamed in
// instructions
REQUIRE(GetEventFirstActionType(
@@ -1752,7 +1766,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
REQUIRE(GetEventFirstActionType(
eventsList->GetEvent(BehaviorSharedPropertyAction)) ==
"MyRenamedExtension::MyEventsBasedBehavior::"
"SetSharedPropertyMyProperty");
"SetSharedPropertyMySharedProperty");
// Check events-based behavior methods have *not* been renamed in
// expressions
@@ -2114,7 +2128,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
REQUIRE(GetEventFirstActionType(
eventsList->GetEvent(BehaviorSharedPropertyAction)) ==
"MyEventsExtension::MyRenamedEventsBasedBehavior::"
"SetSharedPropertyMyProperty");
"SetSharedPropertyMySharedProperty");
// Check events-based behavior methods have *not* been renamed in
// expressions
@@ -2729,16 +2743,16 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
REQUIRE(GetEventFirstActionType(
eventsList->GetEvent(BehaviorSharedPropertyAction)) ==
"MyEventsExtension::MyEventsBasedBehavior::"
"SetSharedPropertyMyProperty");
"SetSharedPropertyMySharedProperty");
REQUIRE(GetEventFirstConditionType(
eventsList->GetEvent(BehaviorSharedPropertyCondition)) ==
"MyEventsExtension::MyEventsBasedBehavior::"
"SharedPropertyMyProperty");
"SharedPropertyMySharedProperty");
REQUIRE(GetEventFirstActionFirstParameterString(
eventsList->GetEvent(BehaviorSharedPropertyExpression)) ==
"ObjectWithMyBehavior.MyBehavior::SharedPropertyMyProperty()");
"ObjectWithMyBehavior.MyBehavior::SharedPropertyMySharedProperty()");
}
}
@@ -2750,6 +2764,11 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
auto &eventsBasedBehavior =
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
gd::WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
project, eventsExtension, eventsBasedBehavior, "MySharedProperty",
"MyRenamedSharedProperty");
// Also wrongly try to rename a property that is not a shared property.
gd::WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
project, eventsExtension, eventsBasedBehavior, "MyProperty",
"MyRenamedProperty");
@@ -2760,16 +2779,16 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
REQUIRE(GetEventFirstActionType(
eventsList->GetEvent(BehaviorSharedPropertyAction)) ==
"MyEventsExtension::MyEventsBasedBehavior::"
"SetSharedPropertyMyRenamedProperty");
"SetSharedPropertyMyRenamedSharedProperty");
REQUIRE(GetEventFirstConditionType(
eventsList->GetEvent(BehaviorSharedPropertyCondition)) ==
"MyEventsExtension::MyEventsBasedBehavior::"
"SharedPropertyMyRenamedProperty");
"SharedPropertyMyRenamedSharedProperty");
REQUIRE(GetEventFirstActionFirstParameterString(
eventsList->GetEvent(BehaviorSharedPropertyExpression)) ==
"ObjectWithMyBehavior.MyBehavior::SharedPropertyMyRenamedProperty()");
"ObjectWithMyBehavior.MyBehavior::SharedPropertyMyRenamedSharedProperty()");
// Ensure that the property was NOT renamed.
REQUIRE(GetEventFirstActionType(
@@ -2951,7 +2970,7 @@ TEST_CASE("WholeProjectRefactorer (FixInvalidRequiredBehaviorProperties)",
// - add the behavior "A" to an object
// Check that no behavior is added on the object for it and that there is no
// crash.
SECTION("Fix nothing if there are no missing required behavior") {
gd::Project project;
gd::Platform platform;
@@ -3732,7 +3751,7 @@ TEST_CASE("RemoveLayer", "[common]") {
layout.InsertNewLayer("My layer", 0);
otherLayout.InsertNewLayer("My layer", 0);
auto &externalLayout =
project.InsertNewExternalLayout("My external layout", 0);
auto &otherExternalLayout =
@@ -3746,7 +3765,7 @@ TEST_CASE("RemoveLayer", "[common]") {
initialInstances.InsertNewInitialInstance().SetLayer("My layer");
initialInstances.InsertNewInitialInstance().SetLayer("");
initialInstances.InsertNewInitialInstance().SetLayer("");
auto &externalInitialInstances = externalLayout.GetInitialInstances();
externalInitialInstances.InsertNewInitialInstance().SetLayer("My layer");
externalInitialInstances.InsertNewInitialInstance().SetLayer("My layer");
@@ -3789,7 +3808,7 @@ TEST_CASE("MergeLayers", "[common]") {
layout.InsertNewLayer("My layer", 0);
otherLayout.InsertNewLayer("My layer", 0);
auto &externalLayout =
project.InsertNewExternalLayout("My external layout", 0);
auto &otherExternalLayout =
@@ -3804,7 +3823,7 @@ TEST_CASE("MergeLayers", "[common]") {
initialInstances.InsertNewInitialInstance().SetLayer("");
initialInstances.InsertNewInitialInstance().SetLayer("");
initialInstances.InsertNewInitialInstance().SetLayer("My other layer");
auto &externalInitialInstances = externalLayout.GetInitialInstances();
externalInitialInstances.InsertNewInitialInstance().SetLayer("My layer");
externalInitialInstances.InsertNewInitialInstance().SetLayer("My layer");

View File

@@ -144,6 +144,40 @@ namespace gdjs {
return this._z;
}
/**
* Get the Z position of the rendered object.
*
* For most objects, this will returns the same value as getZ(). But if the
* object has an origin that is not the same as the point (0,0,0) of the
* object displayed, getDrawableZ will differ.
*
* @return The Z position of the rendered object.
*/
getDrawableZ(): float {
return this.getZ();
}
/**
* Return the Z position of the object center, **relative to the object Z
* position** (`getDrawableX`).
*
* Use `getCenterZInScene` to get the position of the center in the scene.
*
* @return the Z position of the object center, relative to
* `getDrawableZ()`.
*/
getCenterZ(): float {
return this.getDepth() / 2;
}
getCenterZInScene(): float {
return this.getDrawableZ() + this.getCenterZ();
}
setCenterZInScene(z: float): void {
this.setZ(z + this._z - (this.getDrawableZ() + this.getCenterZ()));
}
setAngle(angle: float): void {
super.setAngle(angle);
this.getRenderer().updateRotation();

View File

@@ -15,6 +15,17 @@ namespace gdjs {
*/
getZ(): float;
/**
* Return the Z position of the object center, **relative to the scene origin**.
*/
getCenterZInScene(): float;
/**
* Change the object center Z position in the scene.
* @param z The new Z position of the center in the scene.
*/
setCenterZInScene(z: float): void;
/**
* Set the object rotation on the X axis.
*
@@ -132,6 +143,14 @@ namespace gdjs {
return this.object.getZ();
}
getCenterZInScene(): number {
return this.object.getCenterZInScene();
}
setCenterZInScene(z: number): void {
this.object.setCenterZInScene(z);
}
setRotationX(angle: float): void {
this.object.setRotationX(angle);
}

View File

@@ -67,6 +67,22 @@ module.exports = {
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('setZ')
.setGetter('getZ');
base3D
.addExpressionAndConditionAndAction(
'number',
'CenterZ',
_('Center Z position'),
_('the Z position of the center of rotation'),
_('the Z position of the center'),
_('Position/Center'),
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('setCenterZInScene')
.setGetter('getCenterZInScene');
base3D
.addExpressionAndConditionAndAction(

View File

@@ -298,6 +298,11 @@ namespace gdjs {
return this.getHeight() * centerPoint[1];
}
getCenterZ(): float {
const centerPoint = this._renderer.getCenterPoint();
return this.getDepth() * centerPoint[2];
}
getDrawableX(): float {
const originPoint = this._renderer.getOriginPoint();
return this.getX() - this.getWidth() * originPoint[0];
@@ -307,6 +312,11 @@ namespace gdjs {
const originPoint = this._renderer.getOriginPoint();
return this.getY() - this.getHeight() * originPoint[1];
}
getDrawableZ(): float {
const originPoint = this._renderer.getOriginPoint();
return this.getZ() - this.getDepth() * originPoint[2];
}
}
export namespace Model3DRuntimeObject {

Some files were not shown because too many files have changed in this diff Show More