Compare commits

...

200 Commits

Author SHA1 Message Date
Florian Rival
831dce0f51 Bump newIDE version 2020-04-15 09:58:22 +02:00
Florian Rival
2da4e79d06 Upgrade material-ui to 4.9.10 to avoid infinite rerender of text areas (#1656) 2020-04-14 23:03:55 +02:00
Florian Rival
804a07c56e Add "Jump sustain time" to Platformer Object behavior (#1650) 2020-04-14 11:24:45 +02:00
Florian Rival
b367f13116 Fix popovers in ForEach/JsCode/Link/Repeat events
Also ensure the fields are focused when opened.
Fix #1653.
2020-04-14 10:04:33 +02:00
Florian Rival
581d7716f7 Fix imports 2020-04-14 10:01:27 +02:00
Nilay Majorwar
19de7aefbc Fix confirmation dialogs making the app lose the focus on Windows (#1649)
Fix #1646

Electron default confirm and alert dialogs don't play nicely with the focus on Windows.
2020-04-14 09:57:22 +02:00
Florian Rival
57759aa1b8 Run auto formatting on platformerobjectruntimebehavior.spec.js 2020-04-13 21:39:38 +02:00
Todor Imreorov
07876afc28 fix "Select option by number" in Dialogue Tree (#1648) 2020-04-13 19:17:06 +02:00
Aurélien Vivet
b9029fba4d Fix help button for BBText objects (#1647) 2020-04-13 14:46:13 +02:00
Florian Rival
c4ba357296 Bump newIDE version 2020-04-13 11:16:06 +02:00
Florian Rival
a47acbb82a Fix deploy script for web-app 2020-04-12 20:05:58 +02:00
Florian Rival
f1f93c9be0 Update translations 2020-04-12 19:54:05 +02:00
Florian Rival
f56d864efb Add cannon-ball-with-physics example 2020-04-12 19:29:19 +02:00
Florian Rival
017f8cf554 Update flappy bird example 2020-04-12 19:19:12 +02:00
Arthur Pacaud
d8546c5547 Add doc and type annotations to gdjs.WebsocketDebuggerClient (#1637) 2020-04-11 18:37:28 +02:00
Florian Rival
0bc6e41709 Remove extra console.log and close confirm dialog in development 2020-04-11 16:28:55 +02:00
Florian Rival
943fac772d Fix translation and warn about unsaved changes in CloseConfirmDialog 2020-04-10 23:24:34 +02:00
Florian Rival
327e4cb6a3 Fix horizontal scrollbar in ExpressionAutocompletionsDisplayer 2020-04-10 19:58:12 +02:00
Florian Rival
277989f329 Fix ExpressionSelector positioning when errors shown in an expression 2020-04-10 19:58:12 +02:00
Florian Rival
2bdae4ed14 Display autocompletions at the bottom of the input 2020-04-10 19:58:12 +02:00
Florian Rival
4bfbd7c78f Add autocompletions when typing an expression
Add ExpressionAutocompletion, ExpressionAutocompletionsDisplayer, ExpressionAutocompletionsHandler.
2020-04-10 19:58:12 +02:00
Harsimran Singh Virk
7b76564dda Fix for redundant scrollbar in Project Manager (#1635) 2020-04-10 14:35:50 +02:00
Florian Rival
41a2b87dd0 Fix unsaved changes not triggered when editing an object 2020-04-09 22:56:52 +02:00
Nilay Majorwar
397781bb98 Disable some menu items if no project is opened (#1626) 2020-04-09 22:25:23 +02:00
Florian Rival
b4fa741717 Upgrade to electron-builder 21.2.0
* Use new entitlement to avoid the packaged and signed macOS app to crash when launched.
* Don't upgrade to a newer version of electron-builder because not compatible with macOS 10.13 it seems
2020-04-09 21:47:14 +02:00
Nilay Majorwar
6e16bd764f Upgrade IDE to Electron v8.2.1 (#1589) 2020-04-09 21:34:51 +02:00
Florian Rival
1071b66c92 Enhance typing of MenuItemTemplate in the whole app 2020-04-08 19:42:02 +02:00
Florian Rival
9ff6d91717 Simplify a bit extra menu item handling in ProjectManager 2020-04-08 19:24:35 +02:00
Nilay Majorwar
bda1eb01d2 Add menu items to scene context menu to edit properties or variables (#1607) 2020-04-08 18:43:32 +02:00
Florian Rival
dfcaf472c7 Fix tests 2020-04-07 16:26:06 +02:00
Florian Rival
c8a9da6aea Fix some bad characters in results of HelpFinder 2020-04-05 19:26:23 +02:00
Florian Rival
daa50931ae Fix formatting 2020-04-05 17:31:01 +02:00
Florian Rival
f84a97a4f2 Move Cursor/touch on object condition to an "object" condition
See #1611
2020-04-05 17:25:09 +02:00
Aurélien Vivet
10d5c403a7 Add preload auto to fix video in electron 8+ and Chrome 76+ (#1624) 2020-04-05 17:14:28 +02:00
Florian Rival
15471e6e28 Move EventsScope to InstructionOrExpression folder 2020-04-05 14:21:35 +02:00
Florian Rival
7ccebc69fa Fix warnings in Storybook 2020-04-05 12:12:03 +02:00
Florian Rival
4c57fbc01f Rename enumerateInstructions to enumerateAllInstructions 2020-04-05 12:02:45 +02:00
Florian Rival
ed58ebd3be Add imperative methods to SemiControlledTextField 2020-04-05 11:40:08 +02:00
dependabot[bot]
9cae4fb264 Bump minimist from 1.2.0 to 1.2.3 in /newIDE/web-app (#1623)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.3.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.0...1.2.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-04-05 10:49:51 +02:00
Florian Rival
83e7314863 Clean the GDJS runtime folder only when starting the development app
Potential fix for filesystem issue like #1528
2020-04-04 22:36:03 +02:00
Florian Rival
8ec56164af Update GDevelop-services urls to use proper subdomains 2020-04-04 15:56:39 +02:00
Aurélien Vivet
ba0c4a9bc4 Improve bug reports with system details (#1612) 2020-04-04 15:51:24 +02:00
dependabot[bot]
8706dc727d Bump minimist from 1.2.0 to 1.2.3 in /newIDE/electron-app/app (#1619)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.3.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.0...1.2.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-04-04 13:53:15 +02:00
dependabot[bot]
7ee9facb34 Bump minimist from 1.2.0 to 1.2.3 in /newIDE/electron-app (#1620)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.3.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.0...1.2.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-04-04 13:52:58 +02:00
gautamv95
4190cbda88 Rationalise dismissing of dialogs with click outside/escape key (#1522)
Fix #1517
2020-04-01 10:05:03 +02:00
Florian Rival
aa73b01bbc Fix warning in ContextMenu 2020-04-01 09:54:43 +02:00
Florian Rival
b242c7863f Add asterisk in titlebar when the file is modified 2020-04-01 09:51:20 +02:00
Apostoles
41550ee10f Track changes to display a smart unsaved changes warning (#1523) 2020-03-31 22:35:08 +02:00
Florian Rival
7af0999f59 Remove obsolete ExternalEditor in LocalApp (#1604) 2020-03-31 09:45:15 +02:00
Nilay Majorwar
93e0ccc163 Add Prettier to electron-app and autoformat files (#1602) 2020-03-30 20:25:08 +02:00
Aurélien Vivet
94303fccc2 Update required CMake version for GDevelop.js (#1600) 2020-03-30 16:05:24 +02:00
Aurélien Vivet
33949fd93c Add option to use MinGW instead ninja for GDevelop.js (#1599) 2020-03-29 23:21:55 +02:00
Harsimran Singh Virk
bd40bb892c Fix selection issues in the Autocomplete lists (#1586) 2020-03-29 21:11:20 +02:00
Florian Rival
c2cf935bd9 Fix typo 2020-03-28 18:24:06 +01:00
Quarkstar
c7d5ab8013 Update Twist/ZoomBlur/RadialBlur to be centered by default (#1564)
Remove dependency on window size, preparing for object effects support
2020-03-25 23:33:55 +01:00
Florian Rival
e8c93a5622 Add autoHideScrollbar and fix scrolling in ScrollView 2020-03-25 22:36:03 +01:00
Garett Tok Ern Liang
348459481a Fix Left/Right Ctrl/Shift/Alt/Meta not recognised as separate keys (#1553) 2020-03-25 21:13:08 +01:00
Florian Rival
e7348f08c4 Update bouncing-ball-and-rope example 2020-03-24 21:46:35 +01:00
Harsimran Singh Virk
ef96adee92 Close InlinePopover after selection in an autocomplete field (#1568)
Also if Escape key is pressed
2020-03-24 08:55:40 +01:00
Florian Rival
66b3ec1686 Rename IsInformative to IsExact in ExpressionCompletionFinder 2020-03-23 23:32:33 +01:00
Arthur Pacaud
b77eb123e8 Move loadObject to registerObject method in gdjs.RuntimeScene (#1572) 2020-03-22 21:32:43 +01:00
Florian Rival
dcba4272e0 Fix description in CRT 2020-03-22 17:16:24 +01:00
Quarkstar
c2dd5a0a09 Allow to change the animation speed/frequency of effects (#1554) 2020-03-22 17:13:05 +01:00
Aurélien Vivet
a6ae265705 Improve visibility of parameter for checkbox in effects list (#1575) 2020-03-22 15:42:51 +01:00
Florian Rival
94fb2fede6 Remove dead code 2020-03-22 15:41:13 +01:00
Jimil Desai
7bcaf55342 Fix warning not showing when using a digit as first character for a name (#1560) 2020-03-22 15:24:28 +01:00
Geetesh Gupta
cc9632e7c1 Remember the directory previously chosen when selecting a resource (#1494) 2020-03-21 13:29:00 +01:00
Aurélien Vivet
3ff5dd7cd2 Add duplicate menu option in Project Manager (#1556) 2020-03-20 09:45:18 +01:00
Florian Rival
1193e1bbd0 Run Prettier autoformatting on some files 2020-03-19 23:17:38 +01:00
Harsimran Singh Virk
9c55b0acc6 Replace Downshift with material-ui Autocomplete. (#1453) 2020-03-19 22:45:21 +01:00
Jimil Desai
d03c1964cb Fix global objects not updated in other editors after modifying them in an editor (#1545) 2020-03-19 19:17:54 +01:00
Aurélien Vivet
e5695aacf6 Fix sentences for video object actions & clean whitespace (#1552) 2020-03-18 21:50:09 +01:00
Florian Rival
8e6d2da9f7 Fix crash in external events 2020-03-18 21:46:32 +01:00
Aurélien Vivet
5c043fd04a Add context menu to select the scene to start the preview with (#1429) 2020-03-18 21:28:18 +01:00
Pranav Shridhar
4edbd9d377 Added shortcut to export project (#1548) 2020-03-18 15:23:08 +01:00
Quarkstar
c6f21955a3 Add BulgePinch, Glitch, RadialBlur and Twist effects (#1531) 2020-03-18 15:20:01 +01:00
Florian Rival
03cc406459 Fix tests 2020-03-18 15:03:11 +01:00
Ferry Pie
c5d855b768 Make table headers bolder (#1512) 2020-03-17 15:03:40 +01:00
Pranav Shridhar
2aba1c57c4 Fix spinner shown in "See all my builds" when not logged in (#1542)
Fix #1541
2020-03-17 14:54:53 +01:00
Minisonny
9614549436 Fix ContextMenu positioning in the web-app (#1527)
Fix #1492
2020-03-17 09:36:26 +01:00
Florian Rival
f1730c239d Add type to ExpressionCompletionDescription::ForObject 2020-03-16 23:08:05 +01:00
Pranav Shridhar
acb19bf8ed Fix typos (#1535) 2020-03-16 19:03:58 +01:00
Florian Rival
4d63fbcca0 Fix Flow typing of components with useImperativeHandle 2020-03-15 13:12:30 +01:00
Florian Rival
eb19b6ba21 Remove Fullstory analytics 2020-03-14 18:48:45 +01:00
Florian Rival
d2747782b4 Remove mention to babel-loader in package.json.README.md 2020-03-14 17:24:20 +01:00
Nilay Majorwar
8202e6b38e Update react-scripts to v3.4.0 (#1526) 2020-03-14 17:22:08 +01:00
Florian Rival
6deb4fa122 Merge branch 'master' of github.com:4ian/GDevelop 2020-03-14 13:58:58 +01:00
Nilay Majorwar
3990064da9 Add Exit GDevelop option to File menu (#1524)
Closes #1498
2020-03-14 12:45:04 +01:00
Quarkstar
a315eabdce Reorder Effects/JsExtension.js (#1519) 2020-03-12 14:57:34 +01:00
Florian Rival
faa02d4459 Add proper handling of closing parenthesis in ExpressionCompletionFinder 2020-03-11 17:41:52 +00:00
Harsimran Singh Virk
8b294ae369 Fix tooltip content for editor titles (#1495)
Fix #1484
2020-03-10 22:21:26 +00:00
Florian Rival
cb43eb7780 Upgrade to Flow 0.120.1 2020-03-10 21:15:48 +00:00
Geetesh Gupta
765295fc5d Add Escape (and Enter) key to finish renaming groups and comments (#1488) 2020-03-09 20:51:42 +00:00
A.Dilshaad
beb3bde4a8 Make New scene naming convention more consistent (#1508) 2020-03-09 10:01:00 +00:00
Aurélien Vivet
fe5b519917 Add blending mode effect (#1499) 2020-03-08 23:34:53 +00:00
Geetesh Gupta
8661fbef07 Fix object name not selectable when renaming an object (#1477)
Fix #1440
2020-03-08 23:09:10 +00:00
Quarkstar
afd11d2480 Add old film, dot and color replace effects (#1497) 2020-03-08 17:44:06 +00:00
Florian Rival
c5ad9715df Fix project name not escaped in Cordova generated config.xml 2020-03-05 21:21:27 +00:00
Florian Rival
4362e8dd42 Add check for libGD.js size in electron app build and fix failure reports 2020-03-05 07:57:31 +00:00
Florian Rival
372fa46709 Fix yarn command wrongly removing space between words (#1491) 2020-03-04 22:39:17 +00:00
Florian Rival
c43cfcd49e Display human readable names for builds in the Builds list (#1489)
Fix #1421
2020-03-04 20:27:14 +00:00
Florian Rival
c471a0af6d Bump newIDE version 2020-03-04 08:37:28 +00:00
Florian Rival
b1ea60e1d2 Fix Box2D.js in the global namespace conflicting with Facebook Instant Games runtime
Tested on Facebook Instant Games Android, iOS
Tested on Desktop Chrome, Firefox, Safari
2020-03-03 22:59:16 +00:00
Florian Rival
f745907f9d Merge branch 'master' of github.com:4ian/GDevelop 2020-03-03 21:54:34 +00:00
Florian Rival
92df124a92 Update translations 2020-03-03 21:54:25 +00:00
Geetesh Gupta
96f26c89ab Fix renaming not cancelled when object name is not changed (#1479) 2020-03-03 20:07:41 +00:00
Florian Rival
3f6428dfcc Merge pull request #1486 from 4ian/feature/more-expression-parser-node-locations
Improver expression parser with more location information
2020-03-02 22:11:57 +00:00
Florian Rival
22c6a57394 Improve completions from ExpressionCompletionFinder 2020-03-02 22:11:31 +00:00
Harsimran Singh Virk
dc942e6abc Fix unscrollable configuration/parameters of Event Functions (#1483) 2020-03-02 18:26:54 +00:00
Quarkstar
f9430a0da1 Fix hash of piskel-editor (#1485)
Fix #1482
2020-03-02 08:56:58 +00:00
Florian Rival
94ac7166ed Add more tests for ExpressionParser2 2020-03-01 19:00:18 +00:00
Florian Rival
88b20240ff Fix location of identifier (function names, etc...) with trailing whitespaces 2020-03-01 18:40:30 +00:00
Florian Rival
af93149f6a Add more location information in ExpressionParser nodes
Also fix some whitespace sensitivity of the parser
2020-03-01 17:37:21 +00:00
Florian Rival
732a716be4 Refactor error display in GenericExpressionField 2020-02-29 17:51:39 +00:00
gautamv95
11b660e05d Fix change of focused tab after a tab is closed (#1480)
Fix #986
2020-02-29 13:36:47 +00:00
Florian Rival
6155606d20 Refactor EnumerateExpressions 2020-02-27 21:00:27 +00:00
Florian Rival
89e3853296 Enforce TypeScript "strictNullChecks" and other strict compiler options 2020-02-25 21:06:04 +00:00
Arthur Pacaud
df655f2269 Removed non-required use of Hashtable.items (#1458) 2020-02-25 20:51:55 +00:00
Florian Rival
6b6ec6f06f Upgrade another package-lock.json file to latest format 2020-02-25 20:46:03 +00:00
Florian Rival
d80a47e569 Upgrade package-lock.json files to latest format 2020-02-25 20:22:01 +00:00
Florian Rival
9cf9d09f3a Refactor GenericExpressionField highlighting styling 2020-02-25 19:24:50 +00:00
Florian Rival
8fa23c5463 Move some files to InstructionOrExpression folder in newIDE 2020-02-25 19:24:50 +00:00
Aurélien Vivet
1c65e3c655 Fix casing in camera actions/conditions (#1467) 2020-02-25 18:21:22 +00:00
Florian Rival
b1e292e04e Update TimeDelta name/descriptions 2020-02-24 23:09:30 +00:00
Arthur Pacaud
e2f8f70d54 Improve JSDoc in gd.js (#1459) 2020-02-23 20:34:37 +00:00
Aurélien Vivet
d04faa039b Add Discord button on StartPage (#1462) 2020-02-23 20:27:57 +00:00
Florian Rival
4e59573042 Speed up identifiers and other character parsing in ExpressionParser2 2020-02-23 19:41:53 +00:00
Florian Rival
07fce517d6 Add ExpressionParser2Benchmarks 2020-02-23 14:52:09 +00:00
Florian Rival
ac90b982ac Hide verbose extension loading logs in tests 2020-02-23 14:49:35 +00:00
Florian Rival
4df974a4d7 Fix error highlighting in GenericExpressionField when shown inline 2020-02-22 18:28:56 +00:00
Florian Rival
cebf1e2a84 Add sanity check for libGD.js size 2020-02-22 16:35:57 +00:00
Florian Rival
650676cc6e Update Breakout example 2020-02-22 15:21:58 +00:00
Florian Rival
a3614a85b8 Add script to automatically upload example resources for the web-app 2020-02-22 15:15:58 +00:00
Florian Rival
ef52fec3ca Add Flappy bird example 2020-02-22 15:00:22 +00:00
Florian Rival
ca6f11b55a Support for describing completions to display for an expression (#1447) 2020-02-22 14:27:43 +00:00
Arthur Pacaud
7015962aa0 Update README-extensions.md (#1450)
As the majority of extensions use JavaScript now.
2020-02-22 12:55:57 +00:00
Florian Rival
4df7f0d10f Add links to "good first issues"/"not too hard" cards 2020-02-22 11:57:58 +00:00
Florian Rival
8c7ffe319a Merge pull request #1443 from 4ian/fix/visit-function-node
Fix WholeProjectRefactorer potential wrong renaming/moving of parameters
2020-02-19 08:36:12 +00:00
Florian Rival
710c2f0304 Fix renaming a property not renaming property expression used in a property action/condition 2020-02-19 08:33:32 +00:00
Florian Rival
727fa8a538 Fix WholeProjectRefactorer renaming/moving parameters of wrong functions for behaviors 2020-02-18 23:27:19 +00:00
Aurélien Vivet
f45e4c2049 Fix whitespace not preserved in comments (#1442) 2020-02-18 20:54:04 +00:00
Florian Rival
3bdf612f8e Fix ExpressionsRenamer when renaming an object function (#1438) 2020-02-18 08:55:25 +00:00
Florian Rival
4f7c91190e Fix formatting 2020-02-17 21:19:45 +00:00
Aurélien Vivet
5ed0c57e48 Improve pixel perfect rendering of games (#1432)
Can help with the issue of "bleeding" tiles.
2020-02-17 21:34:27 +01:00
Florian Rival
3955612e3b Run autoformatting on runtimegame-pixi-renderer.js 2020-02-17 20:27:58 +00:00
Arthur Pacaud
8eede20b07 Fix warning shown after dismissing the rename of an object (#1437) 2020-02-17 21:25:53 +01:00
Aurélien Vivet
3df9b29c3e Improve description of behavior methods and add "Extension" in tab names (#1431) 2020-02-16 23:48:13 +01:00
Florian Rival
280906dd3a Add "once" in description of onFirstSceneLoaded 2020-02-14 16:58:05 +00:00
Florian Rival
94e81ddf40 Add field for GDevelop version compatibility in extension headers and alert in IDE 2020-02-14 16:58:05 +00:00
Florian Rival
392e602651 Add message about export of lifecycle functions only if extension used 2020-02-14 16:58:05 +00:00
Florian Rival
0d60a54fa7 Add support for extension lifecycle events function 2020-02-14 16:58:05 +00:00
Florian Rival
e95a336dd2 Add new GDJS callbacks: first scene loaded, pre/post events 2020-02-12 20:47:43 +00:00
Florian Rival
b8ccf02f70 Refactor callbacks registration in GDJS 2020-02-11 23:33:30 +00:00
Florian Rival
a564a484a7 Rework codeNamespace to be generated by LayoutCodeGenerator instead of EventsCodeGenerator 2020-02-11 21:54:49 +00:00
Florian Rival
b9c1f5f6a7 Add LayoutCodeGenerator 2020-02-11 21:26:00 +00:00
Florian Rival
243bc93fe5 Add EventsFunctionsExtensionCodeGenerator 2020-02-11 20:45:20 +00:00
Florian Rival
d544319302 Add location in ExpressionParser2 and ExpressionNodeLocationFinder 2020-02-11 08:20:09 +00:00
Florian Rival
0fa5988995 Fix broken scripts since update to Emscripten 1.39.6 2020-02-10 23:28:54 +00:00
Arthur Pacaud
746b2f5480 Add more JSDoc/typing to gdjs.RuntimeObject and Hashtable (#1376) 2020-02-10 20:24:11 +00:00
Florian Rival
2cc7c8740e Fix Storybook and add story for NewInstructionEditorMenu 2020-02-09 22:16:09 +00:00
Florian Rival
aa30052dca Fix Events Extractor dialog showing empty extension names 2020-02-09 11:50:45 +00:00
Florian Rival
beb1cf0631 Hide horizontal scrollbar in InlinePopover 2020-02-09 11:22:31 +00:00
Todor Imreorov
78640d74c8 Add InstructionEditor showing as popover when right click on "Add action/condition" (#1416) 2020-02-09 11:17:38 +00:00
Todor Imreorov
7becb0be4b Fix multiple issues in Dialogue Tree (Yarn) extension (#1418)
* Add protection against misusage of the extension
* Fix json loading resource reading at the beginning of scene (thanks @4ian)
* Fix getClippedLineText sometimes clips the last letter
2020-02-08 18:25:09 +00:00
Florian Rival
6c789b7eb0 Remove remote connection for AppVeyor 2020-02-08 10:43:45 +00:00
Florian Rival
f743a785ad Update tests run on Windows on AppVeyor 2020-02-08 10:43:45 +00:00
Florian Rival
bac8aa14fa Fix Windows tests and add AppVeyor CI 2020-02-08 10:43:45 +00:00
Florian Rival
3ebb483e32 Update GDevelop.js Windows build to use embedded Ninja instead of MinGW make 2020-02-08 00:29:19 +00:00
Florian Rival
4fbbd34d40 Fix typo 2020-02-07 17:10:30 +00:00
Florian Rival
24091db88b Add deploy step to upload GDJS (and extensions) Runtime for web-app 2020-02-05 22:09:16 +00:00
Florian Rival
4ec3f1f082 Merge pull request #1406 from 4ian/refactor/emscripten-1.39
Upgrade to Emscripten 1.39.6
2020-02-05 21:58:31 +00:00
Florian Rival
991378e004 Remove debug message 2020-02-05 21:42:28 +00:00
Florian Rival
dfaee92d24 Fix GDevelop.js tests on Windows 2020-02-05 21:35:28 +00:00
Florian Rival
b07c71cb9c Exclude emsdk from tests in GDevelop.js 2020-02-05 21:27:41 +00:00
Florian Rival
cb0c0f903f Avoid full rebuild when changing a CMake parameter in GDevelop.js 2020-02-05 20:52:03 +00:00
Florian Rival
a81a121a8d Fix test coverage not running with Node environment 2020-02-02 20:24:31 +00:00
Florian Rival
7bf892c7eb Upgrade to Emscripten 1.39.6 2020-02-02 19:32:09 +00:00
Aurélien Vivet
ce4fdbe4f8 Add option to search in event texts (comments, group names) (#1398) 2020-02-01 18:45:15 +00:00
Florian Rival
d82bbdc1af Create CNAME for hosting on editor.gdevelop-app.com
This file is required by GitHub when using a custom domain with GitHub pages.
Now required because of using Cloudflare directly instead of Cloudfront.
2020-02-01 14:01:33 +00:00
Florian Rival
62dca91637 Fix formatting 2020-02-01 13:00:51 +00:00
Florian Rival
1e1c5f7206 Fix drag'n'drop of selected event invalidating the event in memory (potential crash) 2020-02-01 12:33:50 +00:00
Florian Rival
7c9e6ee6f1 Prevent larger texts in landscape mode on Safari iOS 2020-01-30 21:29:55 +00:00
Florian Rival
5690de71c5 Remove useless define in GDevelop.js 2020-01-30 08:24:48 +00:00
Florian Rival
eae2a06a4e Improve export speed (~20%) by memoizing mangled names 2020-01-28 23:57:37 +00:00
Florian Rival
4affa25672 Fix compilation on recent compilers
Fix compilation with latest Emscripten
2020-01-28 22:49:54 +00:00
Florian Rival
7b30f63cb1 Remove unused variables 2020-01-27 23:15:27 +00:00
Arthur Pacaud
574bdaaf41 Refactor SpriteRuntimeObject types to add prefix "Sprite" (#1378)
Also remove useless parseFloat
2020-01-27 22:54:28 +00:00
Florian Rival
bff43ee771 Remove dead code related to old ExpressionParser 2020-01-27 22:08:48 +00:00
Aurélien Vivet
9b9b6c5996 Fix usage of getFontFamily in BBText instance renderer (#1399) 2020-01-27 20:51:07 +00:00
Florian Rival
721872e45b Fix race condition between extension loading and GDJS development watcher 2020-01-27 20:18:35 +00:00
Arthur Pacaud
edd9ec94cf Add @arthuro555 to credits (#1396) 2020-01-27 18:21:18 +00:00
Florian Rival
c986c52409 Add document explaining platforms and exporters 2020-01-26 20:43:39 +00:00
Aurélien Vivet
168b8d3fea Simplify the Loop checkbox in Sprite editor (#1397) 2020-01-26 17:20:47 +00:00
Florian Rival
47025329dd Remove remaining old functions to register objects/behaviors 2020-01-26 10:53:49 +00:00
Aurélien Vivet
9f05a65ed2 Add font selector for BBText object (#1394) 2020-01-25 19:44:13 +00:00
Florian Rival
370c6d03ad Update estimated build time for electron builds 2020-01-20 21:32:30 +00:00
Aurélien Vivet
b650ff3aa5 Improve credits (#1391) 2020-01-20 20:34:56 +00:00
Florian Rival
285ff6f5f5 Update the script to extract expressions reference 2020-01-19 15:29:59 +00:00
Florian Rival
81f292941f Add help button in ExpressionSelector to open expressions reference 2020-01-19 14:12:25 +00:00
Florian Rival
eb27ba7c86 Improve script to extract expressions reference documentation
Still need to add parameters
2020-01-19 12:27:51 +00:00
629 changed files with 57609 additions and 29656 deletions

View File

@@ -19,7 +19,7 @@ jobs:
- run:
name: Install Emscripten (for GDevelop.js)
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install sdk-fastcomp-1.37.37-64bit && ./emsdk activate sdk-fastcomp-1.37.37-64bit && cd ..
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
- run:
name: Install Wine for Electron builder

3
.gitignore vendored
View File

@@ -44,9 +44,6 @@
/Binaries/**/JsPlatform/*.dll.a
/Binaries/Output/Release_Windows/newIDE
*.autosave
/Binaries/Output/libGD.js/Release
/Binaries/Output/libGD.js/Debug
/Binaries/Output/libGD.js/libGD.raw.js
!/GDCpp/scripts/bcp.exe
!/scripts/libgettextlib-0-17.dll
!/scripts/libgettextsrc-0-17.dll

View File

@@ -24,7 +24,7 @@ addons:
- /$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi)/commit/$(git rev-parse HEAD)
- /$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi)/latest
paths:
- Binaries/Output/libGD.js/Release
- Binaries/embuild/GDevelop.js
apt:
sources:
- ubuntu-toolchain-r-test
@@ -63,8 +63,8 @@ install:
# Install Emscripten (for GDevelop.js)
- git clone https://github.com/juj/emsdk.git
- cd emsdk
- ./emsdk install sdk-fastcomp-1.37.37-64bit
- ./emsdk activate sdk-fastcomp-1.37.37-64bit
- ./emsdk install 1.39.6
- ./emsdk activate 1.39.6
- source ./emsdk_env.sh
- cd ..
# Install GDevelop.js dependencies and compile it

View File

@@ -83,7 +83,8 @@
"__threading_support": "cpp",
"any": "cpp",
"array": "cpp",
"cinttypes": "cpp"
"cinttypes": "cpp",
"numeric": "cpp"
},
"files.exclude": {
"Binaries/*build*": true,

View File

@@ -51,7 +51,12 @@ file(GLOB_RECURSE formatted_source_files tests/* GDCore/Events/* GDCore/Extensio
list(REMOVE_ITEM formatted_source_files "${CMAKE_CURRENT_SOURCE_DIR}/GDCore/IDE/Dialogs/GDCoreDialogs.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/GDCore/IDE/Dialogs/GDCoreDialogs.h" "${CMAKE_CURRENT_SOURCE_DIR}/GDCore/IDE/Dialogs/GDCoreDialogs_dialogs_bitmaps.cpp")
gd_add_clang_utils(GDCore "${formatted_source_files}")
add_library(GDCore SHARED ${source_files})
IF(EMSCRIPTEN)
# Emscripten treats all libraries as static libraries
add_library(GDCore STATIC ${source_files})
ELSE()
add_library(GDCore SHARED ${source_files})
ENDIF()
add_dependencies(GDCore GDVersion)
IF(EMSCRIPTEN)
set_target_properties(GDCore PROPERTIES SUFFIX ".bc")

View File

@@ -12,6 +12,15 @@ using namespace std;
namespace gd {
vector<gd::String> CommentEvent::GetAllSearchableStrings() const {
vector<gd::String> allSearchableStrings;
allSearchableStrings.push_back(com1);
allSearchableStrings.push_back(com2); ///< Com2 is deprecated
return allSearchableStrings;
}
void CommentEvent::SerializeTo(SerializerElement &element) const {
element.AddChild("color")
.SetAttribute("r", r)

View File

@@ -7,10 +7,11 @@
#define COMMENTEVENT_H
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
namespace gd {
class Layout;
class Project;
}
} // namespace gd
namespace gd {
@@ -45,6 +46,8 @@ class GD_CORE_API CommentEvent : public gd::BaseEvent {
const gd::String& GetComment() const { return com1; }
void SetComment(const gd::String& comment) { com1 = comment; }
virtual std::vector<gd::String> GetAllSearchableStrings() const;
virtual void SerializeTo(SerializerElement& element) const;
virtual void UnserializeFrom(gd::Project& project,
const SerializerElement& element);

View File

@@ -5,10 +5,6 @@
*/
#include "ForEachEvent.h"
#include <iostream>
#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerator.h"
#include "GDCore/Events/CodeGeneration/ExpressionsCodeGeneration.h"
#include "GDCore/Events/Serialization.h"
#include "GDCore/Events/Tools/EventsCodeNameMangler.h"
#include "GDCore/Serialization/SerializerElement.h"

View File

@@ -19,6 +19,14 @@ namespace gd {
GroupEvent::GroupEvent()
: BaseEvent(), creationTime(0), colorR(74), colorG(176), colorB(228) {}
vector<gd::String> GroupEvent::GetAllSearchableStrings() const {
vector<gd::String> allSearchableStrings;
allSearchableStrings.push_back(name);
return allSearchableStrings;
}
void GroupEvent::SerializeTo(SerializerElement& element) const {
element.SetAttribute("name", name);
element.SetAttribute("source", source);

View File

@@ -106,6 +106,8 @@ class GD_CORE_API GroupEvent : public gd::BaseEvent {
virtual const gd::EventsList& GetSubEvents() const { return events; };
virtual gd::EventsList& GetSubEvents() { return events; };
virtual std::vector<gd::String> GetAllSearchableStrings() const;
virtual void SerializeTo(SerializerElement& element) const;
virtual void UnserializeFrom(gd::Project& project,
const SerializerElement& element);

View File

@@ -5,9 +5,6 @@
*/
#include "RepeatEvent.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerator.h"
#include "GDCore/Events/CodeGeneration/ExpressionsCodeGeneration.h"
#include "GDCore/Events/Serialization.h"
#include "GDCore/Serialization/SerializerElement.h"

View File

@@ -6,9 +6,6 @@
#if defined(GD_IDE_ONLY)
#include "WhileEvent.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerator.h"
#include "GDCore/Events/CodeGeneration/ExpressionsCodeGeneration.h"
#include "GDCore/Events/Serialization.h"
#include "GDCore/Serialization/SerializerElement.h"

View File

@@ -4,8 +4,6 @@
#include "GDCore/CommonTools.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h"
#include "GDCore/Events/CodeGeneration/ExpressionCodeGenerator.h"
#include "GDCore/Events/CodeGeneration/ExpressionsCodeGeneration.h"
#include "GDCore/Events/Parsers/ExpressionParser.h"
#include "GDCore/Events/Tools/EventsCodeNameMangler.h"
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
@@ -662,7 +660,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
if (argOutput.empty()) {
if (!metadata.type.empty())
cout << "Warning: Unknown type of parameter \"" << metadata.type
<< "\".";
<< "\"." << std::endl;
argOutput += "\"" + ConvertToString(parameter) + "\"";
}
}

View File

@@ -35,10 +35,6 @@ namespace gd {
* \brief Internal class used to generate code from events
*/
class GD_CORE_API EventsCodeGenerator {
// Compatiblity with old ExpressionParser
friend class CallbacksForGeneratingExpressionCode;
friend class VariableCodeGenerationCallbacks;
// end of compatibility code
friend class ExpressionCodeGenerator;
public:

View File

@@ -24,117 +24,14 @@
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
// Compatibility with old ExpressionParser
#include "GDCore/Events/CodeGeneration/ExpressionsCodeGeneration.h"
#include "GDCore/Events/CodeGeneration/VariableParserCallbacks.h"
#include "GDCore/Events/Parsers/ExpressionParser.h"
#include "GDCore/Events/Parsers/VariableParser.h"
// end of compatibility code
namespace gd {
bool ExpressionCodeGenerator::useOldExpressionParser = false;
gd::String ExpressionCodeGenerator::GenerateExpressionCode(
EventsCodeGenerator& codeGenerator,
EventsCodeGenerationContext& context,
const gd::String& type,
const gd::String& expression,
const gd::String& objectName) {
// Compatibility with old ExpressionParser
if (useOldExpressionParser) {
if (type == "number") {
gd::String code = "";
gd::CallbacksForGeneratingExpressionCode callbacks(
code, codeGenerator, context);
gd::ExpressionParser parser(expression);
if (!parser.ParseMathExpression(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
callbacks) ||
code.empty()) {
std::cout << "Error (old ExpressionParser): \""
<< parser.GetFirstError() << "\" in: \"" << expression
<< "\" (number)" << std::endl;
code = "0";
}
return code;
} else if (type == "string") {
gd::String code = "";
gd::CallbacksForGeneratingExpressionCode callbacks(
code, codeGenerator, context);
gd::ExpressionParser parser(expression);
if (!parser.ParseStringExpression(
codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
callbacks) ||
code.empty()) {
std::cout << "Error (old ExpressionParser): \""
<< parser.GetFirstError() << "\" in: \"" << expression
<< "\" (string)" << std::endl;
code = "\"\"";
}
return code;
} else if (type == "scenevar") {
gd::String code = "";
gd::VariableCodeGenerationCallbacks callbacks(
code,
codeGenerator,
context,
gd::EventsCodeGenerator::LAYOUT_VARIABLE);
gd::VariableParser parser(expression);
if (!parser.Parse(callbacks)) {
std::cout << "Error (old VariableParser) :" << parser.GetFirstError()
<< " in: " << expression << std::endl;
code = codeGenerator.GenerateBadVariable();
}
return code;
} else if (type == "globalvar") {
gd::String code = "";
gd::VariableCodeGenerationCallbacks callbacks(
code,
codeGenerator,
context,
gd::EventsCodeGenerator::PROJECT_VARIABLE);
gd::VariableParser parser(expression);
if (!parser.Parse(callbacks)) {
std::cout << "Error (old VariableParser) :" << parser.GetFirstError()
<< " in: " << expression << std::endl;
code = codeGenerator.GenerateBadVariable();
}
return code;
} else if (type == "objectvar") {
gd::String code = "";
// Object is either the object of the previous parameter or, if it is
// empty, the object being picked by the instruction.
gd::String object =
objectName.empty() ? context.GetCurrentObject() : objectName;
gd::VariableCodeGenerationCallbacks callbacks(
code, codeGenerator, context, object);
gd::VariableParser parser(expression);
if (!parser.Parse(callbacks)) {
std::cout << "Error (old VariableParser) :" << parser.GetFirstError()
<< " in: " << expression << std::endl;
code = codeGenerator.GenerateBadVariable();
}
return code;
}
std::cout << "Type error (old ExpressionParser): type \"" << type
<< "\" is not supported" << std::endl;
return "/* Error during code generation: type " + type +
" is not supported for old ExpressionParser. */ 0";
}
// end of compatibility code
gd::ExpressionParser2 parser(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups());
@@ -230,7 +127,7 @@ void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
}
}
void ExpressionCodeGenerator::OnVisitFunctionNode(FunctionNode& node) {
void ExpressionCodeGenerator::OnVisitFunctionCallNode(FunctionCallNode& node) {
if (gd::MetadataProvider::IsBadExpressionMetadata(node.expressionMetadata)) {
output += "/* Error during generation, function not found: " +
codeGenerator.ConvertToString(node.functionName) + " for type " +
@@ -462,4 +359,8 @@ void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) {
output += GenerateDefaultValue(node.type);
}
void ExpressionCodeGenerator::OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) {
output += GenerateDefaultValue(node.type);
}
} // namespace gd

View File

@@ -60,11 +60,6 @@ class GD_CORE_API ExpressionCodeGenerator : public ExpressionParser2NodeWorker {
const gd::String& expression,
const gd::String& objectName = "");
static void UseOldExpressionParser(bool enable) {
useOldExpressionParser = enable;
};
static bool IsUsingOldExpressionParser() { return useOldExpressionParser; };
const gd::String& GetOutput() { return output; };
protected:
@@ -78,7 +73,8 @@ class GD_CORE_API ExpressionCodeGenerator : public ExpressionParser2NodeWorker {
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override;
void OnVisitIdentifierNode(IdentifierNode& node) override;
void OnVisitFunctionNode(FunctionNode& node) override;
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override;
void OnVisitFunctionCallNode(FunctionCallNode& node) override;
void OnVisitEmptyNode(EmptyNode& node) override;
private:
@@ -107,11 +103,9 @@ class GD_CORE_API ExpressionCodeGenerator : public ExpressionParser2NodeWorker {
gd::String output;
EventsCodeGenerator& codeGenerator;
EventsCodeGenerationContext& context;
static bool useOldExpressionParser;
};
} // namespace gd
#endif // GDCORE_ExpressionCodeGenerator_H
#endif
#endif

View File

@@ -1,229 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "ExpressionsCodeGeneration.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerator.h"
#include "GDCore/Events/Parsers/ExpressionParser.h"
#include "GDCore/Events/Tools/EventsCodeNameMangler.h"
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
using namespace std;
namespace gd {
CallbacksForGeneratingExpressionCode::CallbacksForGeneratingExpressionCode(
gd::String& plainExpression_,
EventsCodeGenerator& codeGenerator_,
EventsCodeGenerationContext& context_)
: plainExpression(plainExpression_),
codeGenerator(codeGenerator_),
context(context_) {}
void CallbacksForGeneratingExpressionCode::OnConstantToken(gd::String text) {
plainExpression += text;
};
void CallbacksForGeneratingExpressionCode::OnStaticFunction(
gd::String functionName,
const std::vector<gd::Expression>& parameters,
const gd::ExpressionMetadata& expressionInfo) {
codeGenerator.AddIncludeFiles(
expressionInfo.codeExtraInformation.GetIncludeFiles());
// Launch custom code generator if needed
if (expressionInfo.codeExtraInformation.HasCustomCodeGenerator()) {
plainExpression += expressionInfo.codeExtraInformation.customCodeGenerator(
parameters, codeGenerator, context);
return;
}
// Special case: For strings expressions, function without name is a string.
if (GetReturnType() == "string" && functionName.empty()) {
if (parameters.empty()) return;
plainExpression +=
codeGenerator.ConvertToStringExplicit(parameters[0].GetPlainString());
return;
}
// Prepare parameters
std::vector<gd::String> parametersCode =
codeGenerator.GenerateParametersCodes(
parameters, expressionInfo.parameters, context);
gd::String parametersStr;
for (std::size_t i = 0; i < parametersCode.size(); ++i) {
if (i != 0) parametersStr += ", ";
parametersStr += parametersCode[i];
}
plainExpression += expressionInfo.codeExtraInformation.functionCallName +
"(" + parametersStr + ")";
};
void CallbacksForGeneratingExpressionCode::OnObjectFunction(
gd::String functionName,
const std::vector<gd::Expression>& parameters,
const gd::ExpressionMetadata& expressionInfo) {
const gd::ObjectsContainer& globalObjectsAndGroups = codeGenerator.GetGlobalObjectsAndGroups();
const gd::ObjectsContainer& objectsAndGroups = codeGenerator.GetObjectsAndGroups();
codeGenerator.AddIncludeFiles(
expressionInfo.codeExtraInformation.GetIncludeFiles());
if (parameters.empty()) return;
// Launch custom code generator if needed
if (expressionInfo.codeExtraInformation.HasCustomCodeGenerator()) {
plainExpression += expressionInfo.codeExtraInformation.customCodeGenerator(
parameters, codeGenerator, context);
return;
}
// Prepare parameters
std::vector<gd::String> parametersCode =
codeGenerator.GenerateParametersCodes(
parameters, expressionInfo.parameters, context);
gd::String parametersStr;
for (std::size_t i = 1; i < parametersCode.size(); ++i) {
if (i != 1) parametersStr += ", ";
parametersStr += parametersCode[i];
}
gd::String output = GetReturnType() == "string" ? "\"\"" : "0";
// Get object(s) concerned by function call
std::vector<gd::String> realObjects =
codeGenerator.ExpandObjectsName(parameters[0].GetPlainString(), context);
for (std::size_t i = 0; i < realObjects.size(); ++i) {
context.ObjectsListNeeded(realObjects[i]);
gd::String objectType = gd::GetTypeOfObject(globalObjectsAndGroups, objectsAndGroups, realObjects[i]);
const ObjectMetadata& objInfo = MetadataProvider::GetObjectMetadata(
codeGenerator.GetPlatform(), objectType);
// Build gd::String to access the object
codeGenerator.AddIncludeFiles(objInfo.includeFiles);
output = codeGenerator.GenerateObjectFunctionCall(
realObjects[i],
objInfo,
expressionInfo.codeExtraInformation,
parametersStr,
output,
context);
}
plainExpression += output;
};
void CallbacksForGeneratingExpressionCode::OnObjectBehaviorFunction(
gd::String functionName,
const std::vector<gd::Expression>& parameters,
const gd::ExpressionMetadata& expressionInfo) {
const gd::ObjectsContainer& globalObjectsAndGroups = codeGenerator.GetGlobalObjectsAndGroups();
const gd::ObjectsContainer& objectsAndGroups = codeGenerator.GetObjectsAndGroups();
codeGenerator.AddIncludeFiles(
expressionInfo.codeExtraInformation.GetIncludeFiles());
if (parameters.size() < 2) return;
// Launch custom code generator if needed
if (expressionInfo.codeExtraInformation.HasCustomCodeGenerator()) {
plainExpression += expressionInfo.codeExtraInformation.customCodeGenerator(
parameters, codeGenerator, context);
return;
}
// Prepare parameters
std::vector<gd::String> parametersCode =
codeGenerator.GenerateParametersCodes(
parameters, expressionInfo.parameters, context);
gd::String parametersStr;
for (std::size_t i = 2; i < parametersCode.size(); ++i) {
if (i != 2) parametersStr += ", ";
parametersStr += parametersCode[i];
}
// Get object(s) concerned by function call
std::vector<gd::String> realObjects =
codeGenerator.ExpandObjectsName(parameters[0].GetPlainString(), context);
gd::String output = GetReturnType() == "string" ? "\"\"" : "0";
for (std::size_t i = 0; i < realObjects.size(); ++i) {
context.ObjectsListNeeded(realObjects[i]);
// Cast the object if needed
gd::String behaviorType =
gd::GetTypeOfBehavior(globalObjectsAndGroups, objectsAndGroups, parameters[1].GetPlainString());
const BehaviorMetadata& autoInfo = MetadataProvider::GetBehaviorMetadata(
codeGenerator.GetPlatform(), behaviorType);
// Build gd::String to access the behavior
codeGenerator.AddIncludeFiles(autoInfo.includeFiles);
output = codeGenerator.GenerateObjectBehaviorFunctionCall(
realObjects[i],
parameters[1].GetPlainString(),
autoInfo,
expressionInfo.codeExtraInformation,
parametersStr,
output,
context);
}
plainExpression += output;
};
bool CallbacksForGeneratingExpressionCode::OnSubMathExpression(
const gd::Platform& platform,
const gd::ObjectsContainer& globalObjectsAndGroups,
const gd::ObjectsContainer& objectsAndGroups,
gd::Expression& expression) {
gd::String newExpression;
CallbacksForGeneratingExpressionCode callbacks(
newExpression, codeGenerator, context);
gd::ExpressionParser parser(expression.GetPlainString());
if (!parser.ParseMathExpression(platform, globalObjectsAndGroups, objectsAndGroups, callbacks)) {
#if defined(GD_IDE_ONLY)
firstErrorStr = callbacks.GetFirstError();
firstErrorPos = callbacks.GetFirstErrorPosition();
#endif
return false;
}
return true;
}
bool CallbacksForGeneratingExpressionCode::OnSubTextExpression(
const gd::Platform& platform,
const gd::ObjectsContainer& globalObjectsAndGroups,
const gd::ObjectsContainer& objectsAndGroups,
gd::Expression& expression) {
gd::String newExpression;
CallbacksForGeneratingExpressionCode callbacks(
newExpression, codeGenerator, context);
gd::ExpressionParser parser(expression.GetPlainString());
if (!parser.ParseStringExpression(platform, globalObjectsAndGroups, objectsAndGroups, callbacks)) {
#if defined(GD_IDE_ONLY)
firstErrorStr = callbacks.GetFirstError();
firstErrorPos = callbacks.GetFirstErrorPosition();
#endif
return false;
}
return true;
}
} // namespace gd

View File

@@ -1,77 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef EXPRESSIONSCODEGENERATION_H
#define EXPRESSIONSCODEGENERATION_H
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser.h"
#include "GDCore/String.h"
namespace gd {
class ExpressionMetadata;
class Expression;
class Project;
class Layout;
class Layout;
class EventsCodeGenerationContext;
class EventsCodeGenerator;
}
namespace gd {
// TODO: Replace and remove (ExpressionCodeGenerator)
/**
* \brief Used to generate code from expressions.
*
* Usage example :
* \code
* gd::String expressionOutputCppCode;
*
* CallbacksForGeneratingExpressionCode callbacks(expressionOutputCppCode,
* codeGenerator, context); gd::ExpressionParser
* parser(theOriginalGameDevelopExpression);
* parser.ParseStringExpression(platform, project, scene, callbacks);
*
* if (expressionOutputCppCode.empty()) expressionOutputCppCode = "\"\""; //If
* generation failed, we make sure output code is not empty. \endcode \see
* EventsCodeGenerator
*/
class GD_CORE_API CallbacksForGeneratingExpressionCode
: public gd::ParserCallbacks {
public:
CallbacksForGeneratingExpressionCode(gd::String& output,
EventsCodeGenerator& codeGenerator_,
EventsCodeGenerationContext& context_);
virtual ~CallbacksForGeneratingExpressionCode(){};
void OnConstantToken(gd::String text);
void OnStaticFunction(gd::String functionName,
const std::vector<gd::Expression>& parameters,
const gd::ExpressionMetadata& expressionInfo);
void OnObjectFunction(gd::String functionName,
const std::vector<gd::Expression>& parameters,
const gd::ExpressionMetadata& expressionInfo);
void OnObjectBehaviorFunction(gd::String functionName,
const std::vector<gd::Expression>& parameters,
const gd::ExpressionMetadata& expressionInfo);
bool OnSubMathExpression(const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::Expression& expression);
bool OnSubTextExpression(const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::Expression& expression);
private:
gd::String& plainExpression;
EventsCodeGenerator& codeGenerator;
EventsCodeGenerationContext& context;
};
} // namespace gd
#endif // EXPRESSIONSCODEGENERATION_H

View File

@@ -1,68 +0,0 @@
/*
* GDevelop C++ Platform
* 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)
#include "VariableParserCallbacks.h"
#include <string>
#include <vector>
#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerator.h"
#include "GDCore/Events/CodeGeneration/ExpressionCodeGenerator.h"
#include "GDCore/Events/Parsers/VariableParser.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/Object.h"
#include "GDCore/Project/Project.h"
using namespace std;
namespace gd {
VariableCodeGenerationCallbacks::VariableCodeGenerationCallbacks(
gd::String& output_,
gd::EventsCodeGenerator& codeGenerator_,
gd::EventsCodeGenerationContext& context_,
const gd::EventsCodeGenerator::VariableScope& scope_)
: output(output_),
codeGenerator(codeGenerator_),
context(context_),
scope(scope_) {
if (scope == gd::EventsCodeGenerator::OBJECT_VARIABLE) {
std::cout << "ERROR: Initializing VariableCodeGenerationCallbacks with "
"OBJECT_VARIABLE without object.";
}
}
VariableCodeGenerationCallbacks::VariableCodeGenerationCallbacks(
gd::String& output_,
gd::EventsCodeGenerator& codeGenerator_,
gd::EventsCodeGenerationContext& context_,
const gd::String& object_)
: output(output_),
codeGenerator(codeGenerator_),
context(context_),
scope(gd::EventsCodeGenerator::OBJECT_VARIABLE),
object(object_) {}
void VariableCodeGenerationCallbacks::OnRootVariable(gd::String variableName) {
output += codeGenerator.GenerateGetVariable(variableName, scope, context, object);
}
void VariableCodeGenerationCallbacks::OnChildVariable(gd::String variableName) {
output += codeGenerator.GenerateVariableAccessor(variableName);
}
void VariableCodeGenerationCallbacks::OnChildSubscript(
gd::String stringExpression) {
gd::String argumentCode = gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "string", stringExpression);
output += codeGenerator.GenerateVariableBracketAccessor(argumentCode);
}
}
#endif

View File

@@ -1,97 +0,0 @@
/*
* GDevelop C++ Platform
* 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 VARIABLEPARSERCALLBACKS_H
#define VARIABLEPARSERCALLBACKS_H
#include <string>
#include <vector>
#include "GDCore/Events/Parsers/VariableParser.h"
#include "GDCore/String.h"
#include "EventsCodeGenerator.h"
namespace gd {
class EventsCodeGenerationContext;
} // namespace gd
// TODO: Replace and remove (ExpressionCodeGenerator)
namespace gd {
/**
* \brief Callbacks called to generate the code for getting a variable.
*
* Usage example:
\code
VariableCodeGenerationCallbacks callbacks(output, eventsCodeGenerator,
context, VariableCodeGenerationCallbacks::LAYOUT_VARIABLE);
gd::VariableParser parser(parameter);
if ( !parser.Parse(callbacks) )
{
//Error during parsing the variable name:
output = "runtimeContext->GetSceneVariables().GetBadVariable()";
}
//"output" now contains the C++ code to return the variable.
\endcode
*/
class VariableCodeGenerationCallbacks : public gd::VariableParserCallbacks {
public:
/**
* \brief Default constructor for generating code for a layout/global
* variable. \param output The string in which the code will be generated.
* \param codeGenerator The code generator being used.
* \param context The current code generation context.
* \param scope The scope of the variable being accessed: LAYOUT_VARIABLE,
* PROJECT_VARIABLE.
*/
VariableCodeGenerationCallbacks(gd::String& output,
gd::EventsCodeGenerator& codeGenerator_,
gd::EventsCodeGenerationContext& context_,
const gd::EventsCodeGenerator::VariableScope& scope_);
/**
* \brief Default constructor for generating code for an object variable.
* \param output The string in which the code will be generated.
* \param codeGenerator The code generator being used.
* \param context The current code generation context.
* \param object The name of the object
*/
VariableCodeGenerationCallbacks(gd::String& output,
gd::EventsCodeGenerator& codeGenerator_,
gd::EventsCodeGenerationContext& context_,
const gd::String& object);
/**
* \brief Called when the first variable has been parsed.
* \param variableName The variable name.
*/
virtual void OnRootVariable(gd::String variableName);
/**
* \brief Called when accessing the child of a structure variable.
* \param variableName The child variable name.
*/
virtual void OnChildVariable(gd::String variableName);
/**
* \brief Called when accessing the child of a structure variable using a
* string expression in square brackets. \param variableName The expression
* used to access the child variable.
*/
virtual void OnChildSubscript(gd::String stringExpression);
private:
gd::String& output;
gd::EventsCodeGenerator& codeGenerator;
gd::EventsCodeGenerationContext& context;
gd::EventsCodeGenerator::VariableScope scope;
const gd::String object; ///< The object name, when scope == OBJECT_VARIABLE.
};
}
#endif // VARIABLEPARSERCALLBACKS_H
#endif

View File

@@ -90,8 +90,8 @@ class GD_CORE_API BaseEvent {
bool HasSubEvents() const;
/**
* Event must be able to return all conditions std::vector they have.
* Used to preprocess the conditions.
* \brief Return a list of all conditions of the event.
* \note Used to preprocess or search in the conditions.
*/
virtual std::vector<gd::InstructionsList*> GetAllConditionsVectors() {
std::vector<gd::InstructionsList*> noConditions;
@@ -104,8 +104,8 @@ class GD_CORE_API BaseEvent {
};
/**
* Event must be able to return all actions std::vector they have.
* Used to preprocess the actions.
* \brief Return a list of all actions of the event.
* \note Used to preprocess or search in the actions.
*/
virtual std::vector<gd::InstructionsList*> GetAllActionsVectors() {
std::vector<gd::InstructionsList*> noActions;
@@ -118,8 +118,17 @@ class GD_CORE_API BaseEvent {
};
/**
* Event must be able to return all expressions they have.
* Used to preprocess the expressions.
* \brief Return a list of all strings of the event.
* \note Used to preprocess or search in the event strings.
*/
virtual std::vector<gd::String> GetAllSearchableStrings() const {
std::vector<gd::String> noSearchableStrings;
return noSearchableStrings;
};
/**
* \brief Return a list of all expressions of the event.
* \note Used to preprocess or search in the expressions.
*/
virtual std::vector<gd::Expression*> GetAllExpressions() {
std::vector<gd::Expression*> noExpr;

View File

@@ -99,6 +99,23 @@ bool EventsList::Contains(const gd::BaseEvent& eventToSearch,
return false;
}
bool EventsList::MoveEventToAnotherEventsList(const gd::BaseEvent& eventToMove,
gd::EventsList& newEventsList,
std::size_t newPosition) {
for (std::size_t i = 0; i < GetEventsCount(); ++i) {
if (events[i].get() == &eventToMove) {
std::shared_ptr<BaseEvent> event = events[i];
events.erase(events.begin() + i);
newEventsList.InsertEvent(event, newPosition);
return true;
}
}
return false;
}
EventsList::EventsList(const EventsList& other) { Init(other); }
EventsList& EventsList::operator=(const EventsList& other) {

View File

@@ -52,7 +52,8 @@ class GD_CORE_API EventsList {
* \brief Insert the specified event to the list.
* \note The event passed by parameter is not copied.
* \param event The smart pointer to the event that must be inserted into the
* list \param position Insertion position. If the position is invalid, the
* list
* \param position Insertion position. If the position is invalid, the
* object is inserted at the end of the objects list.
*/
void InsertEvent(std::shared_ptr<gd::BaseEvent> event,
@@ -142,6 +143,25 @@ class GD_CORE_API EventsList {
*/
bool Contains(const gd::BaseEvent& eventToSearch,
bool recursive = true) const;
/**
* Move the specified event, that must be in the events list, to another
* events list *without* invalidating the event (i.e: without
* destroying/cloning it) in memory.
*
* \warning newEventsList is supposed not to be contained inside the event
* (you should not try
* to move an event inside one of its children/grand children events).
*
* \param eventToMove The event to be moved
* \param newEventsList The new events list
* \param newPosition The position in the new events list
* \return true if the move was made, false otherwise (for example, if
* eventToMove is not found in the list)
*/
bool MoveEventToAnotherEventsList(const gd::BaseEvent& eventToMove,
gd::EventsList& newEventsList,
std::size_t newPosition);
///@}
/** \name std::vector API compatibility

View File

@@ -1,897 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Events/Parsers/ExpressionParser.h"
#include <algorithm>
#include <iostream>
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Expression.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Tools/Localization.h"
using namespace std;
namespace gd {
gd::String ExpressionParser::parserSeparators = " ,+-*/%.<>=&|;()#^![]{}";
size_t ExpressionParser::GetMinimalParametersNumber(
const std::vector<gd::ParameterMetadata>& parametersInfos) {
size_t nb = 0;
for (std::size_t i = 0; i < parametersInfos.size(); ++i) {
if (!parametersInfos[i].optional && !parametersInfos[i].codeOnly) nb++;
}
return nb;
}
size_t ExpressionParser::GetMaximalParametersNumber(
const std::vector<gd::ParameterMetadata>& parametersInfos) {
size_t nb = 0;
for (std::size_t i = 0; i < parametersInfos.size(); ++i) {
if (!parametersInfos[i].codeOnly) nb++;
}
return nb;
}
/**
* Add blank parameters when code-only parameters are expected.
* \param Parameters information
* \param vector of parameters without code only parameters.
*/
std::vector<gd::Expression> CompleteParameters(
const std::vector<gd::ParameterMetadata>& parametersInfo,
const std::vector<gd::Expression>& parameters) {
std::vector<gd::Expression> completeParameters = parameters;
for (std::size_t i = 0; i < parametersInfo.size();
++i) // Code only parameters are not included in expressions parameters.
{
if (parametersInfo[i].codeOnly) {
if (i > completeParameters.size()) {
cout << "Bad parameter count in expression.";
}
if (i >= completeParameters.size())
completeParameters.push_back(gd::Expression(""));
else
completeParameters.insert(completeParameters.begin() + i,
gd::Expression(""));
} else {
if (i >= completeParameters.size()) {
completeParameters.push_back(gd::Expression(""));
}
}
}
return completeParameters;
}
bool ExpressionParser::ValidSyntax(const gd::String& str) {
static const gd::String numerics = "0123456789.e";
static const gd::String operators = "+/*-%";
size_t parenthesisLevel = 0;
gd::String lastOperator;
bool parsingNumber = false;
bool parsingScientificNotationNumber = false;
bool parsingDecimalNumber = false;
bool requestNumber = false;
gd::String lastNumber;
bool numberWasParsedLast = false;
for (auto it = str.begin(); it != str.end(); ++it) {
char32_t currentChar = *it;
if (currentChar == U' ' || currentChar == U'\n') {
if (requestNumber) {
firstErrorStr = _("Number expected");
return false;
}
if (parsingNumber) {
parsingNumber = false;
parsingScientificNotationNumber = false;
parsingDecimalNumber = false;
requestNumber = false;
lastNumber.clear();
numberWasParsedLast = true;
}
} else if (numerics.find(currentChar) != gd::String::npos) {
requestNumber = false;
if (currentChar == U'.') {
if (!parsingNumber) {
firstErrorStr = _("Syntax error");
return false;
}
if (parsingDecimalNumber) {
firstErrorStr = _("Syntax error in a number.");
return false;
}
parsingDecimalNumber = true;
}
if (currentChar == U'e') {
if (parsingScientificNotationNumber) {
firstErrorStr = _("Syntax error in a number.");
return false;
}
parsingScientificNotationNumber = true;
requestNumber = true;
}
if (numberWasParsedLast) {
firstErrorStr = _("Operator missing before a number");
return false;
}
parsingNumber = true;
lastNumber += currentChar;
} else if (currentChar == U')') {
if (requestNumber) {
firstErrorStr = _("Number expected");
return false;
}
if (parsingNumber) {
parsingNumber = false;
parsingScientificNotationNumber = false;
parsingDecimalNumber = false;
lastNumber.clear();
numberWasParsedLast = true;
}
if (!numberWasParsedLast) {
firstErrorStr = _("Superfluous operator before a paranthesis");
return false;
}
if (parenthesisLevel > 0)
parenthesisLevel--;
else {
firstErrorStr = _("Bad closing paranthesis");
return false;
}
auto previousIt = it;
--previousIt;
if (*previousIt == U'(') {
firstErrorStr = _("Empty paranthesis");
return false;
}
} else if (currentChar == U'(') {
if (requestNumber) {
firstErrorStr = _("Number expected");
return false;
}
if (parsingNumber) {
parsingNumber = false;
parsingScientificNotationNumber = false;
parsingDecimalNumber = false;
lastNumber.clear();
numberWasParsedLast = true;
}
if (numberWasParsedLast) {
firstErrorStr = _("Operator missing before a paranthesis");
return false;
}
parenthesisLevel++;
numberWasParsedLast = false;
} else if (operators.find(currentChar) != gd::String::npos) {
if (currentChar == U'-' && parsingNumber &&
parsingScientificNotationNumber) {
lastNumber += currentChar;
requestNumber = true;
} else {
if (requestNumber) {
firstErrorStr = _("Number expected");
return false;
}
if (parsingNumber) {
parsingNumber = false;
parsingScientificNotationNumber = false;
parsingDecimalNumber = false;
lastNumber.clear();
numberWasParsedLast = true;
}
if (currentChar != U'-' && currentChar != U'+' &&
!numberWasParsedLast) {
firstErrorStr = _("Operators without any number between them");
return false;
}
numberWasParsedLast = false;
}
} else {
firstErrorStr = _("Syntax error");
return false;
}
}
if (parsingNumber) {
parsingNumber = false;
parsingScientificNotationNumber = false;
parsingDecimalNumber = false;
lastNumber.clear();
numberWasParsedLast = true;
} else if (requestNumber) {
firstErrorStr = _("Number expected");
return false;
}
if (parenthesisLevel != 0) {
firstErrorStr = _("Paranthesis mismatch");
return false;
}
if (!numberWasParsedLast) {
firstErrorStr = _("Alone operator at the end of the expression");
return false;
}
return true;
}
bool ExpressionParser::ParseMathExpression(const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::ParserCallbacks& callbacks) {
callbacks.SetReturnType("expression");
gd::String expression = expressionPlainString;
size_t parsePosition = 0;
size_t firstPointPos = expression.find(".");
size_t firstParPos = expression.find("(");
gd::String expressionWithoutFunctions;
gd::String nonFunctionToken;
size_t nonFunctionTokenStartPos = gd::String::npos;
while (firstPointPos != string::npos || firstParPos != string::npos) {
// Identify name
size_t nameEnd = firstPointPos < firstParPos ? firstPointPos : firstParPos;
size_t nameStart = expression.find_last_of(parserSeparators, nameEnd - 1);
nameStart++;
gd::String nameBefore = expression.substr(nameStart, nameEnd - nameStart);
gd::String objectName = nameBefore.FindAndReplace("~", " ");
// Identify function name
gd::String functionName = nameBefore;
size_t functionNameEnd = nameEnd;
vector<gd::Expression> parameters;
bool nameIsFunction = firstPointPos > firstParPos;
if (!nameIsFunction) {
parameters.push_back(gd::Expression(objectName));
functionNameEnd = expression.find_first_of(" (", nameEnd);
if (nameEnd + 1 < expression.length())
functionName =
expression.substr(nameEnd + 1, functionNameEnd - (nameEnd + 1));
if (functionNameEnd == string::npos) {
functionName = "";
functionNameEnd = expression.length() - 1;
}
}
// Now we're going to identify the expression
gd::ExpressionMetadata instructionInfos;
if (functionName.substr(0, functionName.length() - 1)
.find_first_of(parserSeparators) == string::npos) {
bool functionFound = false;
bool staticFunctionFound = false;
bool objectFunctionFound = false;
bool behaviorFunctionFound = false;
// First try to bind to a static expression
if (nameIsFunction &&
MetadataProvider::HasExpression(platform, functionName)) {
functionFound = true;
staticFunctionFound = true;
instructionInfos =
MetadataProvider::GetExpressionMetadata(platform, functionName);
}
// Then search in object expression
else if (!nameIsFunction &&
MetadataProvider::HasObjectExpression(
platform,
gd::GetTypeOfObject(project, layout, objectName),
functionName)) {
functionFound = true;
objectFunctionFound = true;
instructionInfos = MetadataProvider::GetObjectExpressionMetadata(
platform,
gd::GetTypeOfObject(project, layout, objectName),
functionName);
}
// And in behaviors expressions
else if (!nameIsFunction) {
size_t firstDoublePoints = functionName.find("::");
if (firstDoublePoints != string::npos) {
gd::String autoName = functionName.substr(0, firstDoublePoints);
if (firstDoublePoints + 2 < functionName.length())
functionName = functionName.substr(firstDoublePoints + 2,
functionName.length());
else
functionName = "";
if (MetadataProvider::HasBehaviorExpression(
platform,
gd::GetTypeOfBehavior(project, layout, autoName),
functionName)) {
parameters.push_back(gd::Expression(autoName));
functionFound = true;
behaviorFunctionFound = true;
instructionInfos = MetadataProvider::GetBehaviorExpressionMetadata(
platform,
gd::GetTypeOfBehavior(project, layout, autoName),
functionName);
// Verify that object has behavior.
vector<gd::String> behaviors =
gd::GetBehaviorsOfObject(project, layout, objectName);
if (find(behaviors.begin(), behaviors.end(), autoName) ==
behaviors.end()) {
cout << "Bad behavior requested" << endl;
functionFound = false;
}
}
}
}
if (functionFound) // Add the function
{
// Identify parameters
size_t parametersEnd = expression.find_first_of("(", functionNameEnd);
gd::String currentParameterStr;
char32_t previousChar = '(';
bool takeSymbolsInAccount = true;
if (parametersEnd != string::npos) {
size_t level = 0;
parametersEnd++;
while (parametersEnd < expression.length() &&
!(expression[parametersEnd] == ')' && level == 0)) {
// Be sure we are not in quotes
if (expression[parametersEnd] == U'\"' && previousChar != U'\\')
takeSymbolsInAccount = !takeSymbolsInAccount;
// So as to be sure paranthesis don't belong to a parameter
if (expression[parametersEnd] == U'(' && takeSymbolsInAccount)
level++;
if (expression[parametersEnd] == U')' && takeSymbolsInAccount)
level--;
// Add the character to the current parameter or terminate the
// latter
if ((expression[parametersEnd] == U',' && level == 0) &&
takeSymbolsInAccount) {
parameters.push_back(currentParameterStr);
currentParameterStr.clear();
} else
currentParameterStr += expression[parametersEnd];
previousChar = expression[parametersEnd];
parametersEnd++;
}
if (currentParameterStr.find_first_not_of(" ") !=
string::npos) // Add last parameter if needed
{
parameters.push_back(currentParameterStr);
}
// Testing function call is properly closed
if (parametersEnd == expression.length() ||
expression[parametersEnd] != U')') {
firstErrorStr = _("Paranthesis not closed");
firstErrorPos = parametersEnd - 1;
return false;
}
// Testing the number of parameters
if (parameters.size() >
GetMaximalParametersNumber(instructionInfos.parameters) ||
parameters.size() <
GetMinimalParametersNumber(instructionInfos.parameters)) {
firstErrorPos = functionNameEnd;
firstErrorStr = _("Incorrect number of parameters");
firstErrorStr += " ";
firstErrorStr += _("Expected (maximum) :");
firstErrorStr += gd::String::From(
GetMaximalParametersNumber(instructionInfos.parameters));
return false;
}
// Preparing parameters
parameters =
CompleteParameters(instructionInfos.parameters, parameters);
for (std::size_t i = 0; i < instructionInfos.parameters.size(); ++i) {
if (!PrepareParameter(platform,
project,
layout,
callbacks,
parameters[i],
instructionInfos.parameters[i],
functionNameEnd))
return false;
}
} else {
firstErrorPos = functionNameEnd;
firstErrorStr = _("Parameters' parenthesis missing");
return false;
}
callbacks.OnConstantToken(
nonFunctionToken +
expression.substr(parsePosition, nameStart - parsePosition));
expressionWithoutFunctions +=
expression.substr(parsePosition, nameStart - parsePosition);
nonFunctionToken.clear();
nonFunctionTokenStartPos = gd::String::npos;
if (objectFunctionFound)
callbacks.OnObjectFunction(
functionName, parameters, instructionInfos);
else if (behaviorFunctionFound)
callbacks.OnObjectBehaviorFunction(
functionName, parameters, instructionInfos);
else if (staticFunctionFound)
callbacks.OnStaticFunction(
functionName, parameters, instructionInfos);
if (objectFunctionFound || behaviorFunctionFound || staticFunctionFound)
expressionWithoutFunctions += "0";
parsePosition = parametersEnd + 1;
firstPointPos = expression.find(".", parametersEnd + 1);
firstParPos = expression.find("(", parametersEnd + 1);
} else // Math function or math constant : Pass it.
{
nonFunctionToken += expression.substr(
parsePosition, functionNameEnd + 1 - parsePosition);
expressionWithoutFunctions += expression.substr(
parsePosition, functionNameEnd + 1 - parsePosition);
nonFunctionTokenStartPos = (nonFunctionTokenStartPos != gd::String::npos
? nonFunctionTokenStartPos
: parsePosition);
parsePosition = functionNameEnd + 1;
firstPointPos = expression.find(".", functionNameEnd + 1);
firstParPos = expression.find("(", functionNameEnd + 1);
}
} else // Not a function call : Pass it
{
nonFunctionToken +=
expression.substr(parsePosition, nameEnd + 1 - parsePosition);
expressionWithoutFunctions +=
expression.substr(parsePosition, nameEnd + 1 - parsePosition);
nonFunctionTokenStartPos = (nonFunctionTokenStartPos != gd::String::npos
? nonFunctionTokenStartPos
: parsePosition);
parsePosition = nameEnd + 1;
firstPointPos = expression.find(".", nameEnd + 1);
firstParPos = expression.find("(", nameEnd + 1);
}
}
if (parsePosition < expression.length() || !nonFunctionToken.empty())
callbacks.OnConstantToken(
nonFunctionToken +
expression.substr(parsePosition, expression.length()));
expressionWithoutFunctions +=
expression.substr(parsePosition, expression.length());
return ValidSyntax(expressionWithoutFunctions);
}
bool ExpressionParser::ParseStringExpression(const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::ParserCallbacks& callbacks) {
callbacks.SetReturnType("string");
gd::String expression = expressionPlainString;
size_t parsePosition = 0;
// Searching for first token.
size_t firstPointPos = expression.find(".");
size_t firstParPos = expression.find("(");
size_t firstQuotePos = expression.find("\"");
if (firstPointPos == string::npos && firstParPos == string::npos &&
firstQuotePos == string::npos) {
firstErrorPos = 0;
firstErrorStr =
_("The expression is invalid or empty. Enter a text ( surrounded by "
"quotes ) or a function.");
return false;
}
while (firstPointPos != string::npos || firstParPos != string::npos ||
firstQuotePos != string::npos) {
if (firstQuotePos < firstPointPos &&
firstQuotePos < firstParPos) // Adding a constant text
{
callbacks.OnConstantToken(
expression.substr(parsePosition, firstQuotePos - parsePosition));
// Finding start and end of quotes
size_t finalQuotePosition = expression.find("\"", firstQuotePos + 1);
while (finalQuotePosition ==
expression.find("\\\"", finalQuotePosition - 1) + 1)
finalQuotePosition = expression.find("\"", finalQuotePosition + 1);
if (finalQuotePosition == string::npos) {
firstErrorPos = firstQuotePos;
firstErrorStr = _("Quotes not closed.");
return false;
}
// Generating final text, by replacing \" by quotes
gd::String finalText = expression.substr(
firstQuotePos + 1, finalQuotePosition - (firstQuotePos + 1));
size_t foundPos = finalText.find("\\\"");
while (foundPos != string::npos) {
if (foundPos != string::npos) finalText.replace(foundPos, 2, "\"");
foundPos = finalText.find("\\\"", foundPos);
}
// Adding constant text instruction
//(Function without name is considered as a constant text)
vector<gd::Expression> parameters;
parameters.push_back(finalText);
gd::ExpressionMetadata noParametersInfo;
callbacks.OnStaticFunction("", parameters, noParametersInfo);
parsePosition = finalQuotePosition + 1;
} else // Adding a function
{
// Identify name
size_t nameEnd =
firstPointPos < firstParPos ? firstPointPos : firstParPos;
size_t nameStart = expression.find_last_of(parserSeparators, nameEnd - 1);
nameStart++;
callbacks.OnConstantToken(
expression.substr(parsePosition, nameStart - parsePosition));
gd::String nameBefore = expression.substr(nameStart, nameEnd - nameStart);
gd::String objectName = nameBefore.FindAndReplace("~", " ");
// Identify function name
gd::String functionName = nameBefore;
size_t functionNameEnd = nameEnd;
vector<gd::Expression> parameters;
bool nameIsFunction = firstPointPos > firstParPos;
if (!nameIsFunction) {
parameters.push_back(gd::Expression(objectName));
functionNameEnd = expression.find_first_of("( ", nameEnd);
if (nameEnd + 1 < expression.length())
functionName =
expression.substr(nameEnd + 1, functionNameEnd - (nameEnd + 1));
}
// Identify parameters
size_t parametersEnd = expression.find_first_of("(", functionNameEnd) + 1;
char32_t previousChar = U'(';
bool takeSymbolsInAccount = true;
size_t level = 0;
gd::String currentParameterStr;
while (parametersEnd < expression.length() &&
!(expression[parametersEnd] == U')' && level == 0)) {
// Be sure we are not in quotes
if (expression[parametersEnd] == U'\"' && previousChar != U'\\')
takeSymbolsInAccount = !takeSymbolsInAccount;
// So as to be sure paranthesis don't belong to a parameter
if (expression[parametersEnd] == U'(' && takeSymbolsInAccount) level++;
if (expression[parametersEnd] == U')' && takeSymbolsInAccount) level--;
// Add the character to the current parameter or terminate the latter
if ((expression[parametersEnd] == ',' && level == 0) &&
takeSymbolsInAccount) {
gd::Expression currentParameter(currentParameterStr);
parameters.push_back(currentParameter);
currentParameterStr.clear();
} else
currentParameterStr += expression[parametersEnd];
previousChar = expression[parametersEnd];
parametersEnd++;
}
if (parametersEnd == expression.length() ||
expression[parametersEnd] != U')') {
firstErrorPos = parametersEnd - 1;
firstErrorStr = _("Paranthesis not closed");
return false;
}
if (currentParameterStr.find_first_not_of(" ") !=
string::npos) // Add last parameter if needed
{
gd::Expression lastParameter(currentParameterStr);
parameters.push_back(lastParameter);
}
bool functionFound = false;
// First try to bind to a static str expression
if (nameIsFunction &&
MetadataProvider::HasStrExpression(platform, functionName)) {
functionFound = true;
const gd::ExpressionMetadata& expressionInfo =
MetadataProvider::GetStrExpressionMetadata(platform, functionName);
// Testing the number of parameters
if (parameters.size() >
GetMaximalParametersNumber(expressionInfo.parameters) ||
parameters.size() <
GetMinimalParametersNumber(expressionInfo.parameters)) {
firstErrorPos = functionNameEnd;
firstErrorStr = _("Incorrect number of parameters");
return false;
}
// Preparing parameters
parameters = CompleteParameters(expressionInfo.parameters, parameters);
for (std::size_t i = 0;
i < parameters.size() && i < expressionInfo.parameters.size();
++i) {
if (!PrepareParameter(platform,
project,
layout,
callbacks,
parameters[i],
expressionInfo.parameters[i],
functionNameEnd))
return false;
}
callbacks.OnStaticFunction(functionName, parameters, expressionInfo);
}
// Then an object member expression
else if (!nameIsFunction &&
MetadataProvider::HasObjectStrExpression(
platform,
gd::GetTypeOfObject(project, layout, objectName),
functionName)) {
functionFound = true;
const gd::ExpressionMetadata& expressionInfo =
MetadataProvider::GetObjectStrExpressionMetadata(
platform,
gd::GetTypeOfObject(project, layout, nameBefore),
functionName);
// Testing the number of parameters
if (parameters.size() >
GetMaximalParametersNumber(expressionInfo.parameters) ||
parameters.size() <
GetMinimalParametersNumber(expressionInfo.parameters)) {
firstErrorPos = functionNameEnd;
firstErrorStr = _("Incorrect number of parameters");
return false;
}
// Preparing parameters
parameters = CompleteParameters(expressionInfo.parameters, parameters);
for (std::size_t i = 0;
i < parameters.size() && i < expressionInfo.parameters.size();
++i) {
if (!PrepareParameter(platform,
project,
layout,
callbacks,
parameters[i],
expressionInfo.parameters[i],
functionNameEnd))
return false;
}
callbacks.OnObjectFunction(functionName, parameters, expressionInfo);
}
// And search behaviors expressions
else {
size_t firstDoublePoints = functionName.find("::");
if (firstDoublePoints != string::npos) {
gd::String autoName = functionName.substr(0, firstDoublePoints);
if (firstDoublePoints + 2 < functionName.length())
functionName = functionName.substr(firstDoublePoints + 2,
functionName.length());
else
functionName = "";
if (MetadataProvider::HasBehaviorStrExpression(
platform,
gd::GetTypeOfBehavior(project, layout, autoName),
functionName)) {
parameters.push_back(gd::Expression(autoName));
functionFound = true;
const gd::ExpressionMetadata& expressionInfo =
MetadataProvider::GetBehaviorStrExpressionMetadata(
platform,
gd::GetTypeOfBehavior(project, layout, autoName),
functionName);
// Verify that object has behavior.
vector<gd::String> behaviors =
gd::GetBehaviorsOfObject(project, layout, objectName);
if (find(behaviors.begin(), behaviors.end(), autoName) ==
behaviors.end()) {
cout << "Bad behavior requested" << endl;
functionFound = false;
} else {
// Testing the number of parameters
if (parameters.size() >
GetMaximalParametersNumber(expressionInfo.parameters) ||
parameters.size() <
GetMinimalParametersNumber(expressionInfo.parameters)) {
firstErrorPos = functionNameEnd;
firstErrorStr = _("Incorrect number of parameters");
return false;
}
// Preparing parameters
parameters =
CompleteParameters(expressionInfo.parameters, parameters);
for (std::size_t i = 0; i < parameters.size() &&
i < expressionInfo.parameters.size();
++i) {
if (!PrepareParameter(platform,
project,
layout,
callbacks,
parameters[i],
expressionInfo.parameters[i],
functionNameEnd))
return false;
}
callbacks.OnObjectBehaviorFunction(
functionName, parameters, expressionInfo);
}
}
}
}
// Note : _No_ support for implicit conversion from math result to string
if (!functionFound) // Function was not found
{
firstErrorPos = nameStart;
firstErrorStr = _("Function not recognized.");
return false;
}
parsePosition = parametersEnd + 1;
}
// Searching for next token
size_t firstPlusPos = expression.find("+", parsePosition);
firstPointPos = expression.find(".", parsePosition);
firstParPos = expression.find("(", parsePosition);
firstQuotePos = expression.find("\"", parsePosition);
// Checking for a + between token
if ((firstPointPos != string::npos || firstParPos != string::npos ||
firstQuotePos != string::npos)) {
size_t nextTokenPos = firstPointPos;
if (firstParPos < nextTokenPos) nextTokenPos = firstParPos;
if (firstQuotePos < nextTokenPos) nextTokenPos = firstQuotePos;
if (nextTokenPos < firstPlusPos) {
firstErrorPos = nextTokenPos;
firstErrorStr = _("Symbol missing between two +.");
return false;
} else if (expression.find("+", firstPlusPos + 1) < nextTokenPos) {
firstErrorPos = firstPlusPos;
firstErrorStr = _("Symbol missing between two +.");
return false;
}
}
}
if (expression.substr(parsePosition, expression.length())
.find_first_not_of(" \n") != gd::String::npos) {
firstErrorPos = parsePosition;
firstErrorStr = _("Bad symbol at the end of the expression.");
return false;
}
return true;
}
bool ExpressionParser::PrepareParameter(
const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
ParserCallbacks& callbacks,
gd::Expression& parameter,
const gd::ParameterMetadata& parametersInfo,
const size_t positionInExpression) {
if (ParameterMetadata::IsExpression("number", parametersInfo.type)) {
if (parametersInfo.optional && parameter.GetPlainString().empty())
parameter = parametersInfo.GetDefaultValue().empty()
? gd::Expression("0")
: gd::Expression(parametersInfo.GetDefaultValue());
if (!callbacks.OnSubMathExpression(platform, project, layout, parameter)) {
firstErrorStr = callbacks.firstErrorStr;
firstErrorPos = callbacks.firstErrorPos + positionInExpression;
return false;
}
} else if (ParameterMetadata::IsExpression("string", parametersInfo.type)) {
if (parametersInfo.optional && parameter.GetPlainString().empty())
parameter = parametersInfo.GetDefaultValue().empty()
? gd::Expression("\"\"")
: gd::Expression(parametersInfo.GetDefaultValue());
if (!callbacks.OnSubTextExpression(platform, project, layout, parameter)) {
firstErrorStr = callbacks.firstErrorStr;
firstErrorPos = callbacks.firstErrorPos + positionInExpression;
return false;
}
}
return true;
}
ExpressionParser::ExpressionParser(const gd::String& expressionPlainString_)
: expressionPlainString(expressionPlainString_) {}
} // namespace gd

View File

@@ -1,179 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_EXPRESSIONPARSER_H
#define GDCORE_EXPRESSIONPARSER_H
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Expression;
class ParserCallbacks;
class ObjectsContainer;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
}
namespace gd {
/** \brief Parse an expression
*
* Parse an expression, calling callbacks when a token is reached
* \see gd::ParserCallbacks
*/
class GD_CORE_API ExpressionParser {
public:
ExpressionParser(const gd::String &expressionPlainString_);
virtual ~ExpressionParser(){};
/**
* \brief Parse the expression, calling each functor when necessary
* \return True if expression was correctly parsed.
*/
bool ParseMathExpression(const gd::Platform &platform,
const gd::ObjectsContainer &project,
const gd::ObjectsContainer &layout,
gd::ParserCallbacks &callbacks);
/**
* \brief Parse the expression, calling each functor when necessary
* \return True if expression was correctly parsed.
*/
bool ParseStringExpression(const gd::Platform &platform,
const gd::ObjectsContainer &project,
const gd::ObjectsContainer &layout,
gd::ParserCallbacks &callbacks);
/**
* \brief Return the description of the error that was found
*/
const gd::String &GetFirstError() { return firstErrorStr; }
/**
* \brief Return the position of the error that was found
* \return The position, or gd::String::npos if no error is found
*/
size_t GetFirstErrorPosition() { return firstErrorPos; }
private:
gd::String firstErrorStr;
size_t firstErrorPos;
/**
* Tool function to add a parameter
*/
bool AddParameterToList(const gd::ObjectsContainer &project,
const gd::ObjectsContainer &layout,
ParserCallbacks &,
std::vector<gd::Expression> &parameters,
gd::String parameterStr,
std::vector<gd::ParameterMetadata> parametersInfos,
const size_t positionInExpression);
/**
* Tool function to prepare a parameter
*/
bool PrepareParameter(const gd::Platform &platform,
const gd::ObjectsContainer &project,
const gd::ObjectsContainer &layout,
ParserCallbacks &,
gd::Expression &parameter,
const gd::ParameterMetadata &parametersInfo,
const size_t positionInExpression);
/**
* Return the minimal number of parameters which can be used when calling an
* expression ( i.e. ParametersCount-OptionalParameters-CodeOnlyParameters )
*/
size_t GetMinimalParametersNumber(
const std::vector<gd::ParameterMetadata> &parametersInfos);
/**
* Return the maximal number of parameters which can be used when calling an
* expression ( i.e. ParametersCount-CodeOnlyParameters )
*/
size_t GetMaximalParametersNumber(
const std::vector<gd::ParameterMetadata> &parametersInfos);
bool ValidSyntax(const gd::String &str);
gd::String expressionPlainString;
static gd::String parserSeparators;
};
/** \brief Callbacks called by parser during parsing
*
* Parser will call the appropriate functions during parsing, allowing to do
* special works. \see gd::ExpressionParser
*/
class GD_CORE_API ParserCallbacks {
friend class ExpressionParser;
public:
ParserCallbacks() : returnType("expression"){};
virtual ~ParserCallbacks(){};
/**
* \brief Get the type of the expression for which callbacks are used:
* "expression" or "string".
*/
const gd::String &GetReturnType() { return returnType; }
virtual void OnConstantToken(gd::String text) = 0;
virtual void OnStaticFunction(
gd::String functionName,
const std::vector<gd::Expression> &parameters,
const gd::ExpressionMetadata &expressionInfo) = 0;
virtual void OnObjectFunction(
gd::String functionName,
const std::vector<gd::Expression> &parameters,
const gd::ExpressionMetadata &expressionInfo) = 0;
virtual void OnObjectBehaviorFunction(
gd::String functionName,
const std::vector<gd::Expression> &parameters,
const gd::ExpressionMetadata &expressionInfo) = 0;
virtual bool OnSubMathExpression(const gd::Platform &platform,
const gd::ObjectsContainer &project,
const gd::ObjectsContainer &layout,
gd::Expression &expression) = 0;
virtual bool OnSubTextExpression(const gd::Platform &platform,
const gd::ObjectsContainer &project,
const gd::ObjectsContainer &layout,
gd::Expression &expression) = 0;
/**
* \brief Return the description of the error that was found
*/
const gd::String &GetFirstError() { return firstErrorStr; }
/**
* \brief Return the position of the error that was found
* \return The position, or gd::String::npos if no error is found
*/
size_t GetFirstErrorPosition() { return firstErrorPos; }
protected:
gd::String firstErrorStr;
size_t firstErrorPos;
private:
/**
* \brief Set the return type of the expression: Done by ExpressionParser
* according to which Parse* method is called. \see gd::ExpressionParser
*/
void SetReturnType(gd::String type) { returnType = type; }
gd::String returnType; // The type of the expression ("expression" (default),
// "string"...)
};
} // namespace gd
#endif // GDEXPRESSIONPARSER_H

View File

@@ -22,15 +22,6 @@ using namespace std;
namespace gd {
gd::String ExpressionParser2::NUMBER_FIRST_CHAR = ".0123456789";
gd::String ExpressionParser2::DOT = ".";
gd::String ExpressionParser2::PARAMETERS_SEPARATOR = ",";
gd::String ExpressionParser2::QUOTE = "\"";
gd::String ExpressionParser2::BRACKETS = "()[]{}";
gd::String ExpressionParser2::EXPRESSION_OPERATORS = "+-<>?^=\\:!";
gd::String ExpressionParser2::TERM_OPERATORS = "/*";
gd::String ExpressionParser2::UNARY_OPERATORS = "+-";
gd::String ExpressionParser2::WHITESPACES = " \n\r";
gd::String ExpressionParser2::NAMESPACE_SEPARATOR = "::";
ExpressionParser2::ExpressionParser2(
@@ -76,7 +67,7 @@ size_t GetMaximumParametersNumber(
} // namespace
std::unique_ptr<ExpressionParserDiagnostic> ExpressionParser2::ValidateFunction(
const gd::FunctionNode& function, size_t functionStartPosition) {
const gd::FunctionCallNode& function, size_t functionStartPosition) {
if (gd::MetadataProvider::IsBadExpressionMetadata(
function.expressionMetadata)) {
return gd::make_unique<ExpressionParserError>(
@@ -118,11 +109,13 @@ std::unique_ptr<ExpressionParserDiagnostic> ExpressionParser2::ValidateFunction(
}
std::unique_ptr<TextNode> ExpressionParser2::ReadText() {
SkipWhitespace();
if (!IsAnyChar("\"")) {
size_t textStartPosition = GetCurrentPosition();
SkipAllWhitespaces();
if (!CheckIfChar(IsQuote)) {
auto text = gd::make_unique<TextNode>("");
text->diagnostic =
RaiseSyntaxError(_("A text must start with a double quote (\")."));
text->location = ExpressionParserLocation(textStartPosition, GetCurrentPosition());
return text;
}
SkipChar();
@@ -157,6 +150,7 @@ std::unique_ptr<TextNode> ExpressionParser2::ReadText() {
}
auto text = gd::make_unique<TextNode>(parsedText);
text->location = ExpressionParserLocation(textStartPosition, GetCurrentPosition());
if (!textParsingHasEnded) {
text->diagnostic =
RaiseSyntaxError(_("A text must end with a double quote (\"). Add a "
@@ -167,24 +161,25 @@ std::unique_ptr<TextNode> ExpressionParser2::ReadText() {
}
std::unique_ptr<NumberNode> ExpressionParser2::ReadNumber() {
SkipWhitespace();
size_t numberStartPosition = GetCurrentPosition();
SkipAllWhitespaces();
gd::String parsedNumber;
bool numberHasStarted = false;
bool digitFound = false;
bool dotFound = false;
while (!IsEndReached()) {
if (IsAnyChar("0")) {
if (CheckIfChar(IsZeroDigit)) {
numberHasStarted = true;
digitFound = true;
if (!parsedNumber.empty()) { // Ignore leading 0s.
parsedNumber += GetCurrentChar();
}
} else if (IsAnyChar("123456789")) {
} else if (CheckIfChar(IsNonZeroDigit)) {
numberHasStarted = true;
digitFound = true;
parsedNumber += GetCurrentChar();
} else if (IsAnyChar(".") && !dotFound) {
} else if (CheckIfChar(IsDot) && !dotFound) {
numberHasStarted = true;
dotFound = true;
if (parsedNumber == "") {
@@ -209,6 +204,7 @@ std::unique_ptr<NumberNode> ExpressionParser2::ReadNumber() {
// valid in most languages so we allow this.
auto number = gd::make_unique<NumberNode>(parsedNumber);
number->location = ExpressionParserLocation(numberStartPosition, GetCurrentPosition());
if (!numberHasStarted || !digitFound) {
number->diagnostic = RaiseSyntaxError(
_("A number was expected. You must enter a number here."));

View File

@@ -48,8 +48,9 @@ class GD_CORE_API ExpressionParser2 {
*
* \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
* gd::ParameterMetadata::IsExpression or "unknown".
* \param expression The expression to parse
* \param objectName Specify the object name, only for the
* case of "objectvar" type.
*
* \return The node representing the expression as a parsed tree.
@@ -71,18 +72,21 @@ class GD_CORE_API ExpressionParser2 {
///@{
std::unique_ptr<ExpressionNode> Start(const gd::String &type,
const gd::String &objectName = "") {
size_t expressionStartPosition = GetCurrentPosition();
auto expression = Expression(type, objectName);
// Check for extra characters at the end of the expression
if (!IsEndReached()) {
auto op = gd::make_unique<OperatorNode>();
op->op = ' ';
auto op = gd::make_unique<OperatorNode>(type, ' ');
op->leftHandSide = std::move(expression);
op->rightHandSide = ReadUntilEnd("unknown");
op->rightHandSide->diagnostic = RaiseSyntaxError(
_("The expression has extra character at the end that should be "
"removed (or completed if your expression is not finished)."));
op->location = ExpressionParserLocation(expressionStartPosition,
GetCurrentPosition());
return std::move(op);
}
@@ -91,22 +95,24 @@ class GD_CORE_API ExpressionParser2 {
std::unique_ptr<ExpressionNode> Expression(
const gd::String &type, const gd::String &objectName = "") {
SkipWhitespace();
SkipAllWhitespaces();
size_t expressionStartPosition = GetCurrentPosition();
std::unique_ptr<ExpressionNode> leftHandSide = Term(type, objectName);
SkipWhitespace();
SkipAllWhitespaces();
if (IsEndReached()) return leftHandSide;
if (IsAnyChar(",)]")) return leftHandSide;
if (IsAnyChar(EXPRESSION_OPERATORS)) {
auto op = gd::make_unique<OperatorNode>();
op->op = GetCurrentChar();
if (CheckIfChar(IsExpressionEndingChar)) return leftHandSide;
if (CheckIfChar(IsExpressionOperator)) {
auto op = gd::make_unique<OperatorNode>(type, GetCurrentChar());
op->leftHandSide = std::move(leftHandSide);
op->diagnostic = ValidateOperator(type, GetCurrentChar());
SkipChar();
op->rightHandSide = Expression(type, objectName);
op->location = ExpressionParserLocation(expressionStartPosition,
GetCurrentPosition());
return std::move(op);
}
@@ -124,31 +130,35 @@ class GD_CORE_API ExpressionParser2 {
"properly written.");
}
auto op = gd::make_unique<OperatorNode>();
op->op = ' ';
auto op = gd::make_unique<OperatorNode>(type, ' ');
op->leftHandSide = std::move(leftHandSide);
op->rightHandSide = Expression(type, objectName);
op->location =
ExpressionParserLocation(expressionStartPosition, GetCurrentPosition());
return std::move(op);
}
std::unique_ptr<ExpressionNode> Term(const gd::String &type,
const gd::String &objectName) {
SkipWhitespace();
SkipAllWhitespaces();
size_t expressionStartPosition = GetCurrentPosition();
std::unique_ptr<ExpressionNode> factor = Factor(type, objectName);
SkipWhitespace();
SkipAllWhitespaces();
// This while loop is used instead of a recursion (like in Expression)
// to guarantee the proper operator precedence. (Expression could also
// be reworked to use a while loop).
while (IsAnyChar(TERM_OPERATORS)) {
auto op = gd::make_unique<OperatorNode>();
op->op = GetCurrentChar();
while (CheckIfChar(IsTermOperator)) {
auto op = gd::make_unique<OperatorNode>(type, GetCurrentChar());
op->leftHandSide = std::move(factor);
op->diagnostic = ValidateOperator(type, GetCurrentChar());
SkipChar();
op->rightHandSide = Factor(type, objectName);
SkipWhitespace();
op->location = ExpressionParserLocation(expressionStartPosition,
GetCurrentPosition());
SkipAllWhitespaces();
factor = std::move(op);
}
@@ -158,12 +168,12 @@ class GD_CORE_API ExpressionParser2 {
std::unique_ptr<ExpressionNode> Factor(const gd::String &type,
const gd::String &objectName) {
SkipWhitespace();
SkipAllWhitespaces();
size_t expressionStartPosition = GetCurrentPosition();
std::unique_ptr<ExpressionNode> factor;
if (IsAnyChar(QUOTE)) {
if (CheckIfChar(IsQuote)) {
factor = ReadText();
if (type == "number")
factor->diagnostic =
@@ -173,14 +183,17 @@ class GD_CORE_API ExpressionParser2 {
factor->diagnostic = RaiseTypeError(
_("You entered a text, but this type was expected:") + type,
expressionStartPosition);
} else if (IsAnyChar(UNARY_OPERATORS)) {
auto unaryOperator = gd::make_unique<UnaryOperatorNode>(GetCurrentChar());
} else if (CheckIfChar(IsUnaryOperator)) {
auto unaryOperator =
gd::make_unique<UnaryOperatorNode>(type, GetCurrentChar());
unaryOperator->diagnostic = ValidateUnaryOperator(type, GetCurrentChar());
SkipChar();
unaryOperator->factor = Factor(type, objectName);
unaryOperator->location = ExpressionParserLocation(
expressionStartPosition, GetCurrentPosition());
factor = std::move(unaryOperator);
} else if (IsAnyChar(NUMBER_FIRST_CHAR)) {
} else if (CheckIfChar(IsNumberFirstChar)) {
factor = ReadNumber();
if (type == "string")
factor->diagnostic = RaiseTypeError(
@@ -190,16 +203,16 @@ class GD_CORE_API ExpressionParser2 {
factor->diagnostic = RaiseTypeError(
_("You entered a number, but this type was expected:") + type,
expressionStartPosition);
} else if (IsAnyChar("(")) {
} else if (CheckIfChar(IsOpeningParenthesis)) {
SkipChar();
factor = SubExpression(type, objectName);
if (!IsAnyChar(")")) {
if (!CheckIfChar(IsClosingParenthesis)) {
factor->diagnostic =
RaiseSyntaxError(_("Missing a closing parenthesis. Add a closing "
"parenthesis for each opening parenthesis."));
}
SkipIfIsAnyChar(")");
SkipIfChar(IsClosingParenthesis);
} else if (IsIdentifierAllowedChar()) {
// This is a place where the grammar differs according to the
// type being expected.
@@ -218,92 +231,132 @@ class GD_CORE_API ExpressionParser2 {
std::unique_ptr<SubExpressionNode> SubExpression(
const gd::String &type, const gd::String &objectName) {
return std::move(
gd::make_unique<SubExpressionNode>(Expression(type, objectName)));
size_t expressionStartPosition = GetCurrentPosition();
auto subExpression =
gd::make_unique<SubExpressionNode>(type, Expression(type, objectName));
subExpression->location =
ExpressionParserLocation(expressionStartPosition, GetCurrentPosition());
return std::move(subExpression);
};
std::unique_ptr<IdentifierOrFunctionOrEmptyNode> Identifier(
const gd::String &type) {
size_t identifierStartPosition = GetCurrentPosition();
gd::String name = ReadIdentifierName();
std::unique_ptr<IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode>
Identifier(const gd::String &type) {
auto identifierAndLocation = ReadIdentifierName();
gd::String name = identifierAndLocation.name;
auto nameLocation = identifierAndLocation.location;
SkipWhitespace();
SkipAllWhitespaces();
// We consider a namespace separator to be allowed here and be part of the
// function name (or object name, but object names are not allowed to
// contain a ":"). This is because functions from extensions have their
// extension name prefix, and separated by the namespace separator. This
// could maybe be refactored to create different nodes in the future.
if (IsNamespaceSeparator()) {
SkipNamespaceSeparator();
SkipAllWhitespaces();
auto postNamespaceIdentifierAndLocation = ReadIdentifierName();
name += NAMESPACE_SEPARATOR;
name += ReadIdentifierName();
name += postNamespaceIdentifierAndLocation.name;
ExpressionParserLocation completeNameLocation(
nameLocation.GetStartPosition(),
postNamespaceIdentifierAndLocation.location.GetEndPosition());
nameLocation = completeNameLocation;
}
if (IsAnyChar("(")) {
SkipChar();
return FreeFunction(type, name, identifierStartPosition);
} else if (IsAnyChar(DOT)) {
SkipChar();
if (CheckIfChar(IsOpeningParenthesis)) {
ExpressionParserLocation openingParenthesisLocation = SkipChar();
return FreeFunction(type, name, nameLocation, openingParenthesisLocation);
} else if (CheckIfChar(IsDot)) {
ExpressionParserLocation dotLocation = SkipChar();
SkipAllWhitespaces();
return ObjectFunctionOrBehaviorFunction(
type, name, identifierStartPosition);
type, name, nameLocation, dotLocation);
} else {
auto identifier = gd::make_unique<IdentifierNode>(name, type);
if (type == "string") {
identifier->diagnostic =
RaiseTypeError(_("You must wrap your text inside double quotes "
"(example: \"Hello world\")."),
identifierStartPosition);
nameLocation.GetStartPosition());
} else if (type == "number") {
identifier->diagnostic = RaiseTypeError(_("You must enter a number."),
identifierStartPosition);
identifier->diagnostic = RaiseTypeError(
_("You must enter a number."), nameLocation.GetStartPosition());
} else if (!gd::ParameterMetadata::IsObject(type)) {
identifier->diagnostic = RaiseTypeError(
_("You've entered a name, but this type was expected:") + type,
identifierStartPosition);
nameLocation.GetStartPosition());
}
identifier->location = ExpressionParserLocation(
nameLocation.GetStartPosition(), GetCurrentPosition());
return std::move(identifier);
}
}
std::unique_ptr<VariableNode> Variable(const gd::String &type,
const gd::String &objectName) {
size_t identifierStartPosition = GetCurrentPosition();
auto identifierAndLocation = ReadIdentifierName();
const gd::String &name = identifierAndLocation.name;
const auto &nameLocation = identifierAndLocation.location;
gd::String name = ReadIdentifierName();
auto variable = gd::make_unique<VariableNode>(type, name, objectName);
variable->child = VariableAccessorOrVariableBracketAccessor();
variable->location = ExpressionParserLocation(
nameLocation.GetStartPosition(), GetCurrentPosition());
variable->nameLocation = nameLocation;
return std::move(variable);
}
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode>
VariableAccessorOrVariableBracketAccessor() {
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode> child;
SkipWhitespace();
if (IsAnyChar("[")) {
size_t childStartPosition = GetCurrentPosition();
SkipAllWhitespaces();
if (CheckIfChar(IsOpeningSquareBracket)) {
SkipChar();
child =
auto child =
gd::make_unique<VariableBracketAccessorNode>(Expression("string"));
if (!IsAnyChar("]")) {
if (!CheckIfChar(IsClosingSquareBracket)) {
child->diagnostic =
RaiseSyntaxError(_("Missing a closing bracket. Add a closing "
"bracket for each opening bracket."));
}
SkipIfIsAnyChar("]");
SkipIfChar(IsClosingSquareBracket);
child->child = VariableAccessorOrVariableBracketAccessor();
} else if (IsAnyChar(DOT)) {
SkipChar();
SkipWhitespace();
child->location =
ExpressionParserLocation(childStartPosition, GetCurrentPosition());
child = gd::make_unique<VariableAccessorNode>(ReadIdentifierName());
return std::move(child);
} else if (CheckIfChar(IsDot)) {
auto dotLocation = SkipChar();
SkipAllWhitespaces();
auto identifierAndLocation = ReadIdentifierName();
auto child =
gd::make_unique<VariableAccessorNode>(identifierAndLocation.name);
child->child = VariableAccessorOrVariableBracketAccessor();
child->nameLocation = identifierAndLocation.location;
child->dotLocation = dotLocation;
child->location =
ExpressionParserLocation(childStartPosition, GetCurrentPosition());
return std::move(child);
}
return child;
return std::move(
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode>());
}
std::unique_ptr<FunctionNode> FreeFunction(const gd::String &type,
const gd::String &functionFullName,
size_t functionStartPosition) {
std::unique_ptr<FunctionCallNode> FreeFunction(
const gd::String &type,
const gd::String &functionFullName,
const ExpressionParserLocation &identifierLocation,
const ExpressionParserLocation &openingParenthesisLocation) {
// TODO: error if trying to use function for type != "number" && != "string"
// + Test for it
@@ -315,32 +368,49 @@ class GD_CORE_API ExpressionParser2 {
: MetadataProvider::GetStrExpressionMetadata(
platform, functionFullName);
auto parametersAndError = Parameters(metadata.parameters);
auto function = gd::make_unique<FunctionNode>(
type, std::move(parametersAndError.first), metadata, functionFullName);
function->diagnostic = std::move(parametersAndError.second);
auto parametersNode = Parameters(metadata.parameters);
auto function = gd::make_unique<FunctionCallNode>(
type, std::move(parametersNode.parameters), metadata, functionFullName);
function->diagnostic = std::move(parametersNode.diagnostic);
if (!function->diagnostic)
function->diagnostic = ValidateFunction(*function, functionStartPosition);
function->diagnostic =
ValidateFunction(*function, identifierLocation.GetStartPosition());
function->location = ExpressionParserLocation(
identifierLocation.GetStartPosition(), GetCurrentPosition());
function->functionNameLocation = identifierLocation;
function->openingParenthesisLocation = openingParenthesisLocation;
function->closingParenthesisLocation =
parametersNode.closingParenthesisLocation;
return std::move(function);
}
std::unique_ptr<FunctionOrEmptyNode> ObjectFunctionOrBehaviorFunction(
std::unique_ptr<FunctionCallOrObjectFunctionNameOrEmptyNode>
ObjectFunctionOrBehaviorFunction(
const gd::String &type,
const gd::String &objectName,
size_t functionStartPosition) {
gd::String objectFunctionOrBehaviorName = ReadIdentifierName();
const ExpressionParserLocation &objectNameLocation,
const ExpressionParserLocation &objectNameDotLocation) {
auto identifierAndLocation = ReadIdentifierName();
const gd::String &objectFunctionOrBehaviorName = identifierAndLocation.name;
const auto &objectFunctionOrBehaviorNameLocation =
identifierAndLocation.location;
SkipWhitespace();
SkipAllWhitespaces();
if (IsNamespaceSeparator()) {
SkipNamespaceSeparator();
ExpressionParserLocation namespaceSeparatorLocation =
SkipNamespaceSeparator();
SkipAllWhitespaces();
return BehaviorFunction(type,
objectName,
objectFunctionOrBehaviorName,
functionStartPosition);
} else if (IsAnyChar("(")) {
SkipChar();
objectNameLocation,
objectNameDotLocation,
objectFunctionOrBehaviorNameLocation,
namespaceSeparatorLocation);
} else if (CheckIfChar(IsOpeningParenthesis)) {
ExpressionParserLocation openingParenthesisLocation = SkipChar();
gd::String objectType =
GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName);
@@ -354,40 +424,60 @@ class GD_CORE_API ExpressionParser2 {
: MetadataProvider::GetObjectStrExpressionMetadata(
platform, objectType, objectFunctionOrBehaviorName);
auto parametersAndError = Parameters(metadata.parameters, objectName);
auto function =
gd::make_unique<FunctionNode>(type,
objectName,
std::move(parametersAndError.first),
metadata,
objectFunctionOrBehaviorName);
function->diagnostic = std::move(parametersAndError.second);
auto parametersNode = Parameters(metadata.parameters, objectName);
auto function = gd::make_unique<FunctionCallNode>(
type,
objectName,
std::move(parametersNode.parameters),
metadata,
objectFunctionOrBehaviorName);
function->diagnostic = std::move(parametersNode.diagnostic);
if (!function->diagnostic)
function->diagnostic =
ValidateFunction(*function, functionStartPosition);
ValidateFunction(*function, objectNameLocation.GetStartPosition());
function->location = ExpressionParserLocation(
objectNameLocation.GetStartPosition(), GetCurrentPosition());
function->objectNameLocation = objectNameLocation;
function->objectNameDotLocation = objectNameDotLocation;
function->functionNameLocation = objectFunctionOrBehaviorNameLocation;
function->openingParenthesisLocation = openingParenthesisLocation;
function->closingParenthesisLocation =
parametersNode.closingParenthesisLocation;
return std::move(function);
}
auto node = gd::make_unique<EmptyNode>(type);
auto node = gd::make_unique<ObjectFunctionNameNode>(
type, objectName, objectFunctionOrBehaviorName);
node->diagnostic = RaiseSyntaxError(
_("An opening parenthesis (for an object expression), or double colon "
"(::) was expected (for a behavior expression)."));
node->location = ExpressionParserLocation(
objectNameLocation.GetStartPosition(), GetCurrentPosition());
node->objectNameLocation = objectNameLocation;
node->objectNameDotLocation = objectNameDotLocation;
node->objectFunctionOrBehaviorNameLocation =
objectFunctionOrBehaviorNameLocation;
return std::move(node);
}
std::unique_ptr<FunctionOrEmptyNode> BehaviorFunction(
std::unique_ptr<FunctionCallOrObjectFunctionNameOrEmptyNode> BehaviorFunction(
const gd::String &type,
const gd::String &objectName,
const gd::String &behaviorName,
size_t functionStartPosition) {
gd::String functionName = ReadIdentifierName();
const ExpressionParserLocation &objectNameLocation,
const ExpressionParserLocation &objectNameDotLocation,
const ExpressionParserLocation &behaviorNameLocation,
const ExpressionParserLocation &behaviorNameNamespaceSeparatorLocation) {
auto identifierAndLocation = ReadIdentifierName();
const gd::String &functionName = identifierAndLocation.name;
const auto &functionNameLocation = identifierAndLocation.location;
SkipWhitespace();
SkipAllWhitespaces();
if (IsAnyChar("(")) {
SkipChar();
if (CheckIfChar(IsOpeningParenthesis)) {
ExpressionParserLocation openingParenthesisLocation = SkipChar();
gd::String behaviorType = GetTypeOfBehavior(
globalObjectsContainer, objectsContainer, behaviorName);
@@ -400,35 +490,61 @@ class GD_CORE_API ExpressionParser2 {
: MetadataProvider::GetBehaviorStrExpressionMetadata(
platform, behaviorType, functionName);
auto parametersAndError =
auto parametersNode =
Parameters(metadata.parameters, objectName, behaviorName);
auto function =
gd::make_unique<FunctionNode>(type,
objectName,
behaviorName,
std::move(parametersAndError.first),
metadata,
functionName);
function->diagnostic = std::move(parametersAndError.second);
auto function = gd::make_unique<FunctionCallNode>(
type,
objectName,
behaviorName,
std::move(parametersNode.parameters),
metadata,
functionName);
function->diagnostic = std::move(parametersNode.diagnostic);
if (!function->diagnostic)
function->diagnostic =
ValidateFunction(*function, functionStartPosition);
ValidateFunction(*function, objectNameLocation.GetStartPosition());
function->location = ExpressionParserLocation(
objectNameLocation.GetStartPosition(), GetCurrentPosition());
function->objectNameLocation = objectNameLocation;
function->objectNameDotLocation = objectNameDotLocation;
function->behaviorNameLocation = behaviorNameLocation;
function->behaviorNameNamespaceSeparatorLocation =
behaviorNameNamespaceSeparatorLocation;
function->openingParenthesisLocation = openingParenthesisLocation;
function->closingParenthesisLocation =
parametersNode.closingParenthesisLocation;
function->functionNameLocation = functionNameLocation;
return std::move(function);
} else {
auto node = gd::make_unique<EmptyNode>(type);
auto node = gd::make_unique<ObjectFunctionNameNode>(
type, objectName, behaviorName, functionName);
node->diagnostic = RaiseSyntaxError(
_("An opening parenthesis was expected here to call a function."));
node->location = ExpressionParserLocation(
objectNameLocation.GetStartPosition(), GetCurrentPosition());
node->objectNameLocation = objectNameLocation;
node->objectNameDotLocation = objectNameDotLocation;
node->objectFunctionOrBehaviorNameLocation = behaviorNameLocation;
node->behaviorNameNamespaceSeparatorLocation =
behaviorNameNamespaceSeparatorLocation;
node->behaviorFunctionNameLocation = functionNameLocation;
return std::move(node);
}
}
std::pair<std::vector<std::unique_ptr<ExpressionNode>>,
std::unique_ptr<gd::ExpressionParserError>>
Parameters(std::vector<gd::ParameterMetadata> parameterMetadata,
const gd::String &objectName = "",
const gd::String &behaviorName = "") {
// A temporary node that will be integrated into function nodes.
struct ParametersNode {
std::vector<std::unique_ptr<ExpressionNode>> parameters;
std::unique_ptr<gd::ExpressionParserError> diagnostic;
ExpressionParserLocation closingParenthesisLocation;
};
ParametersNode Parameters(
std::vector<gd::ParameterMetadata> parameterMetadata,
const gd::String &objectName = "",
const gd::String &behaviorName = "") {
std::vector<std::unique_ptr<ExpressionNode>> parameters;
// By convention, object is always the first parameter, and behavior the
@@ -437,11 +553,12 @@ class GD_CORE_API ExpressionParser2 {
WrittenParametersFirstIndex(objectName, behaviorName);
while (!IsEndReached()) {
SkipWhitespace();
SkipAllWhitespaces();
if (IsAnyChar(")")) {
SkipChar();
return std::make_pair(std::move(parameters), nullptr);
if (CheckIfChar(IsClosingParenthesis)) {
auto closingParenthesisLocation = SkipChar();
return ParametersNode{
std::move(parameters), nullptr, closingParenthesisLocation};
} else {
if (parameterIndex < parameterMetadata.size()) {
const gd::String &type = parameterMetadata[parameterIndex].GetType();
@@ -479,16 +596,18 @@ class GD_CORE_API ExpressionParser2 {
GetCurrentPosition());
}
SkipWhitespace();
SkipIfIsAnyChar(PARAMETERS_SEPARATOR);
SkipAllWhitespaces();
SkipIfChar(IsParameterSeparator);
parameterIndex++;
}
}
return std::make_pair(
ExpressionParserLocation invalidClosingParenthesisLocation;
return ParametersNode{
std::move(parameters),
RaiseSyntaxError(_("The list of parameters is not terminated. Add a "
"closing parenthesis to end the parameters.")));
"closing parenthesis to end the parameters.")),
invalidClosingParenthesisLocation};
}
///@}
@@ -497,7 +616,7 @@ class GD_CORE_API ExpressionParser2 {
*/
///@{
std::unique_ptr<ExpressionParserDiagnostic> ValidateFunction(
const gd::FunctionNode &function, size_t functionStartPosition);
const gd::FunctionCallNode &function, size_t functionStartPosition);
std::unique_ptr<ExpressionParserDiagnostic> ValidateOperator(
const gd::String &type, gd::String::value_type operatorChar) {
@@ -525,7 +644,8 @@ class GD_CORE_API ExpressionParser2 {
} else if (gd::ParameterMetadata::IsObject(type)) {
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("Operators (+, -, /, *) can't be used with an object name. Remove the operator."),
_("Operators (+, -, /, *) can't be used with an object name. Remove "
"the operator."),
GetCurrentPosition());
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
return gd::make_unique<ExpressionParserError>(
@@ -561,7 +681,8 @@ class GD_CORE_API ExpressionParser2 {
} else if (gd::ParameterMetadata::IsObject(type)) {
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("Operators (+, -) can't be used with an object name. Remove the operator."),
_("Operators (+, -) can't be used with an object name. Remove the "
"operator."),
GetCurrentPosition());
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
return gd::make_unique<ExpressionParserError>(
@@ -579,55 +700,133 @@ class GD_CORE_API ExpressionParser2 {
* Read tokens or characters
*/
///@{
void SkipChar() { currentPosition++; }
ExpressionParserLocation SkipChar() {
size_t startPosition = currentPosition;
return ExpressionParserLocation(startPosition, ++currentPosition);
}
void SkipWhitespace() {
void SkipAllWhitespaces() {
while (currentPosition < expression.size() &&
WHITESPACES.find(expression[currentPosition]) != gd::String::npos) {
IsWhitespace(expression[currentPosition])) {
currentPosition++;
}
}
void SkipIfIsAnyChar(const gd::String &allowedCharacters) {
if (IsAnyChar(allowedCharacters)) {
void SkipIfChar(
const std::function<bool(gd::String::value_type)> &predicate) {
if (CheckIfChar(predicate)) {
currentPosition++;
}
}
void SkipNamespaceSeparator() {
ExpressionParserLocation SkipNamespaceSeparator() {
size_t startPosition = currentPosition;
// Namespace separator is a special kind of delimiter as it is 2 characters
// long
if (IsNamespaceSeparator()) {
currentPosition += NAMESPACE_SEPARATOR.size();
}
return ExpressionParserLocation(startPosition, currentPosition);
}
bool IsAnyChar(const gd::String &allowedCharacters) {
if (currentPosition < expression.size() &&
allowedCharacters.find(expression[currentPosition]) !=
gd::String::npos) {
return true;
}
bool CheckIfChar(
const std::function<bool(gd::String::value_type)> &predicate) {
if (currentPosition >= expression.size()) return false;
gd::String::value_type character = expression[currentPosition];
return false;
return predicate(character);
}
bool IsIdentifierAllowedChar() {
if (currentPosition < expression.size() &&
PARAMETERS_SEPARATOR.find(expression[currentPosition]) ==
gd::String::npos &&
DOT.find(expression[currentPosition]) == gd::String::npos &&
QUOTE.find(expression[currentPosition]) == gd::String::npos &&
BRACKETS.find(expression[currentPosition]) == gd::String::npos &&
EXPRESSION_OPERATORS.find(expression[currentPosition]) ==
gd::String::npos &&
TERM_OPERATORS.find(expression[currentPosition]) == gd::String::npos) {
if (currentPosition >= expression.size()) return false;
gd::String::value_type character = expression[currentPosition];
// Quickly compare if the character is a number or ASCII character.
if ((character >= '0' && character <= '9') ||
(character >= 'A' && character <= 'Z') ||
(character >= 'a' && character <= 'z'))
return true;
// Otherwise do the full check against separators forbidden in identifiers.
if (!IsParameterSeparator(character) && !IsDot(character) &&
!IsQuote(character) && !IsBracket(character) &&
!IsExpressionOperator(character) && !IsTermOperator(character)) {
return true;
}
return false;
}
static bool IsWhitespace(gd::String::value_type character) {
return character == ' ' || character == '\n' || character == '\r';
}
static bool IsParameterSeparator(gd::String::value_type character) {
return character == ',';
}
static bool IsDot(gd::String::value_type character) {
return character == '.';
}
static bool IsQuote(gd::String::value_type character) {
return character == '"';
}
static bool IsBracket(gd::String::value_type character) {
return character == '(' || character == ')' || character == '[' ||
character == ']' || character == '{' || character == '}';
}
static bool IsOpeningParenthesis(gd::String::value_type character) {
return character == '(';
}
static bool IsClosingParenthesis(gd::String::value_type character) {
return character == ')';
}
static bool IsOpeningSquareBracket(gd::String::value_type character) {
return character == '[';
}
static bool IsClosingSquareBracket(gd::String::value_type character) {
return character == ']';
}
static bool IsExpressionEndingChar(gd::String::value_type character) {
return character == ',' || IsClosingParenthesis(character) ||
IsClosingSquareBracket(character);
}
static bool IsExpressionOperator(gd::String::value_type character) {
return character == '+' || character == '-' || character == '<' ||
character == '>' || character == '?' || character == '^' ||
character == '=' || character == '\\' || character == ':' ||
character == '!';
}
static bool IsUnaryOperator(gd::String::value_type character) {
return character == '+' || character == '-';
}
static bool IsTermOperator(gd::String::value_type character) {
return character == '/' || character == '*';
}
static bool IsNumberFirstChar(gd::String::value_type character) {
return character == '.' || (character >= '0' && character <= '9');
}
static bool IsNonZeroDigit(gd::String::value_type character) {
return (character >= '1' && character <= '9');
}
static bool IsZeroDigit(gd::String::value_type character) {
return character == '0';
}
bool IsNamespaceSeparator() {
// Namespace separator is a special kind of delimiter as it is 2 characters
// long
@@ -638,12 +837,20 @@ class GD_CORE_API ExpressionParser2 {
bool IsEndReached() { return currentPosition >= expression.size(); }
gd::String ReadIdentifierName() {
// A temporary node used when reading an identifier
struct IdentifierAndLocation {
gd::String name;
ExpressionParserLocation location;
};
IdentifierAndLocation ReadIdentifierName() {
gd::String name;
size_t startPosition = currentPosition;
while (currentPosition < expression.size() &&
(IsIdentifierAllowedChar()
// Allow whitespace in identifier name for compatibility
|| expression[currentPosition] == ' ')) {
||
expression[currentPosition] == ' ')) {
name += expression[currentPosition];
currentPosition++;
}
@@ -651,12 +858,23 @@ class GD_CORE_API ExpressionParser2 {
// Trim whitespace at the end (we allow them for compatibility inside
// the name, but after the last character that is not whitespace, they
// should be ignore again).
size_t lastCharacterPos = name.find_last_not_of(WHITESPACES);
if (!name.empty() && (lastCharacterPos + 1) < name.size()) {
name.erase(lastCharacterPos + 1);
if (!name.empty() && IsWhitespace(name[name.size() - 1])) {
size_t lastCharacterPos = name.size() - 1;
while (lastCharacterPos < name.size() &&
IsWhitespace(name[lastCharacterPos])) {
lastCharacterPos--;
}
if ((lastCharacterPos + 1) < name.size()) {
name.erase(lastCharacterPos + 1);
}
}
return name;
IdentifierAndLocation identifierAndLocation{
name,
// The location is ignoring the trailing whitespace (only whitespace
// inside the identifier are allowed for compatibility).
ExpressionParserLocation(startPosition, startPosition + name.size())};
return identifierAndLocation;
}
std::unique_ptr<TextNode> ReadText();
@@ -664,24 +882,32 @@ class GD_CORE_API ExpressionParser2 {
std::unique_ptr<NumberNode> ReadNumber();
std::unique_ptr<EmptyNode> ReadUntilWhitespace(gd::String type) {
size_t startPosition = GetCurrentPosition();
gd::String text;
while (currentPosition < expression.size() &&
WHITESPACES.find(expression[currentPosition]) == gd::String::npos) {
!IsWhitespace(expression[currentPosition])) {
text += expression[currentPosition];
currentPosition++;
}
return gd::make_unique<EmptyNode>(type, text);
auto node = gd::make_unique<EmptyNode>(type, text);
node->location =
ExpressionParserLocation(startPosition, GetCurrentPosition());
return node;
}
std::unique_ptr<EmptyNode> ReadUntilEnd(gd::String type) {
size_t startPosition = GetCurrentPosition();
gd::String text;
while (currentPosition < expression.size()) {
text += expression[currentPosition];
currentPosition++;
}
return gd::make_unique<EmptyNode>(type, text);
auto node = gd::make_unique<EmptyNode>(type, text);
node->location =
ExpressionParserLocation(startPosition, GetCurrentPosition());
return node;
}
size_t GetCurrentPosition() { return currentPosition; }
@@ -746,15 +972,6 @@ class GD_CORE_API ExpressionParser2 {
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
static gd::String NUMBER_FIRST_CHAR;
static gd::String DOT;
static gd::String PARAMETERS_SEPARATOR;
static gd::String QUOTE;
static gd::String BRACKETS;
static gd::String EXPRESSION_OPERATORS;
static gd::String TERM_OPERATORS;
static gd::String UNARY_OPERATORS;
static gd::String WHITESPACES;
static gd::String NAMESPACE_SEPARATOR;
};

View File

@@ -20,6 +20,24 @@ class ExpressionMetadata;
namespace gd {
struct ExpressionParserLocation {
ExpressionParserLocation() : isValid(false){};
ExpressionParserLocation(size_t position)
: isValid(true), startPosition(position), endPosition(position){};
ExpressionParserLocation(size_t startPosition_, size_t endPosition_)
: isValid(true),
startPosition(startPosition_),
endPosition(endPosition_){};
size_t GetStartPosition() const { return startPosition; }
size_t GetEndPosition() const { return endPosition; }
bool IsValid() const { return isValid; }
private:
bool isValid;
size_t startPosition;
size_t endPosition;
};
/**
* \brief A diagnostic that can be attached to a gd::ExpressionNode.
*/
@@ -40,30 +58,25 @@ struct ExpressionParserError : public ExpressionParserDiagnostic {
ExpressionParserError(const gd::String &type_,
const gd::String &message_,
size_t position_)
: type(type_),
message(message_),
startPosition(position_),
endPosition(position_){};
: type(type_), message(message_), location(position_){};
ExpressionParserError(const gd::String &type_,
const gd::String &message_,
size_t startPosition_,
size_t endPosition_)
: type(type_),
message(message_),
startPosition(startPosition_),
endPosition(endPosition_){};
location(startPosition_, endPosition_){};
virtual ~ExpressionParserError(){};
bool IsError() override { return true; }
const gd::String &GetMessage() override { return message; }
size_t GetStartPosition() override { return startPosition; }
size_t GetEndPosition() override { return endPosition; }
size_t GetStartPosition() override { return location.GetStartPosition(); }
size_t GetEndPosition() override { return location.GetEndPosition(); }
private:
gd::String type;
gd::String message;
size_t startPosition;
size_t endPosition;
ExpressionParserLocation location;
};
/**
@@ -75,16 +88,26 @@ struct ExpressionNode {
virtual void Visit(ExpressionParser2NodeWorker &worker){};
std::unique_ptr<ExpressionParserDiagnostic> diagnostic;
ExpressionParserLocation location; ///< The location of the entire node. Some
///nodes might have other locations stored
///inside them. For example, a function
///can store the position of the object
///name, the dot, the function name,
///etc...
};
struct SubExpressionNode : public ExpressionNode {
SubExpressionNode(std::unique_ptr<ExpressionNode> expression_)
: expression(std::move(expression_)){};
SubExpressionNode(const gd::String &type_,
std::unique_ptr<ExpressionNode> expression_)
: type(type_), expression(std::move(expression_)){};
virtual ~SubExpressionNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitSubExpressionNode(*this);
};
gd::String type; // "string", "number", type supported by
// gd::ParameterMetadata::IsObject, types supported by
// gd::ParameterMetadata::IsExpression or "unknown".
std::unique_ptr<ExpressionNode> expression;
};
@@ -92,6 +115,8 @@ struct SubExpressionNode : public ExpressionNode {
* \brief An operator node. For example: "lhs + rhs".
*/
struct OperatorNode : public ExpressionNode {
OperatorNode(const gd::String &type_, gd::String::value_type op_)
: type(type_), op(op_){};
virtual ~OperatorNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitOperatorNode(*this);
@@ -99,6 +124,9 @@ struct OperatorNode : public ExpressionNode {
std::unique_ptr<ExpressionNode> leftHandSide;
std::unique_ptr<ExpressionNode> rightHandSide;
gd::String type; // "string", "number", type supported by
// gd::ParameterMetadata::IsObject, types supported by
// gd::ParameterMetadata::IsExpression or "unknown".
gd::String::value_type op;
};
@@ -106,13 +134,17 @@ struct OperatorNode : public ExpressionNode {
* \brief A unary operator node. For example: "-2".
*/
struct UnaryOperatorNode : public ExpressionNode {
UnaryOperatorNode(gd::String::value_type op_) : op(op_){};
UnaryOperatorNode(const gd::String &type_, gd::String::value_type op_)
: type(type_), op(op_){};
virtual ~UnaryOperatorNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitUnaryOperatorNode(*this);
};
std::unique_ptr<ExpressionNode> factor;
gd::String type; // "string", "number", type supported by
// gd::ParameterMetadata::IsObject, types supported by
// gd::ParameterMetadata::IsExpression or "unknown".
gd::String::value_type op;
};
@@ -170,6 +202,8 @@ struct VariableNode : public ExpressionNode {
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode>
child; // Can be nullptr if no accessor
ExpressionParserLocation nameLocation;
};
/**
@@ -185,6 +219,8 @@ struct VariableAccessorNode
};
gd::String name;
ExpressionParserLocation nameLocation;
ExpressionParserLocation dotLocation;
};
/**
@@ -203,12 +239,14 @@ struct VariableBracketAccessorNode
std::unique_ptr<ExpressionNode> expression;
};
struct IdentifierOrFunctionOrEmptyNode : public ExpressionNode {};
struct IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
: public ExpressionNode {};
/**
* \brief An identifier node, usually representing an object.
* \brief An identifier node, usually representing an object or a function name.
*/
struct IdentifierNode : public IdentifierOrFunctionOrEmptyNode {
struct IdentifierNode
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
IdentifierNode(const gd::String &identifierName_, const gd::String &type_)
: identifierName(identifierName_), type(type_){};
virtual ~IdentifierNode(){};
@@ -220,64 +258,142 @@ struct IdentifierNode : public IdentifierOrFunctionOrEmptyNode {
gd::String type;
};
struct FunctionOrEmptyNode : public IdentifierOrFunctionOrEmptyNode {
virtual ~FunctionOrEmptyNode(){};
struct FunctionCallOrObjectFunctionNameOrEmptyNode
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
virtual ~FunctionCallOrObjectFunctionNameOrEmptyNode(){};
void Visit(ExpressionParser2NodeWorker &worker) override{};
};
/**
* \brief A function node. For example: "MyExtension::MyFunction(1, 2)".
* \brief The name of a function to call on an object or the behavior
* For example: "MyObject.Function" or "MyObject.Physics" or
* "MyObject.Physics::LinearVelocity".
*/
struct FunctionNode : public FunctionOrEmptyNode {
FunctionNode(const gd::String &type_,
std::vector<std::unique_ptr<ExpressionNode>> parameters_,
const ExpressionMetadata &expressionMetadata_,
const gd::String &functionName_)
struct ObjectFunctionNameNode
: public FunctionCallOrObjectFunctionNameOrEmptyNode {
ObjectFunctionNameNode(const gd::String &type_,
const gd::String &objectName_,
const gd::String &objectFunctionOrBehaviorName_)
: type(type_),
objectName(objectName_),
objectFunctionOrBehaviorName(objectFunctionOrBehaviorName_) {}
ObjectFunctionNameNode(const gd::String &type_,
const gd::String &objectName_,
const gd::String &behaviorName_,
const gd::String &behaviorFunctionName_)
: type(type_),
objectName(objectName_),
objectFunctionOrBehaviorName(behaviorName_),
behaviorFunctionName(behaviorFunctionName_) {}
virtual ~ObjectFunctionNameNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitObjectFunctionNameNode(*this);
};
gd::String type; // This could be removed if the type ("string", "number",
// type supported by gd::ParameterMetadata::IsObject, types
// supported by gd::ParameterMetadata::IsExpression or
// "unknown") was stored in ExpressionMetadata.
gd::String objectName;
gd::String objectFunctionOrBehaviorName; ///< Behavior name if
///`behaviorFunctionName` is not
///empty.
gd::String behaviorFunctionName; ///< If empty, then
///objectFunctionOrBehaviorName is filled
///with the behavior name.
ExpressionParserLocation
objectNameLocation; ///< Location of the object name.
ExpressionParserLocation
objectNameDotLocation; ///< Location of the "." after the object name.
ExpressionParserLocation objectFunctionOrBehaviorNameLocation; ///< Location
///of object
///function
///name or
///behavior
///name.
ExpressionParserLocation
behaviorNameNamespaceSeparatorLocation; ///< Location of the "::"
///separator, if any.
ExpressionParserLocation behaviorFunctionNameLocation; ///< Location of the
///behavior function
///name, if any.
};
/**
* \brief A function call node (either free function, object function or object
* behavior function).
* For example: "MyExtension::MyFunction(1, 2)", "MyObject.Function()" or
* "MyObject.Physics::LinearVelocity()".
*/
struct FunctionCallNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
FunctionCallNode(const gd::String &type_,
std::vector<std::unique_ptr<ExpressionNode>> parameters_,
const ExpressionMetadata &expressionMetadata_,
const gd::String &functionName_)
: type(type_),
parameters(std::move(parameters_)),
expressionMetadata(expressionMetadata_),
functionName(functionName_){};
FunctionNode(const gd::String &type_,
const gd::String &objectName_,
std::vector<std::unique_ptr<ExpressionNode>> parameters_,
const ExpressionMetadata &expressionMetadata_,
const gd::String &functionName_)
FunctionCallNode(const gd::String &type_,
const gd::String &objectName_,
std::vector<std::unique_ptr<ExpressionNode>> parameters_,
const ExpressionMetadata &expressionMetadata_,
const gd::String &functionName_)
: type(type_),
objectName(objectName_),
parameters(std::move(parameters_)),
expressionMetadata(expressionMetadata_),
functionName(functionName_){};
FunctionNode(const gd::String &type_,
const gd::String &objectName_,
const gd::String &behaviorName_,
std::vector<std::unique_ptr<ExpressionNode>> parameters_,
const ExpressionMetadata &expressionMetadata_,
const gd::String &functionName_)
FunctionCallNode(const gd::String &type_,
const gd::String &objectName_,
const gd::String &behaviorName_,
std::vector<std::unique_ptr<ExpressionNode>> parameters_,
const ExpressionMetadata &expressionMetadata_,
const gd::String &functionName_)
: type(type_),
objectName(objectName_),
behaviorName(behaviorName_),
parameters(std::move(parameters_)),
expressionMetadata(expressionMetadata_),
functionName(functionName_){};
virtual ~FunctionNode(){};
virtual ~FunctionCallNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitFunctionNode(*this);
worker.OnVisitFunctionCallNode(*this);
};
gd::String type; // This could be removed if the type ("string" or "number")
// was stored in ExpressionMetadata.
gd::String type; // This could be removed if the type ("string", "number",
// type supported by gd::ParameterMetadata::IsObject, types
// supported by gd::ParameterMetadata::IsExpression or
// "unknown") was stored in ExpressionMetadata.
gd::String objectName;
gd::String behaviorName;
std::vector<std::unique_ptr<ExpressionNode>> parameters;
const ExpressionMetadata &expressionMetadata;
gd::String functionName;
ExpressionParserLocation
functionNameLocation; ///< Location of the function name.
ExpressionParserLocation
objectNameLocation; ///< Location of the object name, if any.
ExpressionParserLocation
objectNameDotLocation; ///< Location of the "." after the object name.
ExpressionParserLocation
behaviorNameLocation; ///< Location of the behavior name, if any.
ExpressionParserLocation
behaviorNameNamespaceSeparatorLocation; ///< Location of the "::"
///separator, if any.
ExpressionParserLocation
openingParenthesisLocation; ///< Location of the "(".
ExpressionParserLocation
closingParenthesisLocation; ///< Location of the ")".
};
/**
* \brief An empty node, used when parsing failed/a syntax error was
* encountered and any other node could not make sense.
*/
struct EmptyNode : public FunctionOrEmptyNode {
struct EmptyNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
EmptyNode(const gd::String &type_, const gd::String &text_ = "")
: type(type_), text(text_){};
virtual ~EmptyNode(){};
@@ -285,10 +401,12 @@ struct EmptyNode : public FunctionOrEmptyNode {
worker.OnVisitEmptyNode(*this);
};
gd::String type;
gd::String type; // "string", "number", type supported by
// gd::ParameterMetadata::IsObject, types supported by
// gd::ParameterMetadata::IsExpression or "unknown".
gd::String text;
};
} // namespace gd
#endif
#endif

View File

@@ -92,7 +92,15 @@ class GD_CORE_API ExpressionParser2NodePrinter
void OnVisitIdentifierNode(IdentifierNode& node) override {
output += node.identifierName;
}
void OnVisitFunctionNode(FunctionNode& node) override {
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (!node.behaviorFunctionName.empty()) {
output +=
node.objectName + "." + node.objectFunctionOrBehaviorName + "::" + node.behaviorFunctionName;
} else {
output += node.objectName + "." + node.objectFunctionOrBehaviorName;
}
};
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (!node.behaviorName.empty()) {
output +=
node.objectName + "." + node.behaviorName + "::" + node.functionName;

View File

@@ -16,10 +16,11 @@ class TextNode;
class VariableNode;
class VariableAccessorNode;
class VariableBracketAccessorNode;
class IdentifierOrFunctionOrEmptyNode;
class IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode;
class IdentifierNode;
class FunctionOrEmptyNode;
class FunctionNode;
class FunctionCallOrObjectFunctionNameOrEmptyNode;
class ObjectFunctionNameNode;
class FunctionCallNode;
class EmptyNode;
} // namespace gd
@@ -42,10 +43,11 @@ class GD_CORE_API ExpressionParser2NodeWorker {
friend class VariableNode;
friend class VariableAccessorNode;
friend class VariableBracketAccessorNode;
friend class IdentifierOrFunctionOrEmptyNode;
friend class IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode;
friend class IdentifierNode;
friend class FunctionOrEmptyNode;
friend class FunctionNode;
friend class FunctionCallOrObjectFunctionNameOrEmptyNode;
friend class ObjectFunctionNameNode;
friend class FunctionCallNode;
friend class EmptyNode;
public:
@@ -62,10 +64,11 @@ class GD_CORE_API ExpressionParser2NodeWorker {
virtual void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) = 0;
virtual void OnVisitIdentifierNode(IdentifierNode& node) = 0;
virtual void OnVisitFunctionNode(FunctionNode& node) = 0;
virtual void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) = 0;
virtual void OnVisitFunctionCallNode(FunctionCallNode& node) = 0;
virtual void OnVisitEmptyNode(EmptyNode& node) = 0;
};
} // namespace gd
#endif
#endif

View File

@@ -1,141 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Events/Parsers/VariableParser.h"
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Layout;
}
namespace gd {
class Project;
}
namespace gd {
class Platform;
}
#include "GDCore/Tools/Localization.h"
namespace gd {
VariableParser::~VariableParser() {}
bool VariableParser::Parse(VariableParserCallbacks& callbacks_) {
callbacks = &callbacks_;
rootVariableParsed = false;
firstErrorStr.clear();
firstErrorPos = 0;
currentPositionIt = expression.begin();
currentTokenType = TS_INVALID;
currentToken.clear();
S();
return firstErrorStr == "";
}
void VariableParser::ReadToken() {
currentTokenType = TS_INVALID;
currentToken.clear();
while (currentPositionIt != expression.end()) {
char32_t currentChar = *currentPositionIt;
if (currentChar == U'[' || currentChar == U']' || currentChar == U'.') {
if (currentTokenType == TS_VARNAME)
return; // We've parsed a variable name.
}
if (currentChar == U'[') {
currentTokenType = TS_OPENING_BRACKET;
currentToken.clear();
++currentPositionIt;
return;
} else if (currentChar == U']') {
currentTokenType = TS_CLOSING_BRACKET;
currentToken.clear();
++currentPositionIt;
return;
} else if (currentChar == U'.') {
currentTokenType = TS_PERIOD;
currentToken.clear();
++currentPositionIt;
return;
}
currentTokenType = TS_VARNAME; // We're parsing a variable name.
currentToken.push_back(currentChar);
++currentPositionIt;
}
// Can be reached if we are at the end of the expression. In this case,
// currentTokenType will be either TS_VARNAME or TS_INVALID.
}
void VariableParser::S() {
ReadToken();
if (currentTokenType != TS_VARNAME) {
firstErrorStr = _("Expecting a variable name.");
firstErrorPos = std::distance<gd::String::const_iterator>(
expression.begin(), currentPositionIt);
return;
}
if (!rootVariableParsed) {
rootVariableParsed = true;
if (callbacks) callbacks->OnRootVariable(currentToken);
} else if (callbacks)
callbacks->OnChildVariable(currentToken);
X();
}
void VariableParser::X() {
ReadToken();
if (currentTokenType == TS_INVALID)
return; // Ended parsing.
else if (currentTokenType == TS_PERIOD)
S();
else if (currentTokenType == TS_OPENING_BRACKET) {
gd::String strExpr = SkipStringExpression();
ReadToken();
if (currentTokenType != TS_CLOSING_BRACKET) {
firstErrorStr = _("Expecting ]");
firstErrorPos = std::distance<gd::String::const_iterator>(
expression.begin(), currentPositionIt);
return;
}
if (callbacks) callbacks->OnChildSubscript(strExpr);
X();
}
}
gd::String VariableParser::SkipStringExpression() {
gd::String stringExpression;
bool insideStringLiteral = false;
bool lastCharacterWasBackslash = false;
unsigned int nestedBracket = 0;
while (currentPositionIt != expression.end()) {
char32_t currentChar = *currentPositionIt;
if (currentChar == U'\"') {
if (!insideStringLiteral)
insideStringLiteral = true;
else if (!lastCharacterWasBackslash)
insideStringLiteral = false;
} else if (currentChar == U'[' && !insideStringLiteral) {
nestedBracket++;
} else if (currentChar == U']' && !insideStringLiteral) {
if (nestedBracket == 0)
return stringExpression; // Found the end of the string litteral.
nestedBracket--;
}
lastCharacterWasBackslash = currentChar == U'\\';
stringExpression.push_back(currentChar);
++currentPositionIt;
}
// End of the expression reached (so expression is invalid by the way)
return stringExpression;
}
} // namespace gd

View File

@@ -1,148 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_VARIABLEPARSER_H
#define GDCORE_VARIABLEPARSER_H
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Layout;
}
namespace gd {
class Project;
}
namespace gd {
class Platform;
}
namespace gd {
class VariableParserCallbacks;
}
namespace gd {
/** \brief Parse a variable expression.
*
* Parse an variable expression ( like
myVariable.child["subchild"+ToString(i)].subsubchild ),
* calling callbacks when a token is reached.
*
* Usage example:
\code
//...
//VariableCodeGenerationCallbacks is a class inheriting from
gd::VariableParserCallbacks VariableCodeGenerationCallbacks callbacks(output,
*this, context, VariableCodeGenerationCallbacks::PROJECT_VARIABLE);
gd::VariableParser parser(parameter);
if ( !parser.Parse(callbacks) )
cout << "Error :" << parser.GetFirstError() << " in: "<< parameter <<
endl;
\endcode
*
* Here is the parsed grammar: <br>
* S -> VarName X <br>
* X -> e | . S | [StringExpression] X <br>
*
* where e = nothing (end of expression), StringExpression = A valid string
expression and
* S is the start.
*
* \see gd::VariableParserCallbacks
*/
class GD_CORE_API VariableParser {
public:
/**
* \brief Default constructor
* \param expressionPlainString The string representing the expression to be
* parsed.
*/
VariableParser(const gd::String& expressionPlainString_)
: currentPositionIt(), expression(expressionPlainString_){};
virtual ~VariableParser();
/**
* Parse the expression, calling each callback when necessary.
* \param callbacks The callbacks to be called.
* \return true if expression was correctly parsed.
* \see gd::VariableParserCallbacks
*/
bool Parse(VariableParserCallbacks& callbacks);
/**
* \brief Return the description of the error that was found
*/
const gd::String& GetFirstError() { return firstErrorStr; }
/**
* \brief Return the position of the error that was found
* \return The position, or gd::String::npos if no error is found
*/
size_t GetFirstErrorPosition() { return firstErrorPos; }
gd::String firstErrorStr;
size_t firstErrorPos;
private:
void S();
void X();
/**
* \brief Skip the string expression, starting from the current position.
* \return The string expression skipped. currentPosition is now put on the
* closing bracket.
*/
gd::String SkipStringExpression();
void ReadToken();
enum TokenType {
TS_PERIOD,
TS_OPENING_BRACKET,
TS_CLOSING_BRACKET,
TS_VARNAME,
TS_INVALID
};
TokenType currentTokenType;
gd::String currentToken;
gd::String::const_iterator currentPositionIt;
gd::String expression;
VariableParserCallbacks* callbacks;
bool rootVariableParsed;
};
/**
* \brief Callbacks called by VariableParser when parsing a variable expression.
*/
class GD_CORE_API VariableParserCallbacks {
public:
/**
* \brief Called when the first variable has been parsed. ( varName1 in
* varName1.child for example. ) \param variableName The variable name.
*/
virtual void OnRootVariable(gd::String variableName) = 0;
/**
* \brief Called when accessing the child of a structure variable. ( child in
* varName1.child for example. ) \param variableName The child variable name.
*/
virtual void OnChildVariable(gd::String variableName) = 0;
/**
* \brief Called when accessing the child of a structure variable using a
* string expression in square brackets. ( "subscript" in
* varName1["subscript"] for example. )
*
* \param variableName The expression used to access the child variable.
*/
virtual void OnChildSubscript(gd::String stringExpression) = 0;
};
} // namespace gd
#endif // GDEXPRESSIONPARSER_H

View File

@@ -8,10 +8,15 @@
#include "GDCore/CommonTools.h"
#include "GDCore/String.h"
EventsCodeNameMangler *EventsCodeNameMangler::_singleton = NULL;
EventsCodeNameMangler *EventsCodeNameMangler::_singleton = nullptr;
gd::String EventsCodeNameMangler::GetMangledObjectsListName(
const gd::String& EventsCodeNameMangler::GetMangledObjectsListName(
const gd::String &originalObjectName) {
auto it = mangledObjectNames.find(originalObjectName);
if (it != mangledObjectNames.end()) {
return it->second;
}
gd::String partiallyMangledName = originalObjectName;
static const gd::String allowedCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@@ -27,11 +32,17 @@ gd::String EventsCodeNameMangler::GetMangledObjectsListName(
}
}
return "GD" + partiallyMangledName + "Objects";
mangledObjectNames[originalObjectName] = "GD" + partiallyMangledName + "Objects";
return mangledObjectNames[originalObjectName];
}
gd::String EventsCodeNameMangler::GetExternalEventsFunctionMangledName(
const gd::String& EventsCodeNameMangler::GetExternalEventsFunctionMangledName(
const gd::String &externalEventsName) {
auto it = mangledExternalEventsNames.find(externalEventsName);
if (it != mangledExternalEventsNames.end()) {
return it->second;
}
gd::String partiallyMangledName = externalEventsName;
static const gd::String allowedCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@@ -47,23 +58,24 @@ gd::String EventsCodeNameMangler::GetExternalEventsFunctionMangledName(
}
}
return "GDExternalEvents" + partiallyMangledName;
mangledExternalEventsNames[externalEventsName] = "GDExternalEvents" + partiallyMangledName;
return mangledExternalEventsNames[externalEventsName];
}
gd::String ManObjListName(const gd::String &objectName) {
const gd::String& ManObjListName(const gd::String &objectName) {
return EventsCodeNameMangler::Get()->GetMangledObjectsListName(objectName);
}
EventsCodeNameMangler *EventsCodeNameMangler::Get() {
if (NULL == _singleton) _singleton = new EventsCodeNameMangler;
if (nullptr == _singleton) _singleton = new EventsCodeNameMangler;
return (static_cast<EventsCodeNameMangler *>(_singleton));
}
void EventsCodeNameMangler::DestroySingleton() {
if (NULL != _singleton) {
if (nullptr != _singleton) {
delete _singleton;
_singleton = NULL;
_singleton = nullptr;
}
}

View File

@@ -6,26 +6,34 @@
#if defined(GD_IDE_ONLY)
#ifndef EVENTSCODENAMEMANGLER_H
#define EVENTSCODENAMEMANGLER_H
#include <unordered_map>
#include "GDCore/String.h"
/**
* Manage name mangling, so as to ensure all names used in code are valid.
* \brief Mangle object names, so as to ensure all names used in code are valid.
*
* \see ManObjListName
*/
class GD_CORE_API EventsCodeNameMangler {
public:
/**
* Get the mangled name from a name : All characters that are not 0-9, a-z,
* Get the mangled name from a name: All characters that are not 0-9, a-z,
* A-Z or _ are replaced by "_"+AsciiCodeOfTheCharacter.
*
* The mangled name is memoized as this is intensively used during project
* export and events code generation.
*/
gd::String GetMangledObjectsListName(const gd::String &originalObjectName);
const gd::String &GetMangledObjectsListName(
const gd::String &originalObjectName);
/**
* Get the mangled function name to be used to call external events named \a
* externalEventsName.
*
* The mangled name is memoized as this is intensively used during project
* export and events code generation.
*/
gd::String GetExternalEventsFunctionMangledName(
const gd::String &GetExternalEventsFunctionMangledName(
const gd::String &externalEventsName);
static EventsCodeNameMangler *Get();
@@ -35,14 +43,22 @@ class GD_CORE_API EventsCodeNameMangler {
EventsCodeNameMangler(){};
virtual ~EventsCodeNameMangler(){};
static EventsCodeNameMangler *_singleton;
std::unordered_map<gd::String, gd::String>
mangledObjectNames; ///< Memoized results of mangling for objects
std::unordered_map<gd::String, gd::String>
mangledExternalEventsNames; ///< Memoized results of mangling for
/// external events
};
/**
* Shortcut to
* EventsCodeNameMangler::Get()->GetMangledObjectsListName(objectName). \see
* EventsCodeNameMangler \return Mangled object name
* Shortcut for
* `EventsCodeNameMangler::Get()->GetMangledObjectsListName(objectName)`.
*
* \see EventsCodeNameMangler
* \return Mangled object name
*/
gd::String GD_CORE_API ManObjListName(const gd::String &objectName);
const gd::String &GD_CORE_API ManObjListName(const gd::String &objectName);
#endif // EVENTSCODENAMEMANGLER_H
#endif

View File

@@ -13,11 +13,12 @@ namespace gd {
void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("BuiltinObject",
_("Base object"),
_("Base object"),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionInformation(
"BuiltinObject",
_("Features for all objects"),
_("Common features that can be used for all objects in GDevelop."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects/base_object/events");
gd::ObjectMetadata& obj = extension.AddObject<gd::Object>(
@@ -72,14 +73,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.UseStandardOperatorParameters("number")
.MarkAsSimple();
obj.AddAction(
"MettreXY",
_("Position of an object"),
_("Change the position of an object."),
_("Change the position of _PARAM0_: _PARAM1_ _PARAM2_ (x axis), _PARAM3_ _PARAM4_ (y axis)"),
_("Position"),
"res/actions/position24.png",
"res/actions/position.png")
obj.AddAction("MettreXY",
_("Position of an object"),
_("Change the position of an object."),
_("Change the position of _PARAM0_: _PARAM1_ _PARAM2_ (x "
"axis), _PARAM3_ _PARAM4_ (y axis)"),
_("Position"),
"res/actions/position24.png",
"res/actions/position.png")
.AddParameter("object", _("Object"))
.AddParameter("operator", _("Modification's sign"))
@@ -294,14 +295,13 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddParameter("objectvar", _("Variable"))
.UseStandardOperatorParameters("number");
obj.AddAction(
"ModVarObjetTxt",
_("Modify the text of a variable of an object"),
_("Modify the text of a variable of an object"),
_("the text of variable _PARAM1_"),
_("Variables"),
"res/actions/var24.png",
"res/actions/var.png")
obj.AddAction("ModVarObjetTxt",
_("Modify the text of a variable of an object"),
_("Modify the text of a variable of an object"),
_("the text of variable _PARAM1_"),
_("Variables"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Variable"))
@@ -473,14 +473,13 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddParameter("objectvar", _("Variable"))
.UseStandardRelationalOperatorParameters("number");
obj.AddCondition(
"VarObjetTxt",
_("Text of an object's variable"),
_("Compare the text of a variable of an object."),
_("the text of variable _PARAM1_"),
_("Variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
obj.AddCondition("VarObjetTxt",
_("Text of an object's variable"),
_("Compare the text of a variable of an object."),
_("the text of variable _PARAM1_"),
_("Variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Variable"))
@@ -634,6 +633,23 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddParameter("expression", _("Y position of the point"))
.MarkAsSimple();
extension
.AddCondition("SourisSurObjet",
_("The cursor/touch is on an object"),
_("Test if the cursor is over an object, or if the object "
"is being touched."),
_("The cursor/touch is on _PARAM0_"),
_("Mouse and touch"),
"res/conditions/surObjet24.png",
"res/conditions/surObjet.png")
.AddParameter("objectList", _("Object"))
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("yesorno", _("Accurate test (yes by default)"), "", true)
.SetDefaultValue("yes")
.AddCodeOnlyParameter("conditionInverted", "")
.MarkAsSimple();
obj.AddCondition(
"ObjectTimer",
_("Value of a timer"),

View File

@@ -24,7 +24,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
.AddCondition("CameraX",
_("Camera center X position"),
_("Compare the X position of the center of a camera."),
_("the x position of camera _PARAM4_ (layer: _PARAM3_)"),
_("the X position of camera _PARAM4_ (layer: _PARAM3_)"),
_("Layers and cameras"),
"res/conditions/camera24.png",
"res/conditions/camera.png")
@@ -57,7 +57,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
"CameraX",
_("Camera center X position"),
_("Change the X position of the center of the specified camera."),
_("the x position of camera _PARAM4_ (layer: _PARAM3_)"),
_("the X position of camera _PARAM4_ (layer: _PARAM3_)"),
_("Layers and cameras"),
"res/conditions/camera24.png",
"res/conditions/camera.png")
@@ -74,7 +74,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
"CameraY",
_("Camera center Y position"),
_("Change the Y position of the center of the specified camera."),
_("the y position of camera _PARAM4_ (layer: _PARAM3_)"),
_("the Y position of camera _PARAM4_ (layer: _PARAM3_)"),
_("Layers and cameras"),
"res/conditions/camera24.png",
"res/conditions/camera.png")

View File

@@ -21,23 +21,6 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
.SetExtensionHelpPath("/all-features/mouse-touch");
#if defined(GD_IDE_ONLY)
extension
.AddCondition("SourisSurObjet",
_("The cursor/touch is on an object"),
_("Test if the cursor is over an object, or if the object "
"is being touched."),
_("The cursor/touch is on _PARAM0_"),
_("Mouse and touch"),
"res/conditions/surObjet24.png",
"res/conditions/surObjet.png")
.AddParameter("objectList", _("Object"))
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("yesorno", _("Accurate test (yes by default)"), "", true)
.SetDefaultValue("yes")
.AddCodeOnlyParameter("conditionInverted", "")
.MarkAsSimple();
extension
.AddCondition(
"IsMouseWheelScrollingUp",

View File

@@ -380,7 +380,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
obj.AddAction("ChangeBlendMode",
_("Blend mode"),
_("Change the number of the blend mode of an object.\nThe "
"default blend mode is 0 (Alpha)."),
"default blend mode is 0 (Normal)."),
_("Change Blend mode of _PARAM0_ to _PARAM1_"),
_("Effects"),
"res/actions/color24.png",
@@ -388,7 +388,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
.AddParameter("object", _("Object"), "Sprite")
.AddParameter("expression",
_("Mode (0 : Alpha, 1 : Add, 2 : Multiply, 3 : None)"))
_("Mode (0: Normal, 1: Add, 2: Multiply, 3: Screen)"))
.MarkAsSimple();
obj.AddAction("FlipX",

View File

@@ -121,16 +121,16 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
extension
.AddExpression("TimeDelta",
_("Time elapsed since the last image"),
_("Time elapsed since the last image"),
_("Time elapsed since the last frame"),
_("Time elapsed since the last frame rendered on screen"),
_("Time"),
"res/actions/time.png")
.AddCodeOnlyParameter("currentScene", "");
extension
.AddExpression("TempsFrame",
_("Time elapsed since the last image"),
_("Time elapsed since the last image"),
_("Time elapsed since the last frame"),
_("Time elapsed since the last frame rendered on screen"),
_("Time"),
"res/actions/time.png")
.SetHidden()
@@ -138,8 +138,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
extension
.AddExpression("ElapsedTime",
_("Time elapsed since the last image"),
_("Time elapsed since the last image"),
_("Time elapsed since the last frame"),
_("Time elapsed since the last frame rendered on screen"),
_("Time"),
"res/actions/time.png")
.SetHidden()

View File

@@ -14,19 +14,19 @@ using namespace std;
namespace gd {
Platform::Platform() {}
Platform::Platform(): enableExtensionLoadingLogs(true) {}
Platform::~Platform() {}
bool Platform::AddExtension(std::shared_ptr<gd::PlatformExtension> extension) {
if (!extension) return false;
std::cout << "Loading " << extension->GetName() << "...";
if (enableExtensionLoadingLogs) std::cout << "Loading " << extension->GetName() << "...";
if (IsExtensionLoaded(extension->GetName())) {
std::cout << " (replacing existing extension)";
if (enableExtensionLoadingLogs) std::cout << " (replacing existing extension)";
RemoveExtension(extension->GetName());
}
std::cout << std::endl;
if (enableExtensionLoadingLogs) std::cout << std::endl;
extensionsLoaded.push_back(extension);

View File

@@ -156,6 +156,12 @@ class GD_CORE_API Platform {
///@}
/**
* \brief Activate or disable the logs on the standard output when
* loading an extension.
*/
void EnableExtensionLoadingLogs(bool enable) { enableExtensionLoadingLogs = enable; };
/**
* \brief Called when the IDE is about to shut down: Take this opportunity for
* erasing for example any temporary file.
@@ -174,6 +180,7 @@ class GD_CORE_API Platform {
extensionsLoaded; ///< Extensions of the platform
std::map<gd::String, CreateFunPtr>
creationFunctionTable; ///< Creation functions for objects
bool enableExtensionLoadingLogs;
};
} // namespace gd

View File

@@ -63,7 +63,16 @@ class GD_CORE_API ExpressionObjectsAnalyzer
context.AddObjectName(node.identifierName);
}
}
void OnVisitFunctionNode(FunctionNode& node) override {
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (!node.objectName.empty()) {
context.AddObjectName(node.objectName);
if (!node.behaviorFunctionName.empty()) {
context.AddBehaviorName(node.objectName, node.objectFunctionOrBehaviorName);
}
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (!node.objectName.empty()) {
context.AddObjectName(node.objectName);

View File

@@ -35,7 +35,7 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
virtual ~ExpressionObjectRenamer(){};
static bool Rename(gd::ExpressionNode & node, const gd::String& objectName, const gd::String& objectNewName) {
if (ExpressionValidator::HasNoErrors(node)) {
if (ExpressionValidator::HasNoErrors(node)) {
ExpressionObjectRenamer renamer(objectName, objectNewName);
node.Visit(renamer);
@@ -77,7 +77,13 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
node.identifierName = objectNewName;
}
}
void OnVisitFunctionNode(FunctionNode& node) override {
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (node.objectName == objectName) {
hasDoneRenaming = true;
node.objectName = objectNewName;
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (node.objectName == objectName) {
hasDoneRenaming = true;
node.objectName = objectNewName;
@@ -107,7 +113,7 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
virtual ~ExpressionObjectFinder(){};
static bool CheckIfHasObject(gd::ExpressionNode & node, const gd::String & objectName) {
if (ExpressionValidator::HasNoErrors(node)) {
if (ExpressionValidator::HasNoErrors(node)) {
ExpressionObjectFinder finder(objectName);
node.Visit(finder);
@@ -148,7 +154,12 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
hasObject = true;
}
}
void OnVisitFunctionNode(FunctionNode& node) override {
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (node.objectName == objectName) {
hasObject = true;
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (node.objectName == objectName) {
hasObject = true;
}
@@ -184,7 +195,7 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
"number", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("number", actions[aId].GetParameter(pNb).GetPlainString());
if (ExpressionObjectRenamer::Rename(*node, oldName, newName)) {
actions[aId].SetParameter(pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -194,7 +205,7 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
"string", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("string", actions[aId].GetParameter(pNb).GetPlainString());
if (ExpressionObjectRenamer::Rename(*node, oldName, newName)) {
actions[aId].SetParameter(pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -237,7 +248,7 @@ bool EventsRefactorer::RenameObjectInConditions(
"number", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("number", conditions[cId].GetParameter(pNb).GetPlainString());
if (ExpressionObjectRenamer::Rename(*node, oldName, newName)) {
conditions[cId].SetParameter(pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -247,7 +258,7 @@ bool EventsRefactorer::RenameObjectInConditions(
"string", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("string", conditions[cId].GetParameter(pNb).GetPlainString());
if (ExpressionObjectRenamer::Rename(*node, oldName, newName)) {
conditions[cId].SetParameter(pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -323,7 +334,7 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
"number", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("number", actions[aId].GetParameter(pNb).GetPlainString());
if (ExpressionObjectFinder::CheckIfHasObject(*node, name)) {
deleteMe = true;
break;
@@ -334,7 +345,7 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
"string", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("string", actions[aId].GetParameter(pNb).GetPlainString());
if (ExpressionObjectFinder::CheckIfHasObject(*node, name)) {
deleteMe = true;
break;
@@ -384,7 +395,7 @@ bool EventsRefactorer::RemoveObjectInConditions(
"number", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("number", conditions[cId].GetParameter(pNb).GetPlainString());
if (ExpressionObjectFinder::CheckIfHasObject(*node, name)) {
deleteMe = true;
break;
@@ -395,7 +406,7 @@ bool EventsRefactorer::RemoveObjectInConditions(
"string", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("string", conditions[cId].GetParameter(pNb).GetPlainString());
if (ExpressionObjectFinder::CheckIfHasObject(*node, name)) {
deleteMe = true;
break;
@@ -595,7 +606,8 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
gd::String search,
bool matchCase,
bool inConditions,
bool inActions) {
bool inActions,
bool inEventStrings) {
vector<EventsSearchResult> results;
for (std::size_t i = 0; i < events.size(); ++i) {
@@ -631,6 +643,16 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
}
}
if (inEventStrings) {
if (!eventAddedInResults &&
SearchStringInEvent(project, layout, events[i], search, matchCase)) {
results.push_back(EventsSearchResult(
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
&events,
i));
}
}
if (events[i].CanHaveSubEvents()) {
vector<EventsSearchResult> subResults =
SearchInEvents(project,
@@ -639,7 +661,8 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
search,
matchCase,
inConditions,
inActions);
inActions,
inEventStrings);
std::copy(
subResults.begin(), subResults.end(), std::back_inserter(results));
}
@@ -711,6 +734,22 @@ bool EventsRefactorer::SearchStringInConditions(
return false;
}
bool EventsRefactorer::SearchStringInEvent(gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::BaseEvent& event,
gd::String search,
bool matchCase) {
for (gd::String str : event.GetAllSearchableStrings()) {
if (matchCase) {
if (str.find(search) != gd::String::npos) return true;
} else {
if (str.FindCaseInsensitive(search) != gd::String::npos) return true;
}
}
return false;
}
EventsSearchResult::EventsSearchResult(std::weak_ptr<gd::BaseEvent> event_,
gd::EventsList* eventsList_,
std::size_t positionInList_)

View File

@@ -103,7 +103,8 @@ class GD_CORE_API EventsRefactorer {
gd::String search,
bool matchCase,
bool inConditions,
bool inAction);
bool inActions,
bool inEventStrings);
/**
* Replace all occurrences of a gd::String in events
@@ -202,6 +203,11 @@ class GD_CORE_API EventsRefactorer {
gd::InstructionsList& conditions,
gd::String search,
bool matchCase);
static bool SearchStringInEvent(gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::BaseEvent& events,
gd::String search,
bool matchCase);
EventsRefactorer(){};
};

View File

@@ -63,7 +63,8 @@ class GD_CORE_API ExpressionParameterSearcher
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {}
void OnVisitFunctionNode(FunctionNode& node) override {
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
bool considerFunction = objectName.empty() || node.objectName == objectName;
for (size_t i = 0; i < node.parameters.size() &&
i < node.expressionMetadata.parameters.size();

View File

@@ -0,0 +1,366 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_EXPRESSIONAUTOCOMPLETIONPROVIDER_H
#define GDCORE_EXPRESSIONAUTOCOMPLETIONPROVIDER_H
#include <memory>
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/IDE/Events/ExpressionNodeLocationFinder.h"
namespace gd {
class Expression;
class ObjectsContainer;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
} // namespace gd
namespace gd {
/**
* \brief Describe completions to be shown to the user.
*
* The IDE is responsible for actually *searching* and showing the completions -
* this is only describing what must be listed.
*/
struct ExpressionCompletionDescription {
public:
/**
* The different kind of completions that can be described.
*/
enum CompletionKind {
Object,
Behavior,
Expression,
Variable,
};
/**
* \brief Create a completion for an object with the given prefix
*/
static ExpressionCompletionDescription ForObject(const gd::String& type_,
const gd::String& prefix_) {
return ExpressionCompletionDescription(Object, type_, prefix_);
}
/**
* \brief Create a completion for a behavior with the given prefix of
* the specified object
*/
static ExpressionCompletionDescription ForBehavior(
const gd::String& prefix_, const gd::String& objectName_) {
return ExpressionCompletionDescription(Behavior, "", prefix_, objectName_);
}
/**
* \brief Create a completion for a variable with the given prefix
*/
static ExpressionCompletionDescription ForVariable(
const gd::String& type_, const gd::String& prefix_) {
return ExpressionCompletionDescription(Variable, type_, prefix_);
}
/**
* \brief Create a completion for an expression (free, object or behavior
* expression) with the given prefix
*/
static ExpressionCompletionDescription ForExpression(
const gd::String& type_,
const gd::String& prefix_,
const gd::String& objectName_ = "",
const gd::String& behaviorName_ = "") {
return ExpressionCompletionDescription(
Expression, type_, prefix_, objectName_, behaviorName_);
}
/** Check if two description of completions are equal */
bool operator==(const ExpressionCompletionDescription& other) const {
return completionKind == other.completionKind && type == other.type &&
prefix == other.prefix && objectName == other.objectName &&
behaviorName == other.behaviorName;
};
/** \brief Return the kind of the completion */
CompletionKind GetCompletionKind() const { return completionKind; }
/**
* \brief Return the type of the completion (same type as types supported in
* expressions)
* (in other words, for expression this is the type of what must be returned).
*/
const gd::String& GetType() const { return type; }
/**
* \brief Return the prefix currently entered and that must be completed.
*/
const gd::String& GetPrefix() const { return prefix; }
/**
* \brief Return the object name, if completing an object expression or a
* behavior.
*/
const gd::String& GetObjectName() const { return objectName; }
/**
* \brief Return the behavior name, if completing an object behavior
* expression.
*
* \warning If completing a behavior, the behavior (partial) name is returned
* by `GetPrefix`.
*/
const gd::String& GetBehaviorName() const { return behaviorName; }
/**
* \brief Set if the completion description is exact, i.e: it's not used
* to complete anything. Rather, it should display information about what is
* described by the completion.
*/
ExpressionCompletionDescription& SetIsExact(bool isExact_) {
isExact = isExact_;
return *this;
}
/**
* \brief Check if the completion description is exact, i.e: it's not
* used to complete anything. Rather, it should display information
* about what is described by the completion.
*/
bool IsExact() const { return isExact; }
/** Default constructor, only to be used by Emscripten bindings. */
ExpressionCompletionDescription() : completionKind(Object){};
private:
ExpressionCompletionDescription(CompletionKind completionKind_,
const gd::String& type_,
const gd::String& prefix_,
const gd::String& objectName_ = "",
const gd::String& behaviorName_ = "")
: completionKind(completionKind_),
type(type_),
prefix(prefix_),
objectName(objectName_),
behaviorName(behaviorName_),
isExact(false) {}
CompletionKind completionKind;
gd::String type;
gd::String prefix;
gd::String objectName;
gd::String behaviorName;
bool isExact;
};
/**
* \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") << " }";
return os;
}
/**
* \brief Returns the list of completion descriptions for an expression node.
*
* \see gd::ExpressionCompletionDescription
*/
class GD_CORE_API ExpressionCompletionFinder
: public ExpressionParser2NodeWorker {
public:
/**
* \brief Given the expression, find the node at the specified location
* and returns completions for it.
*/
static std::vector<ExpressionCompletionDescription>
GetCompletionDescriptionsFor(gd::ExpressionNode& node,
size_t searchedPosition) {
gd::ExpressionNode* nodeAtLocation =
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(node,
searchedPosition);
if (nodeAtLocation == nullptr) {
std::vector<ExpressionCompletionDescription> emptyCompletions;
return emptyCompletions;
}
gd::ExpressionCompletionFinder autocompletionProvider(searchedPosition);
nodeAtLocation->Visit(autocompletionProvider);
return autocompletionProvider.GetCompletionDescriptions();
}
/**
* \brief Return the completions found for the visited node.
*/
const std::vector<ExpressionCompletionDescription>&
GetCompletionDescriptions() {
return completions;
};
virtual ~ExpressionCompletionFinder(){};
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
completions.push_back(
ExpressionCompletionDescription::ForObject(node.type, ""));
completions.push_back(
ExpressionCompletionDescription::ForExpression(node.type, ""));
}
void OnVisitOperatorNode(OperatorNode& node) override {
completions.push_back(
ExpressionCompletionDescription::ForObject(node.type, ""));
completions.push_back(
ExpressionCompletionDescription::ForExpression(node.type, ""));
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
completions.push_back(
ExpressionCompletionDescription::ForObject(node.type, ""));
completions.push_back(
ExpressionCompletionDescription::ForExpression(node.type, ""));
}
void OnVisitNumberNode(NumberNode& node) override {
// No completions
}
void OnVisitTextNode(TextNode& node) override {
// No completions
}
void OnVisitVariableNode(VariableNode& node) override {
completions.push_back(
ExpressionCompletionDescription::ForVariable(node.type, node.name));
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
// No completions
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
// No completions
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
if (gd::ParameterMetadata::IsObject(node.type)) {
// Only show completions of objects if an object is required
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.identifierName));
} else {
// Show completions for expressions and objects otherwise.
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.identifierName));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, node.identifierName));
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (!node.behaviorFunctionName.empty() ||
node.behaviorNameNamespaceSeparatorLocation.IsValid()) {
// Behavior function (or behavior function being written, with the
// function name missing)
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.objectName));
} else if (IsCaretOn(node.objectNameDotLocation) ||
IsCaretOn(node.objectFunctionOrBehaviorNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForBehavior(
node.objectFunctionOrBehaviorName, node.objectName));
} else if (IsCaretOn(node.behaviorNameNamespaceSeparatorLocation) ||
IsCaretOn(node.behaviorFunctionNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type,
node.behaviorFunctionName,
node.objectName,
node.objectFunctionOrBehaviorName));
}
} else {
// Object function or behavior name
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.objectName));
} else if (IsCaretOn(node.objectNameDotLocation) ||
IsCaretOn(node.objectFunctionOrBehaviorNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForBehavior(
node.objectFunctionOrBehaviorName, node.objectName));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, node.objectFunctionOrBehaviorName, node.objectName));
}
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
bool isCaretOnParenthesis = IsCaretOn(node.openingParenthesisLocation) ||
IsCaretOn(node.closingParenthesisLocation);
if (!node.behaviorName.empty()) {
// Behavior function
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.objectName));
} else if (IsCaretOn(node.objectNameDotLocation) ||
IsCaretOn(node.behaviorNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForBehavior(
node.behaviorName, node.objectName));
} else {
completions.push_back(
ExpressionCompletionDescription::ForExpression(node.type,
node.functionName,
node.objectName,
node.behaviorName)
.SetIsExact(isCaretOnParenthesis));
}
} else if (!node.objectName.empty()) {
// Object function
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.objectName));
} else {
// Add completions for behaviors, because we could imagine that the user
// wants to move from an object function to a behavior function, and so
// need behavior completions. Do this unless we're on the parenthesis
// (at which point we're only showing informative message about the
// function).
if (!isCaretOnParenthesis) {
completions.push_back(ExpressionCompletionDescription::ForBehavior(
node.functionName, node.objectName));
}
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, node.functionName, node.objectName)
.SetIsExact(isCaretOnParenthesis));
}
} else {
// Free function
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, node.functionName)
.SetIsExact(isCaretOnParenthesis));
}
}
void OnVisitEmptyNode(EmptyNode& node) override {
completions.push_back(
ExpressionCompletionDescription::ForObject(node.type, node.text));
completions.push_back(
ExpressionCompletionDescription::ForExpression(node.type, node.text));
}
private:
bool IsCaretOn(const ExpressionParserLocation& location,
bool inclusive = false) {
if (!location.IsValid()) return false;
return (location.GetStartPosition() <= searchedPosition &&
((!inclusive && searchedPosition < location.GetEndPosition()) ||
(inclusive && searchedPosition <= location.GetEndPosition())));
}
ExpressionCompletionFinder(size_t searchedPosition_)
: searchedPosition(searchedPosition_){};
std::vector<ExpressionCompletionDescription> completions;
size_t searchedPosition;
};
} // namespace gd
#endif // GDCORE_EXPRESSIONAUTOCOMPLETIONPROVIDER_H

View File

@@ -0,0 +1,126 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_EXPRESSIONNODELOCATIONFINDER_H
#define GDCORE_EXPRESSIONNODELOCATIONFINDER_H
#include <memory>
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
namespace gd {
class Expression;
class ObjectsContainer;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
} // namespace gd
namespace gd {
/**
* \brief Find the deepest node at the specified location in an expression.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionNodeLocationFinder
: public ExpressionParser2NodeWorker {
public:
/**
* \brief Initialize the finder to search at the specified position.
*/
ExpressionNodeLocationFinder(size_t searchedPosition_)
: searchedPosition(searchedPosition_), foundNode(nullptr){};
virtual ~ExpressionNodeLocationFinder(){};
/**
* \brief Helper function to find the deepest node at the search position, if
* any.
*/
static ExpressionNode* GetNodeAtPosition(gd::ExpressionNode& node,
size_t searchedPosition) {
gd::ExpressionNodeLocationFinder finder(searchedPosition);
node.Visit(finder);
return finder.GetNode();
}
/**
* \brief Return the deepest node found at the search position, if any.
*/
ExpressionNode* GetNode() { return foundNode; };
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
CheckSearchPositionInNode(node);
node.expression->Visit(*this);
}
void OnVisitOperatorNode(OperatorNode& node) override {
if (CheckSearchPositionInNode(node)) {
node.leftHandSide->Visit(*this);
node.rightHandSide->Visit(*this);
}
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
CheckSearchPositionInNode(node);
node.factor->Visit(*this);
}
void OnVisitNumberNode(NumberNode& node) override {
CheckSearchPositionInNode(node);
}
void OnVisitTextNode(TextNode& node) override {
CheckSearchPositionInNode(node);
}
void OnVisitVariableNode(VariableNode& node) override {
if (CheckSearchPositionInNode(node)) {
if (node.child) node.child->Visit(*this);
}
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
if (CheckSearchPositionInNode(node)) {
if (node.child) node.child->Visit(*this);
}
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
if (CheckSearchPositionInNode(node)) {
node.expression->Visit(*this);
if (node.child) node.child->Visit(*this);
}
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
CheckSearchPositionInNode(node);
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
CheckSearchPositionInNode(node);
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
CheckSearchPositionInNode(node);
for (auto& parameter : node.parameters) {
parameter->Visit(*this);
}
}
void OnVisitEmptyNode(EmptyNode& node) override {
CheckSearchPositionInNode(node, /*inclusive=*/true);
}
private:
bool CheckSearchPositionInNode(ExpressionNode& node, bool inclusive = false) {
if (node.location.GetStartPosition() <= searchedPosition &&
((!inclusive && searchedPosition < node.location.GetEndPosition()) ||
(inclusive && searchedPosition <= node.location.GetEndPosition()))) {
foundNode = &node;
return true;
}
return false;
}
size_t searchedPosition;
ExpressionNode* foundNode;
};
} // namespace gd
#endif // GDCORE_EXPRESSIONNODELOCATIONFINDER_H

View File

@@ -83,7 +83,10 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
void OnVisitIdentifierNode(IdentifierNode& node) override {
ReportAnyError(node);
}
void OnVisitFunctionNode(FunctionNode& node) override {
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
ReportAnyError(node);
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
ReportAnyError(node);
for (auto& parameter : node.parameters) {
parameter->Visit(*this);

View File

@@ -1,59 +0,0 @@
/*
* 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/ExpressionsCorrectnessTesting.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Expression.h"
#include "GDCore/Events/Parsers/ExpressionParser.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
namespace gd {
CallbacksForExpressionCorrectnessTesting::
CallbacksForExpressionCorrectnessTesting(const gd::ObjectsContainer& project_,
const gd::ObjectsContainer& layout_)
: project(project_), layout(layout_) {}
bool CallbacksForExpressionCorrectnessTesting::OnSubMathExpression(
const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::Expression& expression) {
CallbacksForExpressionCorrectnessTesting callbacks(project, layout);
gd::ExpressionParser parser(expression.GetPlainString());
if (!parser.ParseMathExpression(platform, project, layout, callbacks)) {
#if defined(GD_IDE_ONLY)
firstErrorStr = callbacks.GetFirstError();
firstErrorPos = callbacks.GetFirstErrorPosition();
#endif
return false;
}
return true;
}
bool CallbacksForExpressionCorrectnessTesting::OnSubTextExpression(
const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::Expression& expression) {
CallbacksForExpressionCorrectnessTesting callbacks(project, layout);
gd::ExpressionParser parser(expression.GetPlainString());
if (!parser.ParseStringExpression(platform, project, layout, callbacks)) {
#if defined(GD_IDE_ONLY)
firstErrorStr = callbacks.GetFirstError();
firstErrorPos = callbacks.GetFirstErrorPosition();
#endif
return false;
}
return true;
}
} // namespace gd

View File

@@ -1,75 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_EXPRESSIONSCORRECTNESSTESTING_H
#define GDCORE_EXPRESSIONSCORRECTNESSTESTING_H
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser.h"
#include "GDCore/String.h"
namespace gd {
class ExpressionMetadata;
class Expression;
class Project;
class Layout;
}
namespace gd {
// TODO: Replace and remove (ExpressionValidator)
/**
* \brief Parser callbacks used to check expressions correctness
*
* Usage example:
* \code
* gd::CallbacksForExpressionCorrectnessTesting callbacks(game, scene);
* gd::ExpressionParser expressionParser(expression);
* if ( !expressionParser.ParseMathExpression(game, scene, callbacks) )
* //Expression is not valid
* else
* //Expression is correct
* \endcode
*
* \see gd::ExpressionParser
* \see gd::ParserCallbacks
*
* \ingroup IDE
*/
class GD_CORE_API CallbacksForExpressionCorrectnessTesting
: public gd::ParserCallbacks {
public:
CallbacksForExpressionCorrectnessTesting(const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout);
virtual ~CallbacksForExpressionCorrectnessTesting(){};
void OnConstantToken(gd::String text){};
void OnStaticFunction(gd::String functionName,
const std::vector<gd::Expression>& parameters,
const gd::ExpressionMetadata& expressionInfo){};
void OnObjectFunction(gd::String functionName,
const std::vector<gd::Expression>& parameters,
const gd::ExpressionMetadata& expressionInfo){};
void OnObjectBehaviorFunction(gd::String functionName,
const std::vector<gd::Expression>& parameters,
const gd::ExpressionMetadata& expressionInfo){};
bool OnSubMathExpression(const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::Expression& expression);
bool OnSubTextExpression(const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::Expression& expression);
private:
const gd::ObjectsContainer& project;
const gd::ObjectsContainer& layout;
};
} // namespace gd
#endif // GDCORE_EXPRESSIONSCORRECTNESSTESTING_H

View File

@@ -74,7 +74,8 @@ class GD_CORE_API ExpressionParameterMover
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {}
void OnVisitFunctionNode(FunctionNode& node) override {
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
auto moveParameter =
[this](std::vector<std::unique_ptr<gd::ExpressionNode>>& parameters) {
if (oldIndex >= parameters.size() || newIndex >= parameters.size())
@@ -87,7 +88,8 @@ class GD_CORE_API ExpressionParameterMover
};
if (node.functionName == functionName) {
if (!objectType.empty() && !node.objectName.empty()) {
if (behaviorType.empty() && !objectType.empty() &&
!node.objectName.empty()) {
// Move parameter of an object function
const gd::String& thisObjectType = gd::GetTypeOfObject(
globalObjectsContainer, objectsContainer, node.objectName);
@@ -103,7 +105,7 @@ class GD_CORE_API ExpressionParameterMover
moveParameter(node.parameters);
hasDoneMoving = true;
}
} else {
} else if (behaviorType.empty() && objectType.empty()) {
// Move parameter of a free function
moveParameter(node.parameters);
hasDoneMoving = true;
@@ -119,10 +121,12 @@ class GD_CORE_API ExpressionParameterMover
bool hasDoneMoving;
const gd::ObjectsContainer& globalObjectsContainer;
const gd::ObjectsContainer& objectsContainer;
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
// must be replaced (optional)
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
// must have a parameter moved (optional). If
// `behaviorType` is not empty, it takes
// precedence over `objectType`.
const gd::String& functionName;
std::size_t oldIndex;
std::size_t newIndex;

View File

@@ -71,13 +71,41 @@ class GD_CORE_API ExpressionFunctionRenamer
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {}
void OnVisitFunctionNode(FunctionNode& node) override {
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);
if (thisBehaviorType == behaviorType) {
node.behaviorFunctionName = newFunctionName;
hasDoneRenaming = true;
}
}
} else {
// Object function name
if (behaviorType.empty() && !objectType.empty() &&
node.objectFunctionOrBehaviorName == oldFunctionName) {
const gd::String& thisObjectType = gd::GetTypeOfObject(
globalObjectsContainer, objectsContainer, node.objectName);
if (thisObjectType == objectType) {
node.objectFunctionOrBehaviorName = newFunctionName;
hasDoneRenaming = true;
}
}
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (node.functionName == oldFunctionName) {
if (!objectType.empty() && !node.objectName.empty()) {
if (behaviorType.empty() && !objectType.empty() &&
!node.objectName.empty()) {
// Replace an object function
const gd::String& thisObjectType = gd::GetTypeOfObject(
globalObjectsContainer, objectsContainer, node.objectName);
if (thisObjectType == behaviorType) {
if (thisObjectType == objectType) {
node.functionName = newFunctionName;
hasDoneRenaming = true;
}
@@ -89,7 +117,7 @@ class GD_CORE_API ExpressionFunctionRenamer
node.functionName = newFunctionName;
hasDoneRenaming = true;
}
} else {
} else if (behaviorType.empty() && objectType.empty()) {
// Replace a free function
node.functionName = newFunctionName;
hasDoneRenaming = true;
@@ -106,9 +134,11 @@ class GD_CORE_API ExpressionFunctionRenamer
const gd::ObjectsContainer& globalObjectsContainer;
const gd::ObjectsContainer& objectsContainer;
const gd::String& behaviorType; // The behavior type for which the expression
// must be replaced (optional)
// must be replaced (optional).
const gd::String& objectType; // The object type for which the expression
// must be replaced (optional)
// must be replaced (optional). If
// `behaviorType` is not empty, it takes
// precedence over `objectType`.
const gd::String& oldFunctionName;
const gd::String& newFunctionName;
};

View File

@@ -10,34 +10,49 @@
namespace gd {
gd::String SceneNameMangler::GetMangledSceneName(gd::String sceneName) {
static const gd::String allowedCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const gd::String allowedExceptFirst = "0123456789";
SceneNameMangler *SceneNameMangler::_singleton = nullptr;
std::size_t i = 0;
for (auto it = sceneName.begin(); it != sceneName.end(); ++it) {
char32_t character = *it;
if (allowedCharacters.find(character) == gd::String::npos &&
(allowedExceptFirst.find(character) == gd::String::npos ||
i == 0)) // Also disallow some characters to be in first position
{
// Replace the character by an underscore and its unicode codepoint (in
// base 10)
auto it2 = it;
++it2;
sceneName.replace(it, it2, "_" + gd::String::From(character));
// The iterator it may have been invalidated:
// re-assign it with a new iterator pointing to the same position.
it = sceneName.begin();
std::advance(it, i);
}
++i;
const gd::String &SceneNameMangler::GetMangledSceneName(
const gd::String &sceneName) {
auto it = mangledSceneNames.find(sceneName);
if (it != mangledSceneNames.end()) {
return it->second;
}
return sceneName;
gd::String partiallyMangledName = sceneName;
static const gd::String alwaysAllowedCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const gd::String allowedExceptFirstCharacters = "0123456789";
for (size_t i = 0; i < partiallyMangledName.size();
++i) // Replace all unallowed letter by an underscore and the unicode
// code point of the letter
{
if (alwaysAllowedCharacters.find_first_of(
std::u32string(1, partiallyMangledName[i])) == gd::String::npos &&
(i == 0 ||
allowedExceptFirstCharacters.find(
std::u32string(1, partiallyMangledName[i])) == gd::String::npos)) {
char32_t unallowedChar = partiallyMangledName[i];
partiallyMangledName.replace(i, 1, "_" + gd::String::From(unallowedChar));
}
}
mangledSceneNames[sceneName] = partiallyMangledName;
return mangledSceneNames[sceneName];
}
SceneNameMangler *SceneNameMangler::Get() {
if (nullptr == _singleton) _singleton = new SceneNameMangler;
return (static_cast<SceneNameMangler *>(_singleton));
}
void SceneNameMangler::DestroySingleton() {
if (nullptr != _singleton) {
delete _singleton;
_singleton = nullptr;
}
}
} // namespace gd

View File

@@ -6,12 +6,14 @@
#ifndef SCENENAMEMANGLER_H
#define SCENENAMEMANGLER_H
#include <unordered_map>
#include "GDCore/String.h"
namespace gd {
/**
* \brief Used to mangle the name of a scene
* \brief Mangle the name of a scene, so that it can be used in code or file
* names.
*
* \ingroup IDE
*/
@@ -21,12 +23,22 @@ class GD_CORE_API SceneNameMangler {
* \brief Mangle the name of a scene, replacing all characters that are not
* 0-9, a-z or A-Z by "_"+UnicodeCodePointOfTheCharacter. The first character
* must be a letter, otherwise it is also replaced in the same manner.
*
* The mangled name is memoized as this is intensively used during project
* export and events code generation.
*/
static gd::String GetMangledSceneName(gd::String sceneName);
const gd::String& GetMangledSceneName(const gd::String& sceneName);
static SceneNameMangler* Get();
static void DestroySingleton();
private:
SceneNameMangler(){};
virtual ~SceneNameMangler(){};
static SceneNameMangler* _singleton;
std::unordered_map<gd::String, gd::String>
mangledSceneNames; ///< Memoized results of mangling
};
} // namespace gd

View File

@@ -470,7 +470,17 @@ void WholeProjectRefactorer::RenameBehaviorProperty(
auto& properties = eventsBasedBehavior.GetPropertyDescriptors();
if (!properties.Has(oldPropertyName)) return;
const auto& property = properties.Get(oldPropertyName);
// Order is important: we first rename the expressions then the instructions,
// to avoid being unable to fetch the metadata (the types of parameters) of
// instructions after they are renamed.
gd::ExpressionsRenamer expressionRenamer =
gd::ExpressionsRenamer(project.GetCurrentPlatform());
expressionRenamer.SetReplacedBehaviorExpression(
GetBehaviorFullType(eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName()),
EventsBasedBehavior::GetPropertyExpressionName(oldPropertyName),
EventsBasedBehavior::GetPropertyExpressionName(newPropertyName));
ExposeProjectEvents(project, expressionRenamer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
@@ -495,15 +505,6 @@ void WholeProjectRefactorer::RenameBehaviorProperty(
eventsBasedBehavior.GetName(),
EventsBasedBehavior::GetPropertyConditionName(newPropertyName)));
ExposeProjectEvents(project, conditionRenamer);
gd::ExpressionsRenamer expressionRenamer =
gd::ExpressionsRenamer(project.GetCurrentPlatform());
expressionRenamer.SetReplacedBehaviorExpression(
GetBehaviorFullType(eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName()),
EventsBasedBehavior::GetPropertyExpressionName(oldPropertyName),
EventsBasedBehavior::GetPropertyExpressionName(newPropertyName));
ExposeProjectEvents(project, expressionRenamer);
}
void WholeProjectRefactorer::RenameEventsBasedBehavior(

View File

@@ -50,7 +50,10 @@ class GD_CORE_API WholeProjectRefactorer {
gd::ArbitraryEventsWorkerWithContext& worker);
/**
* \brief Refactor the project after an events function extension is renamed
* \brief Refactor the project **before** an events function extension is renamed.
*
* \warning Do the renaming of the specified extension after calling this.
* This is because the extension is expected to have its old name for the refactoring.
*/
static void RenameEventsFunctionsExtension(
gd::Project& project,
@@ -59,7 +62,10 @@ class GD_CORE_API WholeProjectRefactorer {
const gd::String& newName);
/**
* \brief Refactor the project after an events function is renamed
* \brief Refactor the project **before** an events function is renamed.
*
* \warning Do the renaming of the specified function after calling this.
* This is because the function is expected to have its old name for the refactoring.
*/
static void RenameEventsFunction(
gd::Project& project,
@@ -68,8 +74,11 @@ class GD_CORE_API WholeProjectRefactorer {
const gd::String& newFunctionName);
/**
* \brief Refactor the project after an events function of a behavior is
* \brief Refactor the project **before** an events function of a behavior is
* renamed.
*
* \warning Do the renaming of the specified function after calling this.
* This is because the function is expected to have its old name for the refactoring.
*/
static void RenameBehaviorEventsFunction(
gd::Project& project,
@@ -79,8 +88,11 @@ class GD_CORE_API WholeProjectRefactorer {
const gd::String& newFunctionName);
/**
* \brief Refactor the project after an events function parameter
* was moved.
* \brief Refactor the project **before** an events function parameter
* is moved.
*
* \warning Do the move of the specified function parameters after calling this.
* This is because the function is expected to be in its old state for the refactoring.
*/
static void MoveEventsFunctionParameter(
gd::Project& project,
@@ -90,8 +102,11 @@ class GD_CORE_API WholeProjectRefactorer {
std::size_t newIndex);
/**
* \brief Refactor the project after the parmaeter of an events function of a
* behavior was moved.
* \brief Refactor the project **before** the parameter of an events function of a
* behavior is moved.
*
* \warning Do the move of the specified function parameters after calling this.
* This is because the function is expected to be in its old state for the refactoring.
*/
static void MoveBehaviorEventsFunctionParameter(
gd::Project& project,
@@ -102,8 +117,11 @@ class GD_CORE_API WholeProjectRefactorer {
std::size_t newIndex);
/**
* \brief Refactor the project after a property of a behavior is
* \brief Refactor the project **before** a property of a behavior is
* renamed.
*
* \warning Do the renaming of the specified property after calling this.
* This is because the property is expected to have its old name for the refactoring.
*/
static void RenameBehaviorProperty(
gd::Project& project,
@@ -113,7 +131,10 @@ class GD_CORE_API WholeProjectRefactorer {
const gd::String& newPropertyName);
/**
* \brief Refactor the project after a behavior is renamed.
* \brief Refactor the project **before** a behavior is renamed.
*
* \warning Do the renaming of the specified behavior after calling this.
* This is because the behavior is expected to have its old name for the refactoring.
*/
static void RenameEventsBasedBehavior(
gd::Project& project,

View File

@@ -70,6 +70,19 @@ void EventsFunctionsExtension::UnserializeFrom(
"eventsBasedBehavior", project, element.GetChild("eventsBasedBehaviors"));
}
bool EventsFunctionsExtension::IsExtensionLifecycleEventsFunction(
const gd::String& eventsFunctionName) {
// The list of all supported lifecycle events function names.
// If adding a new one, code generator(s) must be updated.
return eventsFunctionName == "onFirstSceneLoaded" ||
eventsFunctionName == "onSceneLoaded" ||
eventsFunctionName == "onScenePreEvents" ||
eventsFunctionName == "onScenePostEvents" ||
eventsFunctionName == "onScenePaused" ||
eventsFunctionName == "onSceneResumed" ||
eventsFunctionName == "onSceneUnloading";
}
} // namespace gd
#endif

View File

@@ -8,8 +8,8 @@
#define GDCORE_EVENTSFUNCTIONEXTENSION_H
#include <vector>
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/Project/EventsBasedBehavior.h"
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/String.h"
#include "GDCore/Tools/SerializableWithNameList.h"
namespace gd {
@@ -59,7 +59,8 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
}
const gd::String& GetShortDescription() const { return shortDescription; };
EventsFunctionsExtension& SetShortDescription(const gd::String& shortDescription_) {
EventsFunctionsExtension& SetShortDescription(
const gd::String& shortDescription_) {
shortDescription = shortDescription_;
return *this;
}
@@ -97,15 +98,15 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
/**
* \brief Return a reference to the list of the events based behaviors.
*/
SerializableWithNameList<EventsBasedBehavior>& GetEventsBasedBehaviors() {
gd::SerializableWithNameList<EventsBasedBehavior>& GetEventsBasedBehaviors() {
return eventsBasedBehaviors;
}
/**
* \brief Return a const reference to the list of the events based behaviors.
*/
const SerializableWithNameList<EventsBasedBehavior>& GetEventsBasedBehaviors()
const {
const gd::SerializableWithNameList<EventsBasedBehavior>&
GetEventsBasedBehaviors() const {
return eventsBasedBehaviors;
}
@@ -124,6 +125,12 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
const gd::SerializerElement& element);
///@}
/** \name Lifecycle event functions
*/
///@{
static bool IsExtensionLifecycleEventsFunction(const gd::String& eventsFunctionName);
///@}
private:
/**
* Initialize object using another object. Used by copy-ctor and assign-op.
@@ -139,7 +146,7 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
gd::String fullName;
gd::String tags;
gd::String author;
SerializableWithNameList<EventsBasedBehavior> eventsBasedBehaviors;
gd::SerializableWithNameList<EventsBasedBehavior> eventsBasedBehaviors;
};
} // namespace gd

View File

@@ -63,7 +63,7 @@ Layout::Layout()
void Layout::SetName(const gd::String& name_) {
name = name_;
mangledName = gd::SceneNameMangler::GetMangledSceneName(name);
mangledName = gd::SceneNameMangler::Get()->GetMangledSceneName(name);
};
bool Layout::HasBehaviorSharedData(const gd::String& behaviorName) {

View File

@@ -7,6 +7,7 @@
#include "Project.h"
#include <stdio.h>
#include <stdlib.h>
#include <cctype>
#include <SFML/System/Utf.hpp>
#include <fstream>
#include <map>
@@ -945,18 +946,16 @@ void Project::SerializeTo(SerializerElement& element) const {
#endif
}
bool Project::ValidateObjectName(const gd::String& name) {
bool Project::ValidateName(const gd::String& name) {
if (name.empty()) return false;
if (isdigit(name[0])) return false;
gd::String allowedCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
return !(name.find_first_not_of(allowedCharacters) != gd::String::npos);
}
gd::String Project::GetBadObjectNameWarning() {
return _("Please use only letters, digits\nand underscores ( _ ).");
}
void Project::ExposeResources(gd::ArbitraryResourceWorker& worker) {
// See also gd::WholeProjectRefactorer::ExposeProjectEvents for a method that
// traverse the whole project (this time for events) and ExposeProjectEffects

View File

@@ -843,21 +843,10 @@ class GD_CORE_API Project : public ObjectsContainer {
///@{
/**
* Return true if \a objectName can be used as name for an object.
*
* Default implementation check if objectName is only composed of a-z,A-Z,0-9
* or _ characters an if does not conflict with an expression.
* Return true if \a name is valid (can be used safely for an object,
* behavior, events function name, etc...).
*/
static bool ValidateObjectName(const gd::String& objectName);
/**
* Return a message that will be displayed when an invalid object name has
* been entered.
*
* \note This message will be displayed by the IDE into a tooltip.
*/
static gd::String GetBadObjectNameWarning();
static bool ValidateName(const gd::String& name);
///@}
/** \name External source files

View File

@@ -95,6 +95,14 @@ void Serializer::FromXML(SerializerElement& element,
}
#endif
gd::String Serializer::ToEscapedXMLString(const gd::String& str) {
return str.FindAndReplace("&", "&amp;")
.FindAndReplace("'", "&apos;")
.FindAndReplace("\"", "&quot;")
.FindAndReplace("<", "&lt;")
.FindAndReplace(">", "&gt;");
}
namespace {
/**

View File

@@ -27,14 +27,26 @@ class GD_CORE_API Serializer {
static void FromXML(SerializerElement& element,
const TiXmlElement* xmlElement);
#endif
/**
* \brief Escape a string for inclusion in a XML tag
*/
static gd::String ToEscapedXMLString(const gd::String& str);
///@}
/** \name JSON serialization.
* Serialize a SerializerElement from/to JSON.
*/
///@{
/**
* \brief Serialize a gd::SerializerElement to a JSON string.
*/
static gd::String ToJSON(const SerializerElement& element);
static SerializerElement FromJSON(const std::string& json);
/**
* \brief Parse a JSON string and returns a gd::SerializerElement for it.
*/
static SerializerElement FromJSON(const gd::String& json) {
return FromJSON(json.ToUTF8());
}

View File

@@ -25,11 +25,11 @@ gd::String GetTranslation(const char* str) { // TODO: Inline?
// }
ensureCache.prepare();
var translatedStr = getTranslation(Pointer_stringify($0));
var translatedStr = getTranslation(UTF8ToString($0));
return ensureString(translatedStr);
},
str);
return gd::String(translatedStr); // TODO: Is copying necessary?
}
} // namespace gd
#endif
#endif

View File

@@ -14,6 +14,9 @@
void SetupProjectWithDummyPlatform(gd::Project &project,
gd::Platform &platform) {
// Don't show extension loading logs for tests (too verbose).
platform.EnableExtensionLoadingLogs(false);
std::shared_ptr<gd::PlatformExtension> baseObjectExtension =
std::shared_ptr<gd::PlatformExtension>(new gd::PlatformExtension);
baseObjectExtension->SetExtensionInformation(

View File

@@ -0,0 +1,307 @@
/*
* 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/ExpressionCompletionFinder.h"
#include "DummyPlatform.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "catch.hpp"
TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto& layout1 = project.InsertNewLayout("Layout1", 0);
gd::ExpressionParser2 parser(platform, project, layout1);
auto getCompletionsFor = [&](
const gd::String& type, const gd::String& expression, size_t location) {
auto node = parser.ParseExpression(type, expression);
REQUIRE(node != nullptr);
return gd::ExpressionCompletionFinder::GetCompletionDescriptionsFor(
*node, location);
};
const std::vector<gd::ExpressionCompletionDescription>
expectedEmptyCompletions;
SECTION("Identifier") {
SECTION("Object or expression completions when type is string") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("string", "My"),
gd::ExpressionCompletionDescription::ForExpression("string", "My")};
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"),
gd::ExpressionCompletionDescription::ForExpression("number", "My")};
REQUIRE(getCompletionsFor("number", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "My", 2) == expectedEmptyCompletions);
}
SECTION("Object when type is an object") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("object", "My")};
REQUIRE(getCompletionsFor("object", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("object", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("object", "My", 2) == expectedEmptyCompletions);
}
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")};
REQUIRE(getCompletionsFor("objectPtr", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("objectPtr", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("objectPtr", "My", 2) ==
expectedEmptyCompletions);
}
}
SECTION("Operator (number)") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("number", ""),
gd::ExpressionCompletionDescription::ForExpression("number", "")};
REQUIRE(getCompletionsFor("number", "1 + ", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "1 + ", 2) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "1 + ", 3) == expectedCompletions);
}
SECTION("Operator (string)") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("string", ""),
gd::ExpressionCompletionDescription::ForExpression("string", "")};
REQUIRE(getCompletionsFor("string", "\"a\" + ", 3) == expectedCompletions);
REQUIRE(getCompletionsFor("string", "\"a\" + ", 4) == expectedCompletions);
REQUIRE(getCompletionsFor("string", "\"a\" + ", 5) == expectedCompletions);
}
SECTION("Free function") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForExpression("string",
"Function")};
std::vector<gd::ExpressionCompletionDescription>
expectedExactCompletions{
gd::ExpressionCompletionDescription::ForExpression("string",
"Function")
.SetIsExact(true)};
REQUIRE(getCompletionsFor("string", "Function(", 0) ==
expectedCompletions);
REQUIRE(getCompletionsFor("string", "Function(", 1) ==
expectedCompletions);
REQUIRE(getCompletionsFor("string", "Function(", 7) ==
expectedCompletions);
REQUIRE(getCompletionsFor("string", "Function(", 8) ==
expectedExactCompletions);
REQUIRE(getCompletionsFor("string", "Function(", 9) ==
expectedEmptyCompletions);
REQUIRE(getCompletionsFor("string", "Function()", 9) ==
expectedExactCompletions);
}
SECTION("Unknown function, test with arguments") {
REQUIRE(getCompletionsFor("string", "Function(1", 9) ==
expectedEmptyCompletions);
REQUIRE(getCompletionsFor("string", "Function(\"", 9) ==
expectedEmptyCompletions);
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("unknown", "a"),
gd::ExpressionCompletionDescription::ForExpression("unknown", "a")};
REQUIRE(getCompletionsFor("string", "Function(a", 9) ==
expectedCompletions);
}
SECTION("Function with a Variable as argument") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForVariable("scenevar",
"myVar")};
REQUIRE(getCompletionsFor("number",
"MyExtension::GetVariableAsNumber(myVar",
33) == expectedCompletions);
}
}
SECTION("Partial object or behavior function") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject("string",
"MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehavior("Func",
"MyObject"),
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", "MyObject")};
REQUIRE(getCompletionsFor("string", "MyObject.Func", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func", 7) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func", 8) ==
expectedBehaviorOrFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func", 9) ==
expectedBehaviorOrFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func", 12) ==
expectedBehaviorOrFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func", 13) ==
expectedEmptyCompletions);
}
}
SECTION("Object function") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject("string",
"MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehavior("Func",
"MyObject"),
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", "MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedExactFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", "MyObject")
.SetIsExact(true)};
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 7) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 8) ==
expectedBehaviorOrFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 9) ==
expectedBehaviorOrFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 12) ==
expectedBehaviorOrFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 13) ==
expectedExactFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 14) ==
expectedEmptyCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func()", 14) ==
expectedExactFunctionCompletions);
}
}
SECTION("Partial behavior function") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject("string",
"MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehavior("MyBehavior",
"MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", "MyObject", "MyBehavior")};
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 7) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 8) ==
expectedBehaviorCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 9) ==
expectedBehaviorCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 18) ==
expectedBehaviorCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 19) ==
expectedFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 20) ==
expectedFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 21) ==
expectedFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 23) ==
expectedFunctionCompletions);
}
SECTION("Test 2") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject("string",
"MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehavior("MyBehavior",
"MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "", "MyObject", "MyBehavior")};
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::", 7) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::", 8) ==
expectedBehaviorCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::", 9) ==
expectedBehaviorCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::", 18) ==
expectedBehaviorCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::", 19) ==
expectedFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::", 20) ==
expectedFunctionCompletions);
}
}
SECTION("Behavior function") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject("string",
"MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehavior("MyBehavior",
"MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", "MyObject", "MyBehavior")};
std::vector<gd::ExpressionCompletionDescription>
expectedExactFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", "MyObject", "MyBehavior")
.SetIsExact(true)};
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 7) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 8) ==
expectedBehaviorCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 9) ==
expectedBehaviorCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 18) ==
expectedBehaviorCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 19) ==
expectedFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 20) ==
expectedFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 21) ==
expectedFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 22) ==
expectedFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 23) ==
expectedFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 24) ==
expectedFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 25) ==
expectedExactFunctionCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 26) ==
expectedEmptyCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func()", 26) ==
expectedExactFunctionCompletions);
}
}
}

View File

@@ -0,0 +1,382 @@
/*
* 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/ExpressionNodeLocationFinder.h"
#include "DummyPlatform.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "catch.hpp"
template <class TNode>
bool CheckNodeAtLocationIs(gd::ExpressionParser2& parser,
const gd::String& type,
const gd::String& expression,
size_t searchPosition) {
auto node = parser.ParseExpression(type, expression);
REQUIRE(node != nullptr);
return dynamic_cast<TNode*>(
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(
*node, searchPosition)) != nullptr;
}
bool CheckNoNodeAtLocation(gd::ExpressionParser2& parser,
const gd::String& type,
const gd::String& expression,
size_t searchPosition) {
auto node = parser.ParseExpression(type, expression);
REQUIRE(node != nullptr);
return gd::ExpressionNodeLocationFinder::GetNodeAtPosition(
*node, searchPosition) == nullptr;
}
TEST_CASE("ExpressionNodeLocationFinder", "[common][events]") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto& layout1 = project.InsertNewLayout("Layout1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
gd::ExpressionParser2 parser(platform, project, layout1);
SECTION("Empty expressions") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::EmptyNode>(
parser, "string", "", 0) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "string", "", 1) ==
true);
}
SECTION("Test 2") {
REQUIRE(CheckNoNodeAtLocation(parser, "string", " ", 0) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::EmptyNode>(
parser, "string", " ", 1) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "string", " ", 2) ==
true);
}
}
SECTION("Valid text") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello world\"", 0) == true);
}
SECTION("Test 2") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello world\"", 1) == true);
}
SECTION("Test 3") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello world\"", 12) == true);
}
SECTION("Test 4") {
REQUIRE(CheckNoNodeAtLocation(parser, "string", "\"Hello world\"", 13) ==
true);
}
SECTION("Test 5") {
REQUIRE(CheckNoNodeAtLocation(parser, "string", "\"Hello world\"", 99) ==
true);
}
}
SECTION("Valid text operators") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello \" + \"World\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "string", "\"Hello \" + \"World\"", 8) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello \" + \"World\"", 15) == true);
}
}
SECTION("Invalid texts") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(parser, "string", "\"", 0) ==
true);
REQUIRE(CheckNoNodeAtLocation(parser, "string", "\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(parser, "string", "\"a", 1) ==
true);
}
SECTION("Invalid parenthesis") {
REQUIRE(CheckNodeAtLocationIs<gd::SubExpressionNode>(
parser, "string", "((\"hello\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "((\"hello\"", 2) == true);
}
SECTION("Invalid text operators") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello \" - \"World\"", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello \" - \"World\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "string", "\"Hello \" / \"World\"", 8) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello \" * \"World\"", 15) == true);
}
}
SECTION("Valid unary operators") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::UnaryOperatorNode>(
parser, "number", "-123", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::UnaryOperatorNode>(
parser, "number", "+123", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "-123", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "-123", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "-123", 3) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "number", "-123", 4) == true);
}
SECTION("Test 2") {
REQUIRE(CheckNodeAtLocationIs<gd::UnaryOperatorNode>(
parser, "number", "-+-123", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::UnaryOperatorNode>(
parser, "number", "-+-123", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::UnaryOperatorNode>(
parser, "number", "-+-123", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "-+-123", 3) == true);
}
}
SECTION("Invalid number operators") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 ! 34", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 ! 34", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 ! 34", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 ! 34", 3) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 ! 34", 4) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 ! 34", 5) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 ! 34", 6) == true);
}
SECTION("Test 2") {
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "1 / /2", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "1 / /2", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "1 / /2", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "1 / /2", 3) == true);
REQUIRE(CheckNodeAtLocationIs<gd::EmptyNode>(
parser, "number", "1 / /2", 4) == true);
REQUIRE(CheckNodeAtLocationIs<gd::EmptyNode>(
parser, "number", "1 / /2", 5) == true);
}
}
SECTION("Numbers and texts mismatchs") {
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(parser, "number", "12+\"hello\"", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(parser, "number", "12+\"hello\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(parser, "number", "12+\"hello\"", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(parser, "number", "12+\"hello\"", 3) == true);
}
SECTION("Valid objects") {
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "HelloWorld1", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "HelloWorld1", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "HelloWorld1", 10) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "object", "HelloWorld1", 11) == true);
}
SECTION("Invalid objects") {
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "a+b", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "object", "a+b", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "a+b", 2) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "object", "a+b", 3) == true);
}
SECTION("Valid function calls") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 + MyExtension::GetNumber()", 0) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 + MyExtension::GetNumber()", 1) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 + MyExtension::GetNumber()", 2) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 + MyExtension::GetNumber()", 3) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 + MyExtension::GetNumber()", 4) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "12 + MyExtension::GetNumber()", 5) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "12 + MyExtension::GetNumber()", 27) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "12 + MyExtension::GetNumber()", 28) ==
true);
REQUIRE(CheckNoNodeAtLocation(
parser, "number", "12 + MyExtension::GetNumber()", 29) ==
true);
}
SECTION("Test 2") {
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
33) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
34) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
35) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
36) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
37) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
38) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
39) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
50) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
51) == true);
REQUIRE(CheckNoNodeAtLocation(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
52) == true);
}
}
SECTION("Invalid function calls") {
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 10) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "Idontexist(12)", 11) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "Idontexist(12)", 12) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 13) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "number", "Idontexist(12)", 14) ==
true);
}
SECTION("Unterminated function calls") {
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(", 10) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "number", "Idontexist(", 11) == true);
}
SECTION("Valid variables") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::VariableNode>(
parser, "scenevar", "myVariable", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::VariableNode>(
parser, "scenevar", "myVariable", 9) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "scenevar", "myVariable", 10) ==
true);
}
SECTION("Test 2") {
auto node = parser.ParseExpression("scenevar", "Var1.Child1");
REQUIRE(node != nullptr);
auto var1Node =
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(*node, 0);
REQUIRE(dynamic_cast<gd::VariableNode*>(var1Node) != nullptr);
REQUIRE(dynamic_cast<gd::VariableNode&>(*var1Node).name == "Var1");
auto child1Node =
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(*node, 4);
REQUIRE(dynamic_cast<gd::VariableAccessorNode*>(child1Node) != nullptr);
REQUIRE(dynamic_cast<gd::VariableAccessorNode&>(*child1Node).name ==
"Child1");
}
SECTION("Test 3") {
auto node = parser.ParseExpression(
"scenevar", "myVariable[ \"My named children\" ].grandChild");
REQUIRE(node != nullptr);
auto myVariableNode =
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(*node, 0);
REQUIRE(dynamic_cast<gd::VariableNode*>(myVariableNode) != nullptr);
REQUIRE(dynamic_cast<gd::VariableNode&>(*myVariableNode).name ==
"myVariable");
auto variableBracketAccessorNode =
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(*node, 10);
REQUIRE(dynamic_cast<gd::VariableBracketAccessorNode*>(
variableBracketAccessorNode) != nullptr);
auto textNode =
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(*node, 15);
REQUIRE(dynamic_cast<gd::TextNode*>(textNode) != nullptr);
auto grandChildNode =
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(*node, 40);
REQUIRE(dynamic_cast<gd::VariableAccessorNode*>(grandChildNode) !=
nullptr);
REQUIRE(dynamic_cast<gd::VariableAccessorNode&>(*grandChildNode).name ==
"grandChild");
}
}
}

View File

@@ -21,6 +21,51 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
gd::ExpressionParser2 parser(platform, project, layout1);
SECTION("Empty expression") {
{
auto node = parser.ParseExpression("string", "");
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
REQUIRE(emptyNode.type == "string");
REQUIRE(emptyNode.text == "");
}
{
auto node = parser.ParseExpression("number", "");
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
REQUIRE(emptyNode.type == "number");
REQUIRE(emptyNode.text == "");
}
{
auto node = parser.ParseExpression("object", "");
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
REQUIRE(emptyNode.type == "object");
REQUIRE(emptyNode.text == "");
}
{
auto node = parser.ParseExpression("string", " ");
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
REQUIRE(emptyNode.type == "string");
REQUIRE(emptyNode.text == "");
}
{
auto node = parser.ParseExpression("number", " ");
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
REQUIRE(emptyNode.type == "number");
REQUIRE(emptyNode.text == "");
}
{
auto node = parser.ParseExpression("object", " ");
REQUIRE(node != nullptr);
auto &emptyNode = dynamic_cast<gd::EmptyNode &>(*node);
REQUIRE(emptyNode.type == "object");
REQUIRE(emptyNode.text == "");
}
}
SECTION("Valid texts") {
{
auto node = parser.ParseExpression("string", "\"hello world\"");
@@ -50,7 +95,8 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(textNode.text == "\\");
}
{
auto node = parser.ParseExpression("string", "\"hello \\\\\\\"world\\\"\"");
auto node =
parser.ParseExpression("string", "\"hello \\\\\\\"world\\\"\"");
REQUIRE(node != nullptr);
auto &textNode = dynamic_cast<gd::TextNode &>(*node);
REQUIRE(textNode.text == "hello \\\"world\"");
@@ -280,12 +326,42 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
}
SECTION("valid operators") {
{
auto node = parser.ParseExpression("number", "123 + 456");
REQUIRE(node != nullptr);
auto &operatorNode = dynamic_cast<gd::OperatorNode &>(*node);
REQUIRE(operatorNode.op == '+');
REQUIRE(operatorNode.type == "number");
auto &leftNumberNode =
dynamic_cast<gd::NumberNode &>(*operatorNode.leftHandSide);
REQUIRE(leftNumberNode.number == "123");
auto &rightNumberNode =
dynamic_cast<gd::NumberNode &>(*operatorNode.rightHandSide);
REQUIRE(rightNumberNode.number == "456");
}
{
auto node = parser.ParseExpression("string", "\"abc\" + \"def\"");
REQUIRE(node != nullptr);
auto &operatorNode = dynamic_cast<gd::OperatorNode &>(*node);
REQUIRE(operatorNode.op == '+');
REQUIRE(operatorNode.type == "string");
auto &leftTextNode =
dynamic_cast<gd::TextNode &>(*operatorNode.leftHandSide);
REQUIRE(leftTextNode.text == "abc");
auto &rightTextNode =
dynamic_cast<gd::TextNode &>(*operatorNode.rightHandSide);
REQUIRE(rightTextNode.text == "def");
}
}
SECTION("valid unary operators") {
{
auto node = parser.ParseExpression("number", "-123");
REQUIRE(node != nullptr);
auto &unaryOperatorNode = dynamic_cast<gd::UnaryOperatorNode &>(*node);
REQUIRE(unaryOperatorNode.op == '-');
REQUIRE(unaryOperatorNode.type == "number");
auto &numberNode =
dynamic_cast<gd::NumberNode &>(*unaryOperatorNode.factor);
REQUIRE(numberNode.number == "123");
@@ -295,6 +371,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &unaryOperatorNode = dynamic_cast<gd::UnaryOperatorNode &>(*node);
REQUIRE(unaryOperatorNode.op == '+');
REQUIRE(unaryOperatorNode.type == "number");
auto &numberNode =
dynamic_cast<gd::NumberNode &>(*unaryOperatorNode.factor);
REQUIRE(numberNode.number == "123");
@@ -304,6 +381,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(node != nullptr);
auto &unaryOperatorNode = dynamic_cast<gd::UnaryOperatorNode &>(*node);
REQUIRE(unaryOperatorNode.op == '-');
REQUIRE(unaryOperatorNode.type == "number");
auto &numberNode =
dynamic_cast<gd::NumberNode &>(*unaryOperatorNode.factor);
REQUIRE(numberNode.number == "123.2");
@@ -520,6 +598,26 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(validator.GetErrors().size() == 0);
}
{
auto node = parser.ParseExpression("object", "Hello World 1");
REQUIRE(node != nullptr);
auto &identifierNode = dynamic_cast<gd::IdentifierNode &>(*node);
REQUIRE(identifierNode.identifierName == "Hello World 1");
gd::ExpressionValidator validator;
node->Visit(validator);
REQUIRE(validator.GetErrors().size() == 0);
}
{
auto node = parser.ParseExpression("object", "Hello World 1 ");
REQUIRE(node != nullptr);
auto &identifierNode = dynamic_cast<gd::IdentifierNode &>(*node);
REQUIRE(identifierNode.identifierName == "Hello World 1");
gd::ExpressionValidator validator;
node->Visit(validator);
REQUIRE(validator.GetErrors().size() == 0);
}
{
auto node = parser.ParseExpression("object", "Hello World 1 ");
REQUIRE(node != nullptr);
@@ -550,9 +648,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
gd::ExpressionValidator validator;
node->Visit(validator);
REQUIRE(validator.GetErrors().size() == 1);
REQUIRE(
validator.GetErrors()[0]->GetMessage() ==
"Operators (+, -, /, *) can't be used with an object name. Remove the operator.");
REQUIRE(validator.GetErrors()[0]->GetMessage() ==
"Operators (+, -, /, *) can't be used with an object name. "
"Remove the operator.");
}
}
@@ -560,7 +658,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
{
auto node = parser.ParseExpression("number", "MyExtension::GetNumber()");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionNode &>(*node);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "MyExtension::GetNumber");
REQUIRE(functionNode.objectName == "");
REQUIRE(functionNode.behaviorName == "");
@@ -573,7 +671,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
auto node = parser.ParseExpression(
"number", "MyExtension::GetNumberWith2Params(12, \"hello world\")");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionNode &>(*node);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "MyExtension::GetNumberWith2Params");
REQUIRE(functionNode.objectName == "");
REQUIRE(functionNode.behaviorName == "");
@@ -586,7 +684,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
auto node = parser.ParseExpression(
"number", "MyExtension::GetNumberWith3Params(12, \"hello world\")");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionNode &>(*node);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
gd::ExpressionValidator validator;
node->Visit(validator);
@@ -597,7 +695,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
"number",
"MyExtension::GetNumberWith3Params(12, \"hello world\", 34)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionNode &>(*node);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
gd::ExpressionValidator validator;
node->Visit(validator);
@@ -607,7 +705,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
auto node =
parser.ParseExpression("number", "MySpriteObject.GetObjectNumber()");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionNode &>(*node);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "GetObjectNumber");
REQUIRE(functionNode.objectName == "MySpriteObject");
REQUIRE(functionNode.behaviorName == "");
@@ -620,7 +718,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
auto node = parser.ParseExpression(
"number", "WhateverObject.WhateverBehavior::WhateverFunction()");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionNode &>(*node);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "WhateverFunction");
REQUIRE(functionNode.objectName == "WhateverObject");
REQUIRE(functionNode.behaviorName == "WhateverBehavior");
@@ -630,7 +728,58 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
"number",
"WhateverObject.WhateverBehavior::WhateverFunction(1, \"2\", three)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionNode &>(*node);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "WhateverFunction");
REQUIRE(functionNode.objectName == "WhateverObject");
REQUIRE(functionNode.behaviorName == "WhateverBehavior");
REQUIRE(functionNode.parameters.size() == 3);
auto &numberNode =
dynamic_cast<gd::NumberNode &>(*functionNode.parameters[0]);
auto &textNode =
dynamic_cast<gd::TextNode &>(*functionNode.parameters[1]);
auto &identifierNode =
dynamic_cast<gd::IdentifierNode &>(*functionNode.parameters[2]);
REQUIRE(numberNode.number == "1");
REQUIRE(textNode.text == "2");
REQUIRE(identifierNode.identifierName == "three");
}
}
SECTION("Valid function calls (whitespaces)") {
{
auto node =
parser.ParseExpression("number", "MyExtension::GetNumber ()");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "MyExtension::GetNumber");
REQUIRE(functionNode.objectName == "");
REQUIRE(functionNode.behaviorName == "");
gd::ExpressionValidator validator;
node->Visit(validator);
REQUIRE(validator.GetErrors().size() == 0);
}
{
auto node = parser.ParseExpression(
"number", "MySpriteObject . GetObjectNumber ()");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "GetObjectNumber");
REQUIRE(functionNode.objectName == "MySpriteObject");
REQUIRE(functionNode.behaviorName == "");
gd::ExpressionValidator validator;
node->Visit(validator);
REQUIRE(validator.GetErrors().size() == 0);
}
{
auto node = parser.ParseExpression("number",
"WhateverObject . WhateverBehavior "
":: WhateverFunction ( 1 , \"2\" "
" , three )");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "WhateverFunction");
REQUIRE(functionNode.objectName == "WhateverObject");
REQUIRE(functionNode.behaviorName == "WhateverBehavior");
@@ -653,7 +802,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
auto node = parser.ParseExpression(
"number", "MyExtension::GetNumberWith3Params(12, \"hello world\",)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionNode &>(*node);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
gd::ExpressionValidator validator;
node->Visit(validator);
@@ -665,7 +814,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
{
auto node = parser.ParseExpression("number", "MyExtension::MouseX(,)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionNode &>(*node);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
gd::ExpressionValidator validator;
node->Visit(validator);
@@ -673,6 +822,40 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
}
SECTION("Valid object function name") {
auto node = parser.ParseExpression("string", "MyObject.MyFunc");
REQUIRE(node != nullptr);
auto &objectFunctionName = dynamic_cast<gd::ObjectFunctionNameNode &>(*node);
REQUIRE(objectFunctionName.objectName == "MyObject");
REQUIRE(objectFunctionName.objectFunctionOrBehaviorName == "MyFunc");
}
SECTION("Valid object behavior name") {
auto node = parser.ParseExpression("string", "MyObject.MyBehavior::MyFunc");
REQUIRE(node != nullptr);
auto &objectFunctionName = dynamic_cast<gd::ObjectFunctionNameNode &>(*node);
REQUIRE(objectFunctionName.objectName == "MyObject");
REQUIRE(objectFunctionName.objectFunctionOrBehaviorName == "MyBehavior");
REQUIRE(objectFunctionName.behaviorFunctionName == "MyFunc");
}
SECTION("Unfinished object function name") {
auto node = parser.ParseExpression("string", "MyObject.");
REQUIRE(node != nullptr);
auto &objectFunctionName = dynamic_cast<gd::ObjectFunctionNameNode &>(*node);
REQUIRE(objectFunctionName.objectName == "MyObject");
REQUIRE(objectFunctionName.objectFunctionOrBehaviorName == "");
}
SECTION("Unfinished object behavior name") {
auto node = parser.ParseExpression("string", "MyObject.MyBehavior::");
REQUIRE(node != nullptr);
auto &objectFunctionName = dynamic_cast<gd::ObjectFunctionNameNode &>(*node);
REQUIRE(objectFunctionName.objectName == "MyObject");
REQUIRE(objectFunctionName.objectFunctionOrBehaviorName == "MyBehavior");
REQUIRE(objectFunctionName.behaviorFunctionName == "");
}
SECTION("Invalid function calls") {
{
auto node = parser.ParseExpression("number", "Idontexist(12)");
@@ -746,6 +929,45 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(validator.GetErrors()[0]->GetEndPosition() == 33);
}
}
SECTION("Invalid free function call, finishing with namespace separator") {
{
auto node = parser.ParseExpression("number", "MyExtension::(12)");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator;
node->Visit(validator);
REQUIRE(validator.GetErrors().size() == 2);
REQUIRE(validator.GetErrors()[0]->GetMessage() ==
"Cannot find an expression with this name: MyExtension::\nDouble "
"check that you've not made any typo in the name.");
REQUIRE(validator.GetErrors()[0]->GetStartPosition() == 0);
REQUIRE(validator.GetErrors()[0]->GetEndPosition() == 17);
REQUIRE(validator.GetErrors()[1]->GetMessage() ==
"This parameter was not expected by this expression. Remove it "
"or verify that you've entered the proper expression name.");
}
}
SECTION("Invalid behavior function call, finishing with namespace separator") {
{
auto node = parser.ParseExpression("number", "MyObject.MyBehavior::(12)");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator;
node->Visit(validator);
REQUIRE(validator.GetErrors().size() == 2);
// TODO: The error message could be improved
REQUIRE(validator.GetErrors()[0]->GetMessage() ==
"Cannot find an expression with this name: \nDouble "
"check that you've not made any typo in the name.");
REQUIRE(validator.GetErrors()[0]->GetStartPosition() == 0);
REQUIRE(validator.GetErrors()[0]->GetEndPosition() == 25);
REQUIRE(validator.GetErrors()[1]->GetMessage() ==
"This parameter was not expected by this expression. Remove it "
"or verify that you've entered the proper expression name.");
}
}
SECTION("Invalid variables") {
{
@@ -795,7 +1017,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
{
auto node = parser.ParseExpression(
"scenevar", "myVariable[ \"My named children\" ].grandChild");
"scenevar", "myVariable[ \"My named children\" ] . grandChild");
REQUIRE(node != nullptr);
auto &variableNode = dynamic_cast<gd::VariableNode &>(*node);
REQUIRE(variableNode.name == "myVariable");
@@ -853,4 +1075,425 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
"\fe'f\fwe'\te'w\f'reg[pto43o]"));
}
}
SECTION("Location") {
SECTION("Single node locations") {
{
auto node = parser.ParseExpression("string", "\"hello world\"");
REQUIRE(node != nullptr);
auto &textNode = dynamic_cast<gd::TextNode &>(*node);
REQUIRE(textNode.text == "hello world");
REQUIRE(textNode.location.GetStartPosition() == 0);
REQUIRE(textNode.location.GetEndPosition() == 13);
}
{
auto node = parser.ParseExpression("number", "3.14159");
REQUIRE(node != nullptr);
auto &numberNode = dynamic_cast<gd::NumberNode &>(*node);
REQUIRE(numberNode.number == "3.14159");
REQUIRE(numberNode.location.GetStartPosition() == 0);
REQUIRE(numberNode.location.GetEndPosition() == 7);
}
{
auto node = parser.ParseExpression("number", "345 + 678");
REQUIRE(node != nullptr);
auto &operatorNode = dynamic_cast<gd::OperatorNode &>(*node);
REQUIRE(operatorNode.location.GetStartPosition() == 0);
REQUIRE(operatorNode.location.GetEndPosition() == 10);
REQUIRE(operatorNode.leftHandSide != nullptr);
REQUIRE(operatorNode.rightHandSide != nullptr);
REQUIRE(operatorNode.leftHandSide->location.GetStartPosition() == 0);
REQUIRE(operatorNode.leftHandSide->location.GetEndPosition() == 3);
REQUIRE(operatorNode.rightHandSide->location.GetStartPosition() == 7);
REQUIRE(operatorNode.rightHandSide->location.GetEndPosition() == 10);
}
}
SECTION("Variable locations (simple variable name)") {
auto node = parser.ParseExpression("scenevar", "MyVariable");
REQUIRE(node != nullptr);
auto &variableNode = dynamic_cast<gd::VariableNode &>(*node);
REQUIRE(variableNode.name == "MyVariable");
REQUIRE(variableNode.location.GetStartPosition() == 0);
REQUIRE(variableNode.location.GetEndPosition() == 10);
REQUIRE(variableNode.nameLocation.GetStartPosition() == 0);
REQUIRE(variableNode.nameLocation.GetEndPosition() == 10);
}
SECTION("Variable locations (with child)") {
auto node = parser.ParseExpression("scenevar", "MyVariable.MyChild");
REQUIRE(node != nullptr);
auto &variableNode = dynamic_cast<gd::VariableNode &>(*node);
REQUIRE(variableNode.name == "MyVariable");
REQUIRE(variableNode.location.GetStartPosition() == 0);
REQUIRE(variableNode.location.GetEndPosition() == 18);
REQUIRE(variableNode.nameLocation.GetStartPosition() == 0);
REQUIRE(variableNode.nameLocation.GetEndPosition() == 10);
REQUIRE(variableNode.child != nullptr);
auto &childVariableAccessorNode =
dynamic_cast<gd::VariableAccessorNode &>(*variableNode.child);
REQUIRE(childVariableAccessorNode.location.GetStartPosition() == 10);
REQUIRE(childVariableAccessorNode.location.GetEndPosition() == 18);
REQUIRE(childVariableAccessorNode.dotLocation.GetStartPosition() == 10);
REQUIRE(childVariableAccessorNode.dotLocation.GetEndPosition() == 11);
REQUIRE(childVariableAccessorNode.nameLocation.GetStartPosition() == 11);
REQUIRE(childVariableAccessorNode.nameLocation.GetEndPosition() == 18);
}
SECTION("Free function locations") {
auto node =
parser.ParseExpression("number", "WhateverFunction(1, \"2\", three)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "WhateverFunction");
REQUIRE(functionNode.parameters.size() == 3);
auto &numberNode =
dynamic_cast<gd::NumberNode &>(*functionNode.parameters[0]);
auto &textNode =
dynamic_cast<gd::TextNode &>(*functionNode.parameters[1]);
auto &identifierNode =
dynamic_cast<gd::IdentifierNode &>(*functionNode.parameters[2]);
REQUIRE(numberNode.number == "1");
REQUIRE(textNode.text == "2");
REQUIRE(identifierNode.identifierName == "three");
REQUIRE(functionNode.location.GetStartPosition() == 0);
REQUIRE(functionNode.location.GetEndPosition() == 31);
REQUIRE(functionNode.objectNameLocation.IsValid() == false);
REQUIRE(functionNode.objectNameDotLocation.IsValid() == false);
REQUIRE(functionNode.behaviorNameLocation.IsValid() == false);
REQUIRE(functionNode.behaviorNameNamespaceSeparatorLocation.IsValid() ==
false);
REQUIRE(functionNode.functionNameLocation.GetStartPosition() == 0);
REQUIRE(functionNode.functionNameLocation.GetEndPosition() == 16);
REQUIRE(functionNode.openingParenthesisLocation.GetStartPosition() == 16);
REQUIRE(functionNode.openingParenthesisLocation.GetEndPosition() == 17);
REQUIRE(numberNode.location.GetStartPosition() == 17);
REQUIRE(numberNode.location.GetEndPosition() == 18);
REQUIRE(textNode.location.GetStartPosition() == 20);
REQUIRE(textNode.location.GetEndPosition() == 23);
REQUIRE(identifierNode.location.GetStartPosition() == 25);
REQUIRE(identifierNode.location.GetEndPosition() == 30);
REQUIRE(functionNode.closingParenthesisLocation.GetStartPosition() == 30);
REQUIRE(functionNode.closingParenthesisLocation.GetEndPosition() == 31);
}
SECTION("Free function locations (with whitespaces)") {
auto node = parser.ParseExpression("number",
"WhateverFunction (1, \"2\", three)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "WhateverFunction");
REQUIRE(functionNode.parameters.size() == 3);
REQUIRE(functionNode.location.GetStartPosition() == 0);
REQUIRE(functionNode.location.GetEndPosition() == 33);
REQUIRE(functionNode.objectNameLocation.IsValid() == false);
REQUIRE(functionNode.objectNameDotLocation.IsValid() == false);
REQUIRE(functionNode.behaviorNameLocation.IsValid() == false);
REQUIRE(functionNode.behaviorNameNamespaceSeparatorLocation.IsValid() ==
false);
REQUIRE(functionNode.functionNameLocation.GetStartPosition() == 0);
REQUIRE(functionNode.functionNameLocation.GetEndPosition() == 16);
REQUIRE(functionNode.openingParenthesisLocation.GetStartPosition() == 18);
REQUIRE(functionNode.openingParenthesisLocation.GetEndPosition() == 19);
REQUIRE(functionNode.closingParenthesisLocation.GetStartPosition() == 32);
REQUIRE(functionNode.closingParenthesisLocation.GetEndPosition() == 33);
}
SECTION("Object function locations") {
auto node = parser.ParseExpression(
"number", "WhateverObject.WhateverFunction(1, \"2\", three)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "WhateverFunction");
REQUIRE(functionNode.objectName == "WhateverObject");
REQUIRE(functionNode.parameters.size() == 3);
auto &numberNode =
dynamic_cast<gd::NumberNode &>(*functionNode.parameters[0]);
auto &textNode =
dynamic_cast<gd::TextNode &>(*functionNode.parameters[1]);
auto &identifierNode =
dynamic_cast<gd::IdentifierNode &>(*functionNode.parameters[2]);
REQUIRE(numberNode.number == "1");
REQUIRE(textNode.text == "2");
REQUIRE(identifierNode.identifierName == "three");
REQUIRE(functionNode.location.GetStartPosition() == 0);
REQUIRE(functionNode.location.GetEndPosition() == 46);
REQUIRE(functionNode.objectNameLocation.GetStartPosition() == 0);
REQUIRE(functionNode.objectNameLocation.GetEndPosition() == 14);
REQUIRE(functionNode.objectNameDotLocation.GetStartPosition() == 14);
REQUIRE(functionNode.objectNameDotLocation.GetEndPosition() == 15);
REQUIRE(functionNode.behaviorNameLocation.IsValid() == false);
REQUIRE(functionNode.behaviorNameNamespaceSeparatorLocation.IsValid() ==
false);
REQUIRE(functionNode.functionNameLocation.GetStartPosition() == 15);
REQUIRE(functionNode.functionNameLocation.GetEndPosition() == 31);
REQUIRE(functionNode.openingParenthesisLocation.GetStartPosition() == 31);
REQUIRE(functionNode.openingParenthesisLocation.GetEndPosition() == 32);
REQUIRE(numberNode.location.GetStartPosition() == 32);
REQUIRE(numberNode.location.GetEndPosition() == 33);
REQUIRE(textNode.location.GetStartPosition() == 35);
REQUIRE(textNode.location.GetEndPosition() == 38);
REQUIRE(identifierNode.location.GetStartPosition() == 40);
REQUIRE(identifierNode.location.GetEndPosition() == 45);
REQUIRE(functionNode.closingParenthesisLocation.GetStartPosition() == 45);
REQUIRE(functionNode.closingParenthesisLocation.GetEndPosition() == 46);
}
SECTION("Object function name locations") {
auto node =
parser.ParseExpression("number", "WhateverObject.WhateverFunction");
REQUIRE(node != nullptr);
auto &objectFunctionNameNode =
dynamic_cast<gd::ObjectFunctionNameNode &>(*node);
REQUIRE(objectFunctionNameNode.objectName == "WhateverObject");
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorName ==
"WhateverFunction");
REQUIRE(objectFunctionNameNode.location.GetStartPosition() == 0);
REQUIRE(objectFunctionNameNode.location.GetEndPosition() == 31);
REQUIRE(objectFunctionNameNode.objectNameLocation.GetStartPosition() ==
0);
REQUIRE(objectFunctionNameNode.objectNameLocation.GetEndPosition() == 14);
REQUIRE(objectFunctionNameNode.objectNameDotLocation.GetStartPosition() ==
14);
REQUIRE(objectFunctionNameNode.objectNameDotLocation.GetEndPosition() ==
15);
REQUIRE(objectFunctionNameNode.behaviorFunctionNameLocation.IsValid() ==
false);
REQUIRE(objectFunctionNameNode.behaviorNameNamespaceSeparatorLocation
.IsValid() == false);
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorNameLocation
.GetStartPosition() == 15);
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorNameLocation
.GetEndPosition() == 31);
}
SECTION("Object function name locations (with whitespace)") {
auto node = parser.ParseExpression(
"number", "WhateverObject . WhateverFunction ");
REQUIRE(node != nullptr);
auto &objectFunctionNameNode =
dynamic_cast<gd::ObjectFunctionNameNode &>(*node);
REQUIRE(objectFunctionNameNode.objectName == "WhateverObject");
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorName ==
"WhateverFunction");
REQUIRE(objectFunctionNameNode.location.GetStartPosition() == 0);
REQUIRE(objectFunctionNameNode.location.GetEndPosition() == 37);
REQUIRE(objectFunctionNameNode.objectNameLocation.GetStartPosition() ==
0);
REQUIRE(objectFunctionNameNode.objectNameLocation.GetEndPosition() == 14);
REQUIRE(objectFunctionNameNode.objectNameDotLocation.GetStartPosition() ==
16);
REQUIRE(objectFunctionNameNode.objectNameDotLocation.GetEndPosition() ==
17);
REQUIRE(objectFunctionNameNode.behaviorFunctionNameLocation.IsValid() ==
false);
REQUIRE(objectFunctionNameNode.behaviorNameNamespaceSeparatorLocation
.IsValid() == false);
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorNameLocation
.GetStartPosition() == 19);
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorNameLocation
.GetEndPosition() == 35);
}
SECTION("Object function locations (with whitespaces)") {
auto node = parser.ParseExpression(
"number", "WhateverObject . WhateverFunction (1, \"2\", three)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "WhateverFunction");
REQUIRE(functionNode.objectName == "WhateverObject");
REQUIRE(functionNode.location.GetStartPosition() == 0);
REQUIRE(functionNode.location.GetEndPosition() == 49);
REQUIRE(functionNode.objectNameLocation.GetStartPosition() == 0);
REQUIRE(functionNode.objectNameLocation.GetEndPosition() == 14);
REQUIRE(functionNode.objectNameDotLocation.GetStartPosition() == 15);
REQUIRE(functionNode.objectNameDotLocation.GetEndPosition() == 16);
REQUIRE(functionNode.functionNameLocation.GetStartPosition() == 17);
REQUIRE(functionNode.functionNameLocation.GetEndPosition() == 33);
REQUIRE(functionNode.openingParenthesisLocation.GetStartPosition() == 34);
REQUIRE(functionNode.openingParenthesisLocation.GetEndPosition() == 35);
REQUIRE(functionNode.closingParenthesisLocation.GetStartPosition() == 48);
REQUIRE(functionNode.closingParenthesisLocation.GetEndPosition() == 49);
}
SECTION("Behavior function locations") {
auto node = parser.ParseExpression(
"number",
"WhateverObject.WhateverBehavior::WhateverFunction(1, \"2\", three)");
REQUIRE(node != nullptr);
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(functionNode.functionName == "WhateverFunction");
REQUIRE(functionNode.objectName == "WhateverObject");
REQUIRE(functionNode.behaviorName == "WhateverBehavior");
REQUIRE(functionNode.parameters.size() == 3);
auto &numberNode =
dynamic_cast<gd::NumberNode &>(*functionNode.parameters[0]);
auto &textNode =
dynamic_cast<gd::TextNode &>(*functionNode.parameters[1]);
auto &identifierNode =
dynamic_cast<gd::IdentifierNode &>(*functionNode.parameters[2]);
REQUIRE(numberNode.number == "1");
REQUIRE(textNode.text == "2");
REQUIRE(identifierNode.identifierName == "three");
REQUIRE(functionNode.location.GetStartPosition() == 0);
REQUIRE(functionNode.location.GetEndPosition() == 64);
REQUIRE(functionNode.objectNameLocation.GetStartPosition() == 0);
REQUIRE(functionNode.objectNameLocation.GetEndPosition() == 14);
REQUIRE(functionNode.objectNameDotLocation.GetStartPosition() == 14);
REQUIRE(functionNode.objectNameDotLocation.GetEndPosition() == 15);
REQUIRE(functionNode.behaviorNameLocation.GetStartPosition() == 15);
REQUIRE(functionNode.behaviorNameLocation.GetEndPosition() == 31);
REQUIRE(functionNode.behaviorNameNamespaceSeparatorLocation
.GetStartPosition() == 31);
REQUIRE(functionNode.behaviorNameNamespaceSeparatorLocation
.GetEndPosition() == 33);
REQUIRE(functionNode.functionNameLocation.GetStartPosition() == 33);
REQUIRE(functionNode.functionNameLocation.GetEndPosition() == 49);
REQUIRE(functionNode.openingParenthesisLocation.GetStartPosition() == 49);
REQUIRE(functionNode.openingParenthesisLocation.GetEndPosition() == 50);
REQUIRE(numberNode.location.GetStartPosition() == 50);
REQUIRE(numberNode.location.GetEndPosition() == 51);
REQUIRE(textNode.location.GetStartPosition() == 53);
REQUIRE(textNode.location.GetEndPosition() == 56);
REQUIRE(identifierNode.location.GetStartPosition() == 58);
REQUIRE(identifierNode.location.GetEndPosition() == 63);
REQUIRE(functionNode.closingParenthesisLocation.GetStartPosition() == 63);
REQUIRE(functionNode.closingParenthesisLocation.GetEndPosition() == 64);
}
SECTION("Behavior function name locations (with whitespace)") {
auto node = parser.ParseExpression(
"number", "WhateverObject . WhateverFunction ");
REQUIRE(node != nullptr);
auto &objectFunctionNameNode =
dynamic_cast<gd::ObjectFunctionNameNode &>(*node);
REQUIRE(objectFunctionNameNode.objectName == "WhateverObject");
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorName ==
"WhateverFunction");
REQUIRE(objectFunctionNameNode.location.GetStartPosition() == 0);
REQUIRE(objectFunctionNameNode.location.GetEndPosition() == 37);
REQUIRE(objectFunctionNameNode.objectNameLocation.GetStartPosition() ==
0);
REQUIRE(objectFunctionNameNode.objectNameLocation.GetEndPosition() == 14);
REQUIRE(objectFunctionNameNode.objectNameDotLocation.GetStartPosition() ==
16);
REQUIRE(objectFunctionNameNode.objectNameDotLocation.GetEndPosition() ==
17);
REQUIRE(objectFunctionNameNode.behaviorFunctionNameLocation.IsValid() ==
false);
REQUIRE(objectFunctionNameNode.behaviorNameNamespaceSeparatorLocation
.IsValid() == false);
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorNameLocation
.GetStartPosition() == 19);
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorNameLocation
.GetEndPosition() == 35);
}
SECTION("Behavior function name locations") {
auto node = parser.ParseExpression(
"number", "WhateverObject.WhateverBehavior::WhateverFunction");
REQUIRE(node != nullptr);
auto &objectFunctionNameNode =
dynamic_cast<gd::ObjectFunctionNameNode &>(*node);
REQUIRE(objectFunctionNameNode.objectName == "WhateverObject");
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorName ==
"WhateverBehavior");
REQUIRE(objectFunctionNameNode.behaviorFunctionName ==
"WhateverFunction");
REQUIRE(objectFunctionNameNode.location.GetStartPosition() == 0);
REQUIRE(objectFunctionNameNode.location.GetEndPosition() == 49);
REQUIRE(objectFunctionNameNode.objectNameLocation.GetStartPosition() ==
0);
REQUIRE(objectFunctionNameNode.objectNameLocation.GetEndPosition() == 14);
REQUIRE(objectFunctionNameNode.objectNameDotLocation.GetStartPosition() ==
14);
REQUIRE(objectFunctionNameNode.objectNameDotLocation.GetEndPosition() ==
15);
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorNameLocation
.GetStartPosition() == 15);
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorNameLocation
.GetEndPosition() == 31);
REQUIRE(objectFunctionNameNode.behaviorNameNamespaceSeparatorLocation
.GetStartPosition() == 31);
REQUIRE(objectFunctionNameNode.behaviorNameNamespaceSeparatorLocation
.GetEndPosition() == 33);
REQUIRE(objectFunctionNameNode.behaviorFunctionNameLocation
.GetStartPosition() == 33);
REQUIRE(objectFunctionNameNode.behaviorFunctionNameLocation
.GetEndPosition() == 49);
}
SECTION("Behavior function name locations (with whitespace)") {
auto node = parser.ParseExpression(
"number", "WhateverObject.WhateverBehavior :: WhateverFunction");
REQUIRE(node != nullptr);
auto &objectFunctionNameNode =
dynamic_cast<gd::ObjectFunctionNameNode &>(*node);
REQUIRE(objectFunctionNameNode.objectName == "WhateverObject");
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorName ==
"WhateverBehavior");
REQUIRE(objectFunctionNameNode.behaviorFunctionName ==
"WhateverFunction");
REQUIRE(objectFunctionNameNode.location.GetStartPosition() == 0);
REQUIRE(objectFunctionNameNode.location.GetEndPosition() == 53);
REQUIRE(objectFunctionNameNode.objectNameLocation.GetStartPosition() ==
0);
REQUIRE(objectFunctionNameNode.objectNameLocation.GetEndPosition() == 14);
REQUIRE(objectFunctionNameNode.objectNameDotLocation.GetStartPosition() ==
14);
REQUIRE(objectFunctionNameNode.objectNameDotLocation.GetEndPosition() ==
15);
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorNameLocation
.GetStartPosition() == 15);
REQUIRE(objectFunctionNameNode.objectFunctionOrBehaviorNameLocation
.GetEndPosition() == 31);
REQUIRE(objectFunctionNameNode.behaviorNameNamespaceSeparatorLocation
.GetStartPosition() == 33);
REQUIRE(objectFunctionNameNode.behaviorNameNamespaceSeparatorLocation
.GetEndPosition() == 35);
REQUIRE(objectFunctionNameNode.behaviorFunctionNameLocation
.GetStartPosition() == 37);
REQUIRE(objectFunctionNameNode.behaviorFunctionNameLocation
.GetEndPosition() == 53);
}
SECTION("Invalid/partial expression locations") {
{
auto node = parser.ParseExpression("number", "3.14159 + ");
REQUIRE(node != nullptr);
auto &operatorNode = dynamic_cast<gd::OperatorNode &>(*node);
REQUIRE(operatorNode.leftHandSide != nullptr);
REQUIRE(operatorNode.rightHandSide != nullptr);
REQUIRE(operatorNode.location.GetStartPosition() == 0);
REQUIRE(operatorNode.location.GetEndPosition() == 10);
auto &numberNode =
dynamic_cast<gd::NumberNode &>(*operatorNode.leftHandSide);
auto &emptyNode =
dynamic_cast<gd::EmptyNode &>(*operatorNode.rightHandSide);
REQUIRE(numberNode.location.GetStartPosition() == 0);
REQUIRE(numberNode.location.GetEndPosition() == 7);
REQUIRE(emptyNode.location.GetStartPosition() == 10);
REQUIRE(emptyNode.location.GetEndPosition() == 10);
}
{
auto node = parser.ParseExpression("number", "\"abcde\" + ");
REQUIRE(node != nullptr);
auto &operatorNode = dynamic_cast<gd::OperatorNode &>(*node);
REQUIRE(operatorNode.leftHandSide != nullptr);
REQUIRE(operatorNode.rightHandSide != nullptr);
REQUIRE(operatorNode.location.GetStartPosition() == 0);
REQUIRE(operatorNode.location.GetEndPosition() == 10);
auto &textNode =
dynamic_cast<gd::TextNode &>(*operatorNode.leftHandSide);
auto &emptyNode =
dynamic_cast<gd::EmptyNode &>(*operatorNode.rightHandSide);
REQUIRE(textNode.location.GetStartPosition() == 0);
REQUIRE(textNode.location.GetEndPosition() == 7);
REQUIRE(emptyNode.location.GetStartPosition() == 10);
REQUIRE(emptyNode.location.GetEndPosition() == 10);
}
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include <chrono>
#include <numeric>
#include "DummyPlatform.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "catch.hpp"
TEST_CASE("ExpressionParser2 - Benchmarks", "[common][events]") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &layout1 = project.InsertNewLayout("Layout1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
gd::ExpressionParser2 parser(platform, project, layout1);
auto parseExpression = [&parser](const gd::String &expression) {
auto parseExpressionWithType = [&parser,
&expression](const gd::String &type) {
auto node = parser.ParseExpression(type, expression);
REQUIRE(node != nullptr);
};
parseExpressionWithType("number");
parseExpressionWithType("string");
parseExpressionWithType("scenevar");
parseExpressionWithType("globalvar");
parseExpressionWithType("objectvar");
parseExpressionWithType("object");
parseExpressionWithType("objectPtr");
parseExpressionWithType("unknown");
};
auto doBenchmark = [](const gd::String &benchmarkName,
const size_t runsCount,
std::function<void()> func) {
std::vector<long long> timesInMicroseconds;
for (size_t i = 0; i < runsCount; i++) {
auto start = std::chrono::steady_clock::now();
func();
auto end = std::chrono::steady_clock::now();
timesInMicroseconds.push_back(
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
.count());
}
std::cout << benchmarkName << " benchmark (" << runsCount << " runs): "
<< (float)std::accumulate(timesInMicroseconds.begin(),
timesInMicroseconds.end(),
0) /
(float)runsCount
<< " microseconds" << std::endl;
};
SECTION("Parse long expression") {
doBenchmark("Parse long expression", 10, [&]() {
REQUIRE_NOTHROW(parseExpression(
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+"
"MySpriteObject.X()+MySpriteObject.X()/"
"cos(3.123456789)+MySpriteObject.X()+0"));
});
}
SECTION("Parse long expression") {
doBenchmark("Long identifier", 100, [&]() {
REQUIRE_NOTHROW(parseExpression(
"MyLoooooongIdentifierThatNeverStoooooopsAndContinueAgainAndAgainAndA"
"gainAndAgainAndAgainAndAgainAndAgainAndAgainAndAgainAndAgainAndAgain"
"AndAgainAndAgainAndAgainAndAgainAndAgainAndAgainAndAgain"));
});
}
}

View File

@@ -176,6 +176,23 @@ TEST_CASE("ExpressionParser2NodePrinter", "[common][events]") {
"Idontexist(12, 34, \"56\" + 2)");
}
SECTION("Valid function name") {
SECTION("Free function") {
testPrinter("number", "MyExtension::GetNumber");
testPrinter("number", "MyExtension::GetNumberWith2Params");
testPrinter("number", "MyExtension::UnknownFunc");
testPrinter("number", "UnknownFunc");
}
SECTION("Object function") {
testPrinter("number", "a.b");
testPrinter("number", "MySpriteObject.GetObjectNumber");
testPrinter("number", "MySpriteObject.MyOtherFunc");
}
SECTION("Behavior function") {
testPrinter("number", "MySpriteObject.MyBehavior::MyFunc");
}
}
SECTION("Valid variables") {
testPrinter("scenevar", "myVariable");
testPrinter("scenevar", "myVariable.myChild");

View File

@@ -11,16 +11,31 @@
TEST_CASE("SceneNameMangler", "[common]") {
SECTION("Basics") {
REQUIRE(gd::SceneNameMangler::GetMangledSceneName(
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(
u8"TotallyCorrectSceneName") == u8"TotallyCorrectSceneName");
REQUIRE(gd::SceneNameMangler::GetMangledSceneName(
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(
u8"TotallyCorrectSceneName") == u8"TotallyCorrectSceneName");
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(
u8"Totally NOT CorrectSceneName") ==
u8"Totally_32NOT_32CorrectSceneName");
REQUIRE(gd::SceneNameMangler::GetMangledSceneName(u8"Nouvelle scène") ==
u8"Nouvelle_32sc_232ne");
REQUIRE(gd::SceneNameMangler::GetMangledSceneName(u8"A test Ԙ") ==
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(
u8"Totally NOT CorrectSceneName") ==
u8"Totally_32NOT_32CorrectSceneName");
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(
u8"Nouvelle scène") == u8"Nouvelle_32sc_232ne");
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(
u8"Nouvelle scène") == u8"Nouvelle_32sc_232ne");
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(u8"A test Ԙ") ==
u8"A_32test_32_1304");
REQUIRE(gd::SceneNameMangler::GetMangledSceneName(u8"1 Nouvelle scène") ==
u8"_49_32Nouvelle_32sc_232ne");
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(u8"A test Ԙ") ==
u8"A_32test_32_1304");
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(
u8"1 Nouvelle scène") == u8"_49_32Nouvelle_32sc_232ne");
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(
u8"1 Nouvelle scène") == u8"_49_32Nouvelle_32sc_232ne");
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(
u8"汉语") == u8"_27721_35821");
REQUIRE(gd::SceneNameMangler::Get()->GetMangledSceneName(
u8"汉语") == u8"_27721_35821");
}
}

View File

@@ -1,67 +0,0 @@
/*
* 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 common features of GDevelop Core.
*/
#include "GDCore/Events/Parsers/VariableParser.h"
#include "GDCore/String.h"
#include "catch.hpp"
namespace {
class TestVariableParserCallbacks : public gd::VariableParserCallbacks {
public:
TestVariableParserCallbacks(gd::String &debugStr)
: VariableParserCallbacks(), debugStr(debugStr) {}
virtual void OnRootVariable(gd::String variableName) {
debugStr += "OnRootVariable(" + variableName + ")\n";
}
virtual void OnChildVariable(gd::String variableName) {
debugStr += "OnChildVariable(" + variableName + ")\n";
}
virtual void OnChildSubscript(gd::String stringExpression) {
debugStr += "OnChildSubscript(" + stringExpression + ")\n";
}
private:
gd::String &debugStr;
};
} // namespace
TEST_CASE("VariableParser", "[common][events]") {
SECTION("Basics") {
{
gd::String str;
TestVariableParserCallbacks callbacks(str);
gd::VariableParser parser("myVar");
parser.Parse(callbacks);
REQUIRE(str == "OnRootVariable(myVar)\n");
}
{
gd::String str;
TestVariableParserCallbacks callbacks(str);
gd::VariableParser parser("myVar.child1.child2");
parser.Parse(callbacks);
REQUIRE(str ==
"OnRootVariable(myVar)\nOnChildVariable(child1)\nOnChildVariable("
"child2)\n");
}
{
gd::String str;
TestVariableParserCallbacks callbacks(str);
gd::VariableParser parser("myVar[ToString(i) + \"é\"].child");
parser.Parse(callbacks);
REQUIRE(str ==
"OnRootVariable(myVar)\nOnChildSubscript(ToString(i) + "
"\"é\")\nOnChildVariable(child)\n");
}
}
}

View File

@@ -25,6 +25,39 @@
namespace {
gd::StandardEvent &EnsureStandardEvent(gd::BaseEvent &baseEvent) {
gd::StandardEvent *standardEvent =
dynamic_cast<gd::StandardEvent *>(&baseEvent);
INFO("The inspected event is "
<< (standardEvent ? "a standard event" : "not a standard event"));
REQUIRE(standardEvent != nullptr);
return *standardEvent;
}
const gd::String &GetEventFirstActionFirstParameterString(
gd::BaseEvent &event) {
auto &actions = EnsureStandardEvent(event).GetActions();
REQUIRE(actions.IsEmpty() == false);
REQUIRE(actions.Get(0).GetParametersCount() != 0);
return actions.Get(0).GetParameter(0).GetPlainString();
}
const gd::String &GetEventFirstConditionType(gd::BaseEvent &event) {
auto &conditions = EnsureStandardEvent(event).GetConditions();
REQUIRE(conditions.IsEmpty() == false);
return conditions.Get(0).GetType();
}
const gd::String &GetEventFirstActionType(gd::BaseEvent &event) {
auto &actions = EnsureStandardEvent(event).GetActions();
REQUIRE(actions.IsEmpty() == false);
return actions.Get(0).GetType();
}
gd::EventsFunctionsExtension &SetupProjectWithEventsFunctionExtension(
gd::Project &project) {
auto &eventsExtension =
@@ -126,8 +159,7 @@ gd::EventsFunctionsExtension &SetupProjectWithEventsFunctionExtension(
layout.GetEvents().InsertEvent(event);
}
// Create an event in the layout referring to
// MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunction
// Create an event in the layout using "MyProperty" action
{
gd::StandardEvent event;
gd::Instruction instruction;
@@ -138,6 +170,33 @@ gd::EventsFunctionsExtension &SetupProjectWithEventsFunctionExtension(
layout.GetEvents().InsertEvent(event);
}
// Create an event in the layout using "MyProperty" condition
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType(
"MyEventsExtension::MyEventsBasedBehavior::" +
gd::EventsBasedBehavior::GetPropertyConditionName("MyProperty"));
event.GetConditions().Insert(instruction);
layout.GetEvents().InsertEvent(event);
}
// Create an event in the layout using "MyProperty" expression
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomething");
instruction.SetParametersCount(1);
instruction.SetParameter(
0,
gd::Expression(
"ObjectWithMyBehavior.MyBehavior::" +
gd::EventsBasedBehavior::GetPropertyExpressionName("MyProperty") +
"()"));
event.GetActions().Insert(instruction);
layout.GetEvents().InsertEvent(event);
}
// Create an event in ExternalEvents1 referring to
// MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunctionExpression
{
@@ -148,11 +207,62 @@ gd::EventsFunctionsExtension &SetupProjectWithEventsFunctionExtension(
instruction.SetParameter(
0,
gd::Expression("1 + "
"ObjectWithMyBehavior.MyBehavior::"
"MyBehaviorEventsFunctionExpression(123, 456, 789)"));
event.GetActions().Insert(instruction);
externalEvents.GetEvents().InsertEvent(event);
}
// Create an event in ExternalEvents1 **wrongly** referring to
// MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunctionExpression
// (it's ill-named).
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomething");
instruction.SetParametersCount(1);
instruction.SetParameter(
0,
gd::Expression("2 + "
"ObjectWithMyBehavior::MyBehavior."
"MyBehaviorEventsFunctionExpression(123, 456, 789)"));
event.GetActions().Insert(instruction);
externalEvents.GetEvents().InsertEvent(event);
}
// Create an event in ExternalEvents1 referring to
// MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunctionExpression
// function name without calling the function.
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomething");
instruction.SetParametersCount(1);
instruction.SetParameter(
0,
gd::Expression("3 + "
"ObjectWithMyBehavior.MyBehavior::"
"MyBehaviorEventsFunctionExpression"));
event.GetActions().Insert(instruction);
externalEvents.GetEvents().InsertEvent(event);
}
// Create an event in ExternalEvents1 **wrongly** referring to
// MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunctionExpression
// function name without calling the function (it's ill-named).
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomething");
instruction.SetParametersCount(1);
instruction.SetParameter(
0,
gd::Expression("4 + "
"ObjectWithMyBehavior::MyBehavior."
"MyBehaviorEventsFunctionExpression"));
event.GetActions().Insert(instruction);
externalEvents.GetEvents().InsertEvent(event);
}
}
return eventsExtension;
@@ -477,23 +587,16 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
project, eventsExtension, "MyEventsExtension", "MyRenamedExtension");
// Check that events function calls in instructions have been renamed
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithFreeFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetType() == "MyRenamedExtension::MyEventsFunction");
REQUIRE(GetEventFirstActionType(project.GetLayout("LayoutWithFreeFunctions")
.GetEvents()
.GetEvent(0)) ==
"MyRenamedExtension::MyEventsFunction");
// Check that events function calls in expressions have been renamed
REQUIRE(static_cast<gd::StandardEvent &>(
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithFreeFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
.GetEvent(0)) ==
"1 + MyRenamedExtension::MyEventsFunctionExpression(123, 456)");
// Check that the type of the behavior was changed in the behaviors of
@@ -506,43 +609,41 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
.GetBehavior("MyBehavior")
.GetTypeName() == "MyRenamedExtension::MyEventsBasedBehavior");
// Check if events based behaviors functions have been renamed in
// Check if events-based behavior methods have been renamed in
// instructions
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetType() ==
"MyRenamedExtension::MyEventsBasedBehavior::"
"MyBehaviorEventsFunction");
REQUIRE(
GetEventFirstActionType(project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(0)) ==
"MyRenamedExtension::MyEventsBasedBehavior::"
"MyBehaviorEventsFunction");
// Check if events based behaviors properties have been renamed in
// Check if events-based behaviors properties have been renamed in
// instructions
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(1))
.GetActions()
.Get(0)
.GetType() ==
"MyRenamedExtension::MyEventsBasedBehavior::"
"SetPropertyMyProperty");
REQUIRE(
GetEventFirstActionType(project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(1)) ==
"MyRenamedExtension::MyEventsBasedBehavior::"
"SetPropertyMyProperty");
// Check events based behaviors functions have *not* been renamed in
// Check events-based behavior methods have *not* been renamed in
// expressions
REQUIRE(static_cast<gd::StandardEvent &>(
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
.GetEvent(0)) ==
"1 + "
"ObjectWithMyBehavior::MyBehavior."
"ObjectWithMyBehavior.MyBehavior::"
"MyBehaviorEventsFunctionExpression(123, 456, 789)");
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(2)) ==
"3 + "
"ObjectWithMyBehavior.MyBehavior::"
"MyBehaviorEventsFunctionExpression");
}
SECTION("(Free) events function renamed") {
gd::Project project;
@@ -561,23 +662,16 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
"MyRenamedFunctionExpression");
// Check that events function calls in instructions have been renamed
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithFreeFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetType() == "MyEventsExtension::MyRenamedEventsFunction");
REQUIRE(GetEventFirstActionType(project.GetLayout("LayoutWithFreeFunctions")
.GetEvents()
.GetEvent(0)) ==
"MyEventsExtension::MyRenamedEventsFunction");
// Check that events function calls in expressions have been renamed
REQUIRE(static_cast<gd::StandardEvent &>(
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithFreeFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
.GetEvent(0)) ==
"1 + MyEventsExtension::MyRenamedFunctionExpression(123, 456)");
}
SECTION("(Free) events function parameter moved") {
@@ -586,36 +680,27 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
gd::WholeProjectRefactorer::MoveEventsFunctionParameter(project,
eventsExtension,
"MyEventsFunction",
0, 2);
gd::WholeProjectRefactorer::MoveEventsFunctionParameter(
project,
eventsExtension,
"MyEventsFunctionExpression",
0, 1);
project, eventsExtension, "MyEventsFunction", 0, 2);
gd::WholeProjectRefactorer::MoveEventsFunctionParameter(
project, eventsExtension, "MyEventsFunctionExpression", 0, 1);
// Check that events function calls in instructions have been updated
auto& action = static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithFreeFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0);
auto &action = static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithFreeFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0);
REQUIRE(action.GetParameter(0).GetPlainString() == "Second parameter");
REQUIRE(action.GetParameter(1).GetPlainString() == "Third parameter");
REQUIRE(action.GetParameter(2).GetPlainString() == "First parameter");
// Check that events function calls in expressions have been updated
REQUIRE(static_cast<gd::StandardEvent &>(
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithFreeFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
.GetEvent(0)) ==
"1 + MyEventsExtension::MyEventsFunctionExpression(456, 123)");
}
SECTION("Events based Behavior type renamed") {
@@ -644,42 +729,32 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
.GetTypeName() ==
"MyEventsExtension::MyRenamedEventsBasedBehavior");
// Check if events based behaviors functions have been renamed in
// Check if events-based behavior methods have been renamed in
// instructions
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetType() ==
"MyEventsExtension::MyRenamedEventsBasedBehavior::"
"MyBehaviorEventsFunction");
REQUIRE(
GetEventFirstActionType(project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(0)) ==
"MyEventsExtension::MyRenamedEventsBasedBehavior::"
"MyBehaviorEventsFunction");
// Check if events based behaviors properties have been renamed in
// Check if events-based behaviors properties have been renamed in
// instructions
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(1))
.GetActions()
.Get(0)
.GetType() ==
"MyEventsExtension::MyRenamedEventsBasedBehavior::"
"SetPropertyMyProperty");
REQUIRE(
GetEventFirstActionType(project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(1)) ==
"MyEventsExtension::MyRenamedEventsBasedBehavior::"
"SetPropertyMyProperty");
// Check events based behaviors functions have *not* been renamed in
// Check events-based behavior methods have *not* been renamed in
// expressions
REQUIRE(static_cast<gd::StandardEvent &>(
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
.GetEvent(0)) ==
"1 + "
"ObjectWithMyBehavior::MyBehavior."
"ObjectWithMyBehavior.MyBehavior::"
"MyBehaviorEventsFunctionExpression(123, 456, 789)");
}
SECTION("(Events based Behavior) events function renamed") {
@@ -703,31 +778,54 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
"MyBehaviorEventsFunctionExpression",
"MyRenamedBehaviorEventsFunctionExpression");
// Check if events based behaviors functions have been renamed in
// Check if events-based behavior methods have been renamed in
// instructions
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetType() ==
"MyEventsExtension::MyEventsBasedBehavior::"
"MyRenamedBehaviorEventsFunction");
REQUIRE(
GetEventFirstActionType(project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(0)) ==
"MyEventsExtension::MyEventsBasedBehavior::"
"MyRenamedBehaviorEventsFunction");
// Check events based behaviors functions have been renamed in
// Check events-based behavior methods have been renamed in
// expressions
REQUIRE(static_cast<gd::StandardEvent &>(
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
.GetEvent(0)) ==
"1 + "
"ObjectWithMyBehavior::MyBehavior."
"ObjectWithMyBehavior.MyBehavior::"
"MyRenamedBehaviorEventsFunctionExpression(123, 456, 789)");
// Check that a ill-named function that looks a bit like a behavior method
// (but which is actually an object function) is *not* renamed.
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(1)) ==
"2 + "
"ObjectWithMyBehavior::MyBehavior."
"MyBehaviorEventsFunctionExpression(123, 456, 789)");
// Check events based behaviors functions have been renamed in
// expressions referring to the function with just its name
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(2)) ==
"3 + "
"ObjectWithMyBehavior.MyBehavior::"
"MyRenamedBehaviorEventsFunctionExpression");
// Check that a ill-named function that looks a bit like a behavior method
// (but which is actually an object function) is *not* renamed.
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(3)) ==
"4 + "
"ObjectWithMyBehavior::MyBehavior."
"MyBehaviorEventsFunctionExpression");
}
SECTION("(Events based Behavior) events function parameter moved") {
gd::Project project;
@@ -742,39 +840,47 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
eventsExtension,
eventsBasedBehavior,
"MyBehaviorEventsFunction",
0, 2);
0,
2);
gd::WholeProjectRefactorer::MoveBehaviorEventsFunctionParameter(
project,
eventsExtension,
eventsBasedBehavior,
"MyBehaviorEventsFunctionExpression",
0, 2);
0,
2);
// Check if events based behaviors functions have been renamed in
// Check if parameters of events-based behavior methods have been moved in
// instructions
auto& action = static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0);
auto &action = static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0);
REQUIRE(action.GetParameter(0).GetPlainString() == "Second parameter");
REQUIRE(action.GetParameter(1).GetPlainString() == "Third parameter");
REQUIRE(action.GetParameter(2).GetPlainString() == "First parameter");
// Check events based behaviors functions have been renamed in
// Check parameters of events-based behavior methods have been moved in
// expressions
REQUIRE(static_cast<gd::StandardEvent &>(
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
.GetEvent(0)) ==
"1 + "
"ObjectWithMyBehavior::MyBehavior."
"ObjectWithMyBehavior.MyBehavior::"
"MyBehaviorEventsFunctionExpression(456, 789, 123)");
// Check that a ill-named function that looks a bit like a behavior method
// (but which is actually a free function) has its parameter *not* moved.
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(1)) ==
"2 + "
"ObjectWithMyBehavior::MyBehavior."
"MyBehaviorEventsFunctionExpression(123, 456, 789)");
}
SECTION("(Events based Behavior) property renamed") {
gd::Project project;
@@ -790,16 +896,26 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
"MyProperty",
"MyRenamedProperty");
// Check if events based behaviors property has been renamed in
// Check if events-based behaviors property has been renamed in
// instructions
REQUIRE(static_cast<gd::StandardEvent &>(
REQUIRE(
GetEventFirstActionType(project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(1)) ==
"MyEventsExtension::MyEventsBasedBehavior::"
"SetPropertyMyRenamedProperty");
REQUIRE(GetEventFirstConditionType(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(1))
.GetActions()
.Get(0)
.GetType() ==
.GetEvent(2)) ==
"MyEventsExtension::MyEventsBasedBehavior::"
"SetPropertyMyRenamedProperty");
"PropertyMyRenamedProperty");
REQUIRE(GetEventFirstActionFirstParameterString(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(3)) ==
"ObjectWithMyBehavior.MyBehavior::PropertyMyRenamedProperty()");
}
}

View File

@@ -1,6 +1,8 @@
#Clone SFML from its official directory using Git
# Clone SFML from its official directory using Git. Only
# do it if not already cloned to avoid triggering a whole
# recompilation every time CMake is run.
find_package(Git)
if(GIT_FOUND)
if(GIT_FOUND AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/SFML/readme.txt")
message( "Cloning SFML in ExtLibs/SFML with Git..." )
execute_process(
COMMAND ${GIT_EXECUTABLE} clone "https://www.github.com/SFML/SFML.git" SFML
@@ -11,7 +13,7 @@ if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} reset --hard 2.4.1
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/SFML
OUTPUT_QUIET)
OUTPUT_QUIET)
message( "Applying the patches..." )
file(GLOB SFML_PATCHES

View File

@@ -85,7 +85,8 @@ module.exports = {
objectProperties.set(
'fontFamily',
new gd.PropertyDescriptor(objectContent.fontFamily)
.setType('string')
.setType('resource')
.addExtraInfo('font')
.setLabel(_('Base font family'))
);
@@ -409,7 +410,7 @@ module.exports = {
objectsEditorService.registerEditorConfiguration(
'BBText::BBText',
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
helpPagePath: '/objects/bbtext_object',
helpPagePath: '/objects/bbtext',
})
);
},
@@ -515,11 +516,25 @@ module.exports = {
.getValue();
this._pixiObject.textStyles.default.fontSize = `${fontSize}px`;
const fontFamily = this._associatedObject
const fontResourceName = this._associatedObject
.getProperties(this.project)
.get('fontFamily')
.getValue();
this._pixiObject.textStyles.default.fontFamily = fontFamily;
if (this._fontResourceName !== fontResourceName) {
this._fontResourceName = fontResourceName;
this._pixiResourcesLoader
.loadFontFamily(this._project, fontResourceName)
.then(fontFamily => {
// Once the font is loaded, we can use the given fontFamily.
this._pixiObject.textStyles.default.fontFamily = fontFamily;
})
.catch(err => {
// Ignore errors
console.warn('Unable to load font family', err);
});
}
const wordWrap = this._associatedObject
.getProperties(this.project)

View File

@@ -13,7 +13,10 @@ gdjs.BBTextRuntimeObjectPixiRenderer = function(runtimeObject, runtimeScene) {
if (this._pixiObject === undefined) {
this._pixiObject = new MultiStyleText(runtimeObject._text, {
default: {
fontFamily: runtimeObject._fontFamily,
fontFamily: runtimeScene
.getGame()
.getFontManager()
.getFontFamily(runtimeObject._fontFamily),
fontSize: runtimeObject._fontSize + 'px',
fill: runtimeObject._color,
tagStyle: 'bbcode',

View File

@@ -50,13 +50,18 @@ function(gd_add_extension_target target_name source_files)
IF(target_name STREQUAL "")
MESSAGE(ERROR "You called gd_add_extension_target without specifying a target name")
ENDIF()
SET(platform_directory ${ARGV2})
IF(NOT platform_directory)
SET(platform_directory "CppPlatform")
ENDIF()
add_library(${target_name} SHARED ${source_files})
IF(EMSCRIPTEN)
# Emscripten treats all libraries as static libraries
add_library(${target_name} STATIC ${source_files})
ELSE()
add_library(${target_name} SHARED ${source_files})
ENDIF()
set_target_properties(${target_name} PROPERTIES PREFIX "")
set_target_properties(${target_name} PROPERTIES COMPILE_DEFINITIONS "${${target_name}_extra_definitions}")
set_target_properties(${target_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${GD_base_dir}/Binaries/Output/${CMAKE_BUILD_TYPE}_${CMAKE_SYSTEM_NAME}/${platform_directory}/Extensions")

View File

@@ -168,15 +168,14 @@ module.exports = {
_(
'Select option by number. Use this when the dialogue line is of type "options" and the player has pressed a button to change selected option.'
),
_('Select option by number'),
_('Select option at index _PARAM0_'),
_('Dialogue Tree (experimental)'),
'JsPlatform/Extensions/yarn24.png',
'JsPlatform/Extensions/yarn32.png'
)
.addParameter('expression', _('Option index number'), '', false)
.setDefaultValue('0')
.getCodeExtraInformation()
.setFunctionName('gdjs.dialogueTree.selectPreviousOption');
.setFunctionName('gdjs.dialogueTree.selectOption');
extension
.addAction(

View File

@@ -120,6 +120,7 @@ gdjs.dialogueTree.completeClippedTextScrolling = function() {
* Useful to prevent the user from skipping to next line before the current one has been printed fully.
*/
gdjs.dialogueTree.hasClippedScrollingCompleted = function() {
if (!this.dialogueIsRunning) return false;
if (this.dialogueData && this.dialogueText.length) {
return this.clipTextEnd >= this.dialogueText.length;
}
@@ -131,8 +132,8 @@ gdjs.dialogueTree.hasClippedScrollingCompleted = function() {
* Used with the scrollClippedText to achieve a classic scrolling text, as well as any <<wait>> effects to pause scrolling.
*/
gdjs.dialogueTree.getClippedLineText = function() {
return this.dialogueText.length
? this.dialogueText.substring(0, this.clipTextEnd)
return this.dialogueIsRunning && this.dialogueText.length
? this.dialogueText.substring(0, this.clipTextEnd + 1)
: '';
};
@@ -142,7 +143,9 @@ gdjs.dialogueTree.getClippedLineText = function() {
*/
gdjs.dialogueTree.getLineText = function() {
this.completeClippedTextScrolling();
return this.dialogueText.length ? this.dialogueText : '';
return this.dialogueIsRunning && this.dialogueText.length
? this.dialogueText
: '';
};
/**
@@ -177,6 +180,8 @@ gdjs.dialogueTree.getCommandParameter = function(paramIndex) {
* @param {string} command The command you want to check for being called. Write it without the `<<>>`.
*/
gdjs.dialogueTree.isCommandCalled = function(command) {
if (!this.dialogueIsRunning) return false;
var commandCalls = gdjs.dialogueTree.commandCalls;
var clipTextEnd = gdjs.dialogueTree.clipTextEnd;
var dialogueText = gdjs.dialogueTree.dialogueText;
@@ -225,7 +230,7 @@ gdjs.dialogueTree._cycledOptionIndex = function(optionIndex) {
* @param {number} optionIndex The index of the option you want to get
*/
gdjs.dialogueTree.getLineOption = function(optionIndex) {
if (!this.options.length) return [];
if (!this.dialogueIsRunning || !this.options.length) return [];
optionIndex = gdjs.dialogueTree._normalizedOptionIndex(optionIndex);
return this.options[optionIndex];
};
@@ -239,7 +244,7 @@ gdjs.dialogueTree.getLineOptionsText = function(
optionSelectionCursor,
addNewLine
) {
if (!this.options.length) return '';
if (!this.dialogueIsRunning || !this.options.length) return '';
var textResult = '';
this.options.forEach(function(optionText, index) {
if (index === gdjs.dialogueTree.selectedOption) {
@@ -266,7 +271,7 @@ gdjs.dialogueTree.getLineOptionsTextVertical = function(optionSelectionCursor) {
* @returns {number} The number of options
*/
gdjs.dialogueTree.getLineOptionsCount = function() {
if (this.options.length) {
if (this.dialogueIsRunning && this.options.length) {
return this.optionsCount;
}
return 0;
@@ -278,6 +283,7 @@ gdjs.dialogueTree.getLineOptionsCount = function() {
* This will advance the dialogue tree to the dialogue branch was selected by the player.
*/
gdjs.dialogueTree.confirmSelectOption = function() {
if (!this.dialogueIsRunning) return;
if (
this.dialogueData.select &&
!this.selectedOptionUpdated &&
@@ -301,6 +307,7 @@ gdjs.dialogueTree.confirmSelectOption = function() {
* Select next option during Options type line parsing. Hook this to your game input.
*/
gdjs.dialogueTree.selectNextOption = function() {
if (!this.dialogueIsRunning) return;
if (this.dialogueData.select) {
this.selectedOption += 1;
this.selectedOption = gdjs.dialogueTree._cycledOptionIndex(
@@ -314,6 +321,7 @@ gdjs.dialogueTree.selectNextOption = function() {
* Select previous option during Options type line parsing. Hook this to your game input.
*/
gdjs.dialogueTree.selectPreviousOption = function() {
if (!this.dialogueIsRunning) return;
if (this.dialogueData.select) {
this.selectedOption -= 1;
this.selectedOption = gdjs.dialogueTree._cycledOptionIndex(
@@ -328,9 +336,10 @@ gdjs.dialogueTree.selectPreviousOption = function() {
* @param {number} optionIndex The index of the option to select
*/
gdjs.dialogueTree.selectOption = function(optionIndex) {
if (!this.dialogueIsRunning) return;
if (this.dialogueData.select) {
this.selectedOption = gdjs.dialogueTree._normalizedOptionIndex(
this.selectedOption
optionIndex
);
this.selectedOptionUpdated = true;
}
@@ -341,6 +350,7 @@ gdjs.dialogueTree.selectOption = function(optionIndex) {
* @returns {number} The index of the currently selected option
*/
gdjs.dialogueTree.getSelectedOption = function() {
if (!this.dialogueIsRunning) return;
if (this.dialogueData.select) {
return this.selectedOption;
}
@@ -462,7 +472,8 @@ gdjs.dialogueTree.goToNextDialogueLine = function() {
this.dialogueText = this.dialogueData.text;
this.commandCalls = [];
} else {
this.dialogueText += this.dialogueData.text;
this.dialogueText +=
(this.dialogueText === '' ? '' : ' ') + this.dialogueData.text;
}
this.dialogueDataType = 'text';
@@ -472,11 +483,14 @@ gdjs.dialogueTree.goToNextDialogueLine = function() {
this.dialogueData = this.dialogue.next().value;
} else if (gdjs.dialogueTree._isLineTypeOptions()) {
this.dialogueDataType = 'options';
this.dialogueText = '';
this.clipTextEnd = 0;
this.optionsCount = this.dialogueData.options.length;
this.options = this.dialogueData.options;
this.selectedOptionUpdated = true;
} else if (gdjs.dialogueTree._isLineTypeCommand()) {
this.dialogueDataType = 'command';
this.clipTextEnd = 0;
var command = this.dialogueData.text.split(' ');
// If last command was to wait, increase time by one

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
gdjs.PixiFiltersTools.registerFilterCreator('BlendingMode', {
makePIXIFilter: function(layer, effectData) {
var blendingModeFilter = new PIXI.filters.AlphaFilter();
return blendingModeFilter;
},
update: function(filter, layer) {},
updateDoubleParameter: function(filter, parameterName, value) {
if (parameterName === 'alpha') {
filter.alpha = value;
}
if (parameterName === 'blendmode') {
filter.blendMode = value;
}
},
updateStringParameter: function(filter, parameterName, value) {},
updateBooleanParameter: function(filter, parameterName, value) {},
});

View File

@@ -14,7 +14,7 @@ gdjs.PixiFiltersTools.registerFilterCreator('Blur', {
return;
if (parameterName === 'kernelSize') {
value = gdjs.PixiFiltersTools.clampKernelSize(value);
value = gdjs.PixiFiltersTools.clampKernelSize(value, 5, 15);
}
filter[parameterName] = value;

View File

@@ -0,0 +1,27 @@
gdjs.PixiFiltersTools.registerFilterCreator('BulgePinch', {
makePIXIFilter: function(layer, effectData) {
var bulgePinchFilter = new PIXI.filters.BulgePinchFilter();
return bulgePinchFilter;
},
update: function(filter, layer) {
},
updateDoubleParameter: function(filter, parameterName, value) {
if (parameterName === 'centerX') {
filter.center[0] = value;
}
else if (parameterName === 'centerY') {
filter.center[1] = value;
}
else if (parameterName === 'radius') {
filter.radius = value;
}
else if (parameterName === 'strength') {
filter.strength = gdjs.PixiFiltersTools.clampValue(value, -1, 1);
}
},
updateStringParameter: function(filter, parameterName, value) {
},
updateBooleanParameter: function(filter, parameterName, value) {
},
});

View File

@@ -0,0 +1,22 @@
gdjs.PixiFiltersTools.registerFilterCreator('ColorReplace', {
makePIXIFilter: function(layer, effectData) {
var colorReplaceFilter = new PIXI.filters.ColorReplaceFilter();
return colorReplaceFilter;
},
update: function(filter, layer) {},
updateDoubleParameter: function(filter, parameterName, value) {
if (parameterName === 'epsilon') {
filter.epsilon = value;
}
},
updateStringParameter: function(filter, parameterName, value) {
if (parameterName === 'originalColor') {
filter.originalColor = parseInt(value.replace('#', '0x'), 16);
}
else if (parameterName === 'newColor') {
filter.newColor = parseInt(value.replace('#', '0x'), 16);
}
},
updateBooleanParameter: function(filter, parameterName, value) {},
});

View File

@@ -1,13 +1,20 @@
gdjs.PixiFiltersTools.registerFilterCreator('CRT', {
makePIXIFilter: function(layer, effectData) {
var crtFilter = new PIXI.filters.CRTFilter();
crtFilter._animationTimer = 0;
return crtFilter;
},
update: function(filter, layer) {
if (filter.animated) {
filter.time += layer.getElapsedTime() / 1000;
filter.seed = Math.random();
if (filter.animationSpeed !== 0) {
// Multiply by 10 so that the default value is a sensible speed
filter.time += layer.getElapsedTime() / 1000 * 10 * filter.animationSpeed;
}
if (filter.animationFrequency !== 0) {
filter._animationTimer += layer.getElapsedTime() / 1000;
if (filter._animationTimer >= 1 / filter.animationFrequency) {
filter.seed = Math.random();
filter._animationTimer = 0;
}
}
},
updateDoubleParameter: function(filter, parameterName, value) {
@@ -27,6 +34,10 @@ gdjs.PixiFiltersTools.registerFilterCreator('CRT', {
filter.vignettingAlpha = value;
} else if (parameterName === 'vignettingBlur') {
filter.vignettingBlur = value;
} else if (parameterName === 'animationSpeed') {
filter.animationSpeed = value;
} else if (parameterName === 'animationFrequency') {
filter.animationFrequency = value;
}
},
updateStringParameter: function(filter, parameterName, value) {},
@@ -34,8 +45,5 @@ gdjs.PixiFiltersTools.registerFilterCreator('CRT', {
if (parameterName === 'verticalLine') {
filter.verticalLine = value;
}
if (parameterName === 'animated') {
filter.animated = value;
}
},
});

View File

@@ -0,0 +1,18 @@
gdjs.PixiFiltersTools.registerFilterCreator('Dot', {
makePIXIFilter: function(layer, effectData) {
var dotFilter = new PIXI.filters.DotFilter();
return dotFilter;
},
update: function(filter, layer) {},
updateDoubleParameter: function(filter, parameterName, value) {
if (parameterName === 'scale') {
filter.scale = value;
}
else if (parameterName === 'angle') {
filter.angle = value;
}
},
updateStringParameter: function(filter, parameterName, value) {},
updateBooleanParameter: function(filter, parameterName, value) {},
});

View File

@@ -0,0 +1,64 @@
gdjs.PixiFiltersTools.registerFilterCreator('Glitch', {
makePIXIFilter: function(layer, effectData) {
var glitchFilter = new PIXI.filters.GlitchFilter();
glitchFilter._animationTimer = 0;
return glitchFilter;
},
update: function(filter, layer) {
if (filter.animationFrequency !== 0) {
filter._animationTimer += layer.getElapsedTime() / 1000;
if (filter._animationTimer >= 1 / filter.animationFrequency) {
filter.seed = Math.random();
filter._animationTimer = 0;
}
}
},
updateDoubleParameter: function(filter, parameterName, value) {
if (parameterName === 'slices') {
filter.slices = value;
}
else if (parameterName === 'offset') {
filter.offset = value;
}
else if (parameterName === 'direction') {
filter.direction = value;
}
else if (parameterName === 'fillMode') {
filter.fillMode = value;
}
else if (parameterName === 'minSize') {
filter.minSize = value;
}
else if (parameterName === 'sampleSize') {
filter.sampleSize = value;
}
else if (parameterName === 'redX') {
filter.red.x = value;
}
else if (parameterName === 'redY') {
filter.red.y = value;
}
else if (parameterName === 'greenX') {
filter.green.x = value;
}
else if (parameterName === 'greenY') {
filter.green.y = value;
}
else if (parameterName === 'blueX') {
filter.blue.x = value;
}
else if (parameterName === 'blueY') {
filter.blue.y = value;
}
else if (parameterName === 'animationFrequency') {
filter.animationFrequency = value;
}
},
updateStringParameter: function(filter, parameterName, value) {},
updateBooleanParameter: function(filter, parameterName, value) {
if (parameterName === 'average') {
filter.average = value;
}
},
});

View File

@@ -5,8 +5,8 @@ gdjs.PixiFiltersTools.registerFilterCreator('Godray', {
return godrayFilter;
},
update: function(filter, layer) {
if (filter.animated) {
filter.time += layer.getElapsedTime() / 1000;
if (filter.animationSpeed !== 0) {
filter.time += layer.getElapsedTime() / 1000 * filter.animationSpeed;
}
},
updateDoubleParameter: function(filter, parameterName, value) {
@@ -22,6 +22,8 @@ gdjs.PixiFiltersTools.registerFilterCreator('Godray', {
filter.x = value;
} else if (parameterName === 'y') {
filter.y = value;
} else if (parameterName === 'animationSpeed') {
filter.animationSpeed = value;
}
},
updateStringParameter: function(filter, parameterName, value) {},
@@ -29,8 +31,5 @@ gdjs.PixiFiltersTools.registerFilterCreator('Godray', {
if (parameterName === 'parallel') {
filter.parallel = value;
}
if (parameterName === 'animated') {
filter.animated = value;
}
},
});

View File

@@ -0,0 +1,50 @@
gdjs.PixiFiltersTools.registerFilterCreator('OldFilm', {
makePIXIFilter: function(layer, effectData) {
var oldFilmFilter = new PIXI.filters.OldFilmFilter();
oldFilmFilter._animationTimer = 0;
return oldFilmFilter;
},
update: function(filter, layer) {
if (filter.animationFrequency !== 0) {
filter._animationTimer += layer.getElapsedTime() / 1000;
if (filter._animationTimer >= 1 / filter.animationFrequency) {
filter.seed = Math.random();
filter._animationTimer = 0;
}
}
},
updateDoubleParameter: function(filter, parameterName, value) {
if (parameterName === 'sepia') {
filter.sepia = value;
}
else if (parameterName === 'noise') {
filter.noise = value;
}
else if (parameterName === 'noiseSize') {
filter.noiseSize = value;
}
else if (parameterName === 'scratch') {
filter.scratch = value;
}
else if (parameterName === 'scratchDensity') {
filter.scratchDensity = value;
}
else if (parameterName === 'scratchWidth') {
filter.scratchWidth = value;
}
else if (parameterName === 'vignetting') {
filter.vignetting = value;
}
else if (parameterName === 'vignettingAlpha') {
filter.vignettingAlpha = value;
}
else if (parameterName === 'vignettingBlur') {
filter.vignettingBlur = value;
}
else if (parameterName === 'animationFrequency') {
filter.animationFrequency = value;
}
},
updateStringParameter: function(filter, parameterName, value) {},
updateBooleanParameter: function(filter, parameterName, value) {},
});

View File

@@ -0,0 +1,9 @@
/*!
* @pixi/filter-alpha - v5.2.1
* Compiled Tue, 28 Jan 2020 23:33:11 UTC
*
* @pixi/filter-alpha is licensed under the MIT License.
* http://www.opensource.org/licenses/mit-license
*/
this.PIXI=this.PIXI||{},this.PIXI.filters=this.PIXI.filters||{};var _pixi_filter_alpha=function(t,r){"use strict";var e="varying vec2 vTextureCoord;\n\nuniform sampler2D uSampler;\nuniform float uAlpha;\n\nvoid main(void)\n{\n gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha;\n}\n",i=function(t){function i(i){void 0===i&&(i=1),t.call(this,r.defaultVertex,e,{uAlpha:1}),this.alpha=i}t&&(i.__proto__=t),i.prototype=Object.create(t&&t.prototype),i.prototype.constructor=i;var a={alpha:{configurable:!0}};return a.alpha.get=function(){return this.uniforms.uAlpha},a.alpha.set=function(t){this.uniforms.uAlpha=t},Object.defineProperties(i.prototype,a),i}(r.Filter);return t.AlphaFilter=i,t}({},PIXI);Object.assign(this.PIXI.filters,_pixi_filter_alpha);
//# sourceMappingURL=filter-alpha.min.js.map

View File

@@ -0,0 +1,9 @@
/*!
* @pixi/filter-bulge-pinch - v2.7.0
* Compiled Sun, 13 Jan 2019 22:51:52 UTC
*
* @pixi/filter-bulge-pinch is licensed under the MIT License.
* http://www.opensource.org/licenses/mit-license
*/
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("pixi.js")):"function"==typeof define&&define.amd?define(["exports","pixi.js"],n):n((e=e||self).__filters={},e.PIXI)}(this,function(e,n){"use strict";var t="attribute vec2 aVertexPosition;\nattribute vec2 aTextureCoord;\n\nuniform mat3 projectionMatrix;\n\nvarying vec2 vTextureCoord;\n\nvoid main(void)\n{\n gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);\n vTextureCoord = aTextureCoord;\n}",r="uniform float radius;\nuniform float strength;\nuniform vec2 center;\nuniform sampler2D uSampler;\nvarying vec2 vTextureCoord;\n\nuniform vec4 filterArea;\nuniform vec4 filterClamp;\nuniform vec2 dimensions;\n\nvoid main()\n{\n vec2 coord = vTextureCoord * filterArea.xy;\n coord -= center * dimensions.xy;\n float distance = length(coord);\n if (distance < radius) {\n float percent = distance / radius;\n if (strength > 0.0) {\n coord *= mix(1.0, smoothstep(0.0, radius / distance, percent), strength * 0.75);\n } else {\n coord *= mix(1.0, pow(percent, 1.0 + strength * 0.75) * radius / distance, 1.0 - percent);\n }\n }\n coord += center * dimensions.xy;\n coord /= filterArea.xy;\n vec2 clampedCoord = clamp(coord, filterClamp.xy, filterClamp.zw);\n vec4 color = texture2D(uSampler, clampedCoord);\n if (coord != clampedCoord) {\n color *= max(0.0, 1.0 - length(coord - clampedCoord));\n }\n\n gl_FragColor = color;\n}\n",o=function(e){function n(n,o,i){e.call(this,t,r),this.uniforms.dimensions=new Float32Array(2),this.center=n||[.5,.5],this.radius="number"==typeof o?o:100,this.strength="number"==typeof i?i:1}e&&(n.__proto__=e),n.prototype=Object.create(e&&e.prototype),n.prototype.constructor=n;var o={radius:{configurable:!0},strength:{configurable:!0},center:{configurable:!0}};return n.prototype.apply=function(e,n,t,r){this.uniforms.dimensions[0]=n.sourceFrame.width,this.uniforms.dimensions[1]=n.sourceFrame.height,e.applyFilter(this,n,t,r)},o.radius.get=function(){return this.uniforms.radius},o.radius.set=function(e){this.uniforms.radius=e},o.strength.get=function(){return this.uniforms.strength},o.strength.set=function(e){this.uniforms.strength=e},o.center.get=function(){return this.uniforms.center},o.center.set=function(e){this.uniforms.center=e},Object.defineProperties(n.prototype,o),n}(n.Filter);e.BulgePinchFilter=o,Object.defineProperty(e,"__esModule",{value:!0})}),Object.assign(PIXI.filters,this?this.__filters:__filters);
//# sourceMappingURL=filter-bulge-pinch.js.map

View File

@@ -0,0 +1,9 @@
/*!
* @pixi/filter-color-replace - v2.7.0
* Compiled Sun, 13 Jan 2019 22:51:52 UTC
*
* @pixi/filter-color-replace is licensed under the MIT License.
* http://www.opensource.org/licenses/mit-license
*/
!function(o,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("pixi.js")):"function"==typeof define&&define.amd?define(["exports","pixi.js"],r):r((o=o||self).__filters={},o.PIXI)}(this,function(o,r){"use strict";var e="attribute vec2 aVertexPosition;\nattribute vec2 aTextureCoord;\n\nuniform mat3 projectionMatrix;\n\nvarying vec2 vTextureCoord;\n\nvoid main(void)\n{\n gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);\n vTextureCoord = aTextureCoord;\n}",i="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nuniform vec3 originalColor;\nuniform vec3 newColor;\nuniform float epsilon;\nvoid main(void) {\n vec4 currentColor = texture2D(uSampler, vTextureCoord);\n vec3 colorDiff = originalColor - (currentColor.rgb / max(currentColor.a, 0.0000000001));\n float colorDistance = length(colorDiff);\n float doReplace = step(colorDistance, epsilon);\n gl_FragColor = vec4(mix(currentColor.rgb, (newColor + colorDiff) * currentColor.a, doReplace), currentColor.a);\n}\n",n=function(o){function n(r,n,t){void 0===r&&(r=16711680),void 0===n&&(n=0),void 0===t&&(t=.4),o.call(this,e,i),this.uniforms.originalColor=new Float32Array(3),this.uniforms.newColor=new Float32Array(3),this.originalColor=r,this.newColor=n,this.epsilon=t}o&&(n.__proto__=o),n.prototype=Object.create(o&&o.prototype),n.prototype.constructor=n;var t={originalColor:{configurable:!0},newColor:{configurable:!0},epsilon:{configurable:!0}};return t.originalColor.set=function(o){var e=this.uniforms.originalColor;"number"==typeof o?(r.utils.hex2rgb(o,e),this._originalColor=o):(e[0]=o[0],e[1]=o[1],e[2]=o[2],this._originalColor=r.utils.rgb2hex(e))},t.originalColor.get=function(){return this._originalColor},t.newColor.set=function(o){var e=this.uniforms.newColor;"number"==typeof o?(r.utils.hex2rgb(o,e),this._newColor=o):(e[0]=o[0],e[1]=o[1],e[2]=o[2],this._newColor=r.utils.rgb2hex(e))},t.newColor.get=function(){return this._newColor},t.epsilon.set=function(o){this.uniforms.epsilon=o},t.epsilon.get=function(){return this.uniforms.epsilon},Object.defineProperties(n.prototype,t),n}(r.Filter);o.ColorReplaceFilter=n,Object.defineProperty(o,"__esModule",{value:!0})}),Object.assign(PIXI.filters,this?this.__filters:__filters);
//# sourceMappingURL=filter-color-replace.js.map

View File

@@ -0,0 +1,9 @@
/*!
* @pixi/filter-dot - v2.7.0
* Compiled Sun, 13 Jan 2019 22:51:52 UTC
*
* @pixi/filter-dot is licensed under the MIT License.
* http://www.opensource.org/licenses/mit-license
*/
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("pixi.js")):"function"==typeof define&&define.amd?define(["exports","pixi.js"],n):n((e=e||self).__filters={},e.PIXI)}(this,function(e,n){"use strict";var t="attribute vec2 aVertexPosition;\nattribute vec2 aTextureCoord;\n\nuniform mat3 projectionMatrix;\n\nvarying vec2 vTextureCoord;\n\nvoid main(void)\n{\n gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);\n vTextureCoord = aTextureCoord;\n}",o="precision mediump float;\n\nvarying vec2 vTextureCoord;\nvarying vec4 vColor;\n\nuniform vec4 filterArea;\nuniform sampler2D uSampler;\n\nuniform float angle;\nuniform float scale;\n\nfloat pattern()\n{\n float s = sin(angle), c = cos(angle);\n vec2 tex = vTextureCoord * filterArea.xy;\n vec2 point = vec2(\n c * tex.x - s * tex.y,\n s * tex.x + c * tex.y\n ) * scale;\n return (sin(point.x) * sin(point.y)) * 4.0;\n}\n\nvoid main()\n{\n vec4 color = texture2D(uSampler, vTextureCoord);\n float average = (color.r + color.g + color.b) / 3.0;\n gl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\n}\n",r=function(e){function n(n,r){void 0===n&&(n=1),void 0===r&&(r=5),e.call(this,t,o),this.scale=n,this.angle=r}e&&(n.__proto__=e),n.prototype=Object.create(e&&e.prototype),n.prototype.constructor=n;var r={scale:{configurable:!0},angle:{configurable:!0}};return r.scale.get=function(){return this.uniforms.scale},r.scale.set=function(e){this.uniforms.scale=e},r.angle.get=function(){return this.uniforms.angle},r.angle.set=function(e){this.uniforms.angle=e},Object.defineProperties(n.prototype,r),n}(n.Filter);e.DotFilter=r,Object.defineProperty(e,"__esModule",{value:!0})}),Object.assign(PIXI.filters,this?this.__filters:__filters);
//# sourceMappingURL=filter-dot.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
/*!
* @pixi/filter-radial-blur - v2.7.0
* Compiled Sun, 13 Jan 2019 22:51:52 UTC
*
* @pixi/filter-radial-blur is licensed under the MIT License.
* http://www.opensource.org/licenses/mit-license
*/
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("pixi.js")):"function"==typeof define&&define.amd?define(["exports","pixi.js"],n):n((e=e||self).__filters={},e.PIXI)}(this,function(e,n){"use strict";var t="attribute vec2 aVertexPosition;\nattribute vec2 aTextureCoord;\n\nuniform mat3 projectionMatrix;\n\nvarying vec2 vTextureCoord;\n\nvoid main(void)\n{\n gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);\n vTextureCoord = aTextureCoord;\n}",r="varying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nuniform vec4 filterArea;\n\nuniform float uRadian;\nuniform vec2 uCenter;\nuniform float uRadius;\nuniform int uKernelSize;\n\nconst int MAX_KERNEL_SIZE = 2048;\n\nvoid main(void)\n{\n vec4 color = texture2D(uSampler, vTextureCoord);\n\n if (uKernelSize == 0)\n {\n gl_FragColor = color;\n return;\n }\n\n float aspect = filterArea.y / filterArea.x;\n vec2 center = uCenter.xy / filterArea.xy;\n float gradient = uRadius / filterArea.x * 0.3;\n float radius = uRadius / filterArea.x - gradient * 0.5;\n int k = uKernelSize - 1;\n\n vec2 coord = vTextureCoord;\n vec2 dir = vec2(center - coord);\n float dist = length(vec2(dir.x, dir.y * aspect));\n\n float radianStep = uRadian;\n if (radius >= 0.0 && dist > radius) {\n float delta = dist - radius;\n float gap = gradient;\n float scale = 1.0 - abs(delta / gap);\n if (scale <= 0.0) {\n gl_FragColor = color;\n return;\n }\n radianStep *= scale;\n }\n radianStep /= float(k);\n\n float s = sin(radianStep);\n float c = cos(radianStep);\n mat2 rotationMatrix = mat2(vec2(c, -s), vec2(s, c));\n\n for(int i = 0; i < MAX_KERNEL_SIZE - 1; i++) {\n if (i == k) {\n break;\n }\n\n coord -= center;\n coord.y *= aspect;\n coord = rotationMatrix * coord;\n coord.y /= aspect;\n coord += center;\n\n vec4 sample = texture2D(uSampler, coord);\n\n // switch to pre-multiplied alpha to correctly blur transparent images\n // sample.rgb *= sample.a;\n\n color += sample;\n }\n\n gl_FragColor = color / float(uKernelSize);\n}\n",i=function(e){function n(n,i,o,a){void 0===n&&(n=0),void 0===i&&(i=[0,0]),void 0===o&&(o=5),void 0===a&&(a=-1),e.call(this,t,r),this._angle=0,this.angle=n,this.center=i,this.kernelSize=o,this.radius=a}e&&(n.__proto__=e),n.prototype=Object.create(e&&e.prototype),n.prototype.constructor=n;var i={angle:{configurable:!0},center:{configurable:!0},radius:{configurable:!0}};return n.prototype.apply=function(e,n,t,r){this.uniforms.uKernelSize=0!==this._angle?this.kernelSize:0,e.applyFilter(this,n,t,r)},i.angle.set=function(e){this._angle=e,this.uniforms.uRadian=e*Math.PI/180},i.angle.get=function(){return this._angle},i.center.get=function(){return this.uniforms.uCenter},i.center.set=function(e){this.uniforms.uCenter=e},i.radius.get=function(){return this.uniforms.uRadius},i.radius.set=function(e){(e<0||e===1/0)&&(e=-1),this.uniforms.uRadius=e},Object.defineProperties(n.prototype,i),n}(n.Filter);e.RadialBlurFilter=i,Object.defineProperty(e,"__esModule",{value:!0})}),Object.assign(PIXI.filters,this?this.__filters:__filters);
//# sourceMappingURL=filter-radial-blur.js.map

View File

@@ -0,0 +1,9 @@
/*!
* @pixi/filter-twist - v2.7.0
* Compiled Sun, 13 Jan 2019 22:51:52 UTC
*
* @pixi/filter-twist is licensed under the MIT License.
* http://www.opensource.org/licenses/mit-license
*/
!function(o,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("pixi.js")):"function"==typeof define&&define.amd?define(["exports","pixi.js"],n):n((o=o||self).__filters={},o.PIXI)}(this,function(o,n){"use strict";var r="attribute vec2 aVertexPosition;\nattribute vec2 aTextureCoord;\n\nuniform mat3 projectionMatrix;\n\nvarying vec2 vTextureCoord;\n\nvoid main(void)\n{\n gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);\n vTextureCoord = aTextureCoord;\n}",e="varying vec2 vTextureCoord;\n\nuniform sampler2D uSampler;\nuniform float radius;\nuniform float angle;\nuniform vec2 offset;\nuniform vec4 filterArea;\n\nvec2 mapCoord( vec2 coord )\n{\n coord *= filterArea.xy;\n coord += filterArea.zw;\n\n return coord;\n}\n\nvec2 unmapCoord( vec2 coord )\n{\n coord -= filterArea.zw;\n coord /= filterArea.xy;\n\n return coord;\n}\n\nvec2 twist(vec2 coord)\n{\n coord -= offset;\n\n float dist = length(coord);\n\n if (dist < radius)\n {\n float ratioDist = (radius - dist) / radius;\n float angleMod = ratioDist * ratioDist * angle;\n float s = sin(angleMod);\n float c = cos(angleMod);\n coord = vec2(coord.x * c - coord.y * s, coord.x * s + coord.y * c);\n }\n\n coord += offset;\n\n return coord;\n}\n\nvoid main(void)\n{\n\n vec2 coord = mapCoord(vTextureCoord);\n\n coord = twist(coord);\n\n coord = unmapCoord(coord);\n\n gl_FragColor = texture2D(uSampler, coord );\n\n}\n",t=function(o){function n(n,t,i){void 0===n&&(n=200),void 0===t&&(t=4),void 0===i&&(i=20),o.call(this,r,e),this.radius=n,this.angle=t,this.padding=i}o&&(n.__proto__=o),n.prototype=Object.create(o&&o.prototype),n.prototype.constructor=n;var t={offset:{configurable:!0},radius:{configurable:!0},angle:{configurable:!0}};return t.offset.get=function(){return this.uniforms.offset},t.offset.set=function(o){this.uniforms.offset=o},t.radius.get=function(){return this.uniforms.radius},t.radius.set=function(o){this.uniforms.radius=o},t.angle.get=function(){return this.uniforms.angle},t.angle.set=function(o){this.uniforms.angle=o},Object.defineProperties(n.prototype,t),n}(n.Filter);o.TwistFilter=t,Object.defineProperty(o,"__esModule",{value:!0})}),Object.assign(PIXI.filters,this?this.__filters:__filters);
//# sourceMappingURL=filter-twist.js.map

View File

@@ -0,0 +1,32 @@
gdjs.PixiFiltersTools.registerFilterCreator('RadialBlur', {
makePIXIFilter: function(layer, effectData) {
var radialBlurFilter = new PIXI.filters.RadialBlurFilter();
return radialBlurFilter;
},
update: function(filter, layer) {
filter.center[0] = Math.round(filter._centerX * layer.getWidth());
filter.center[1] = Math.round(filter._centerY * layer.getHeight());
},
updateDoubleParameter: function(filter, parameterName, value) {
if (parameterName === 'radius') {
filter.radius = (value < 0 ? -1 : value);
}
else if (parameterName === 'angle') {
filter.angle = value;
}
else if (parameterName === 'kernelSize') {
filter.kernelSize = gdjs.PixiFiltersTools.clampKernelSize(value, 3, 25);
}
else if (parameterName === 'centerX') {
filter._centerX = value;
}
else if (parameterName === 'centerY') {
filter._centerY = value;
}
},
updateStringParameter: function(filter, parameterName, value) {
},
updateBooleanParameter: function(filter, parameterName, value) {
},
});

View File

@@ -23,8 +23,8 @@ gdjs.PixiFiltersTools.registerFilterCreator("Reflection", {
return reflectionFilter;
},
update: function(filter, layer) {
if (filter.animated) {
filter.time -= layer.getElapsedTime() / 1000;
if (filter.animationSpeed !== 0) {
filter.time += layer.getElapsedTime() / 1000 * filter.animationSpeed;
}
},
updateDoubleParameter: function(filter, parameterName, value) {
@@ -55,14 +55,15 @@ gdjs.PixiFiltersTools.registerFilterCreator("Reflection", {
if (parameterName === "alphaEnding") {
filter.alpha[1] = value;
}
if (parameterName === "animationSpeed") {
filter.animationSpeed = value;
}
},
updateStringParameter: function(filter, parameterName, value) {},
updateBooleanParameter: function(filter, parameterName, value) {
if (parameterName === "mirror") {
filter.mirror = value;
}
if (parameterName === "animated") {
filter.animated = value;
}
}
});

View File

@@ -0,0 +1,32 @@
gdjs.PixiFiltersTools.registerFilterCreator('Twist', {
makePIXIFilter: function(layer, effectData) {
var twistFilter = new PIXI.filters.TwistFilter();
return twistFilter;
},
update: function(filter, layer) {
filter.offset[0] = Math.round(filter._offsetX * layer.getWidth());
filter.offset[1] = Math.round(filter._offsetY * layer.getHeight());
},
updateDoubleParameter: function(filter, parameterName, value) {
if (parameterName === 'radius') {
filter.radius = value;
}
else if (parameterName === 'angle') {
filter.angle = value;
}
else if (parameterName === 'padding') {
filter.padding = value;
}
else if (parameterName === 'offsetX') {
filter._offsetX = value;
}
else if (parameterName === 'offsetY') {
filter._offsetY = value;
}
},
updateStringParameter: function(filter, parameterName, value) {
},
updateBooleanParameter: function(filter, parameterName, value) {
},
});

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