summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.appveyor.yml55
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md11
-rw-r--r--.github/workflows/android_builds.yml72
-rw-r--r--.github/workflows/ios_builds.yml50
-rw-r--r--.github/workflows/javascript_builds.yml76
-rw-r--r--.github/workflows/linux_builds.yml122
-rw-r--r--.github/workflows/macos_builds.yml99
-rw-r--r--.github/workflows/static_checks.yml5
-rw-r--r--.github/workflows/windows_builds.yml (renamed from .github/workflows/main.yml)55
-rw-r--r--.gitignore6
-rw-r--r--.travis.yml136
-rw-r--r--CHANGELOG.md4
-rw-r--r--COPYRIGHT.txt5
-rw-r--r--README.md3
-rw-r--r--SConstruct20
-rw-r--r--core/bind/core_bind.cpp5
-rw-r--r--core/callable_method_pointer.h4
-rw-r--r--core/class_db.cpp166
-rw-r--r--core/class_db.h8
-rw-r--r--core/container_type_validate.h2
-rw-r--r--core/global_constants.cpp32
-rw-r--r--core/global_constants.h1
-rw-r--r--core/io/http_client.cpp2
-rw-r--r--core/io/json.cpp2
-rw-r--r--core/math/aabb.h4
-rw-r--r--core/math/geometry_3d.cpp2
-rw-r--r--core/math/plane.cpp4
-rw-r--r--core/math/plane.h1
-rw-r--r--core/object.cpp2
-rw-r--r--core/project_settings.cpp19
-rw-r--r--core/project_settings.h10
-rw-r--r--core/script_language.h3
-rw-r--r--core/string_name.h4
-rw-r--r--core/ustring.cpp6
-rw-r--r--core/variant.cpp4
-rw-r--r--core/variant_call.cpp4
-rw-r--r--doc/classes/@GlobalScope.xml2
-rw-r--r--doc/classes/AABB.xml16
-rw-r--r--doc/classes/AESContext.xml2
-rw-r--r--doc/classes/BaseButton.xml3
-rw-r--r--doc/classes/Basis.xml35
-rw-r--r--doc/classes/Color.xml26
-rw-r--r--doc/classes/Control.xml4
-rw-r--r--doc/classes/DynamicFont.xml8
-rw-r--r--doc/classes/EditorFileSystem.xml1
-rw-r--r--doc/classes/EditorFileSystemDirectory.xml2
-rw-r--r--doc/classes/EditorImportPlugin.xml12
-rw-r--r--doc/classes/EditorInspector.xml15
-rw-r--r--doc/classes/EditorInterface.xml26
-rw-r--r--doc/classes/EditorNode3DGizmo.xml9
-rw-r--r--doc/classes/EditorNode3DGizmoPlugin.xml2
-rw-r--r--doc/classes/EditorProperty.xml14
-rw-r--r--doc/classes/EditorSettings.xml33
-rw-r--r--doc/classes/File.xml1
-rw-r--r--doc/classes/HTTPClient.xml1
-rw-r--r--doc/classes/HTTPRequest.xml1
-rw-r--r--doc/classes/InputEventMouseMotion.xml1
-rw-r--r--doc/classes/LineEdit.xml3
-rw-r--r--doc/classes/OS.xml15
-rw-r--r--doc/classes/Plane.xml29
-rw-r--r--doc/classes/PrimitiveMesh.xml7
-rw-r--r--doc/classes/ProjectSettings.xml6
-rw-r--r--doc/classes/Quat.xml41
-rw-r--r--doc/classes/Rect2.xml7
-rw-r--r--doc/classes/Rect2i.xml7
-rw-r--r--doc/classes/RigidBody3D.xml7
-rw-r--r--doc/classes/Skeleton3D.xml4
-rw-r--r--doc/classes/SpringArm3D.xml2
-rw-r--r--doc/classes/String.xml4
-rw-r--r--doc/classes/TileSet.xml12
-rw-r--r--doc/classes/Transform.xml16
-rw-r--r--doc/classes/Transform2D.xml34
-rw-r--r--doc/classes/Vector2.xml65
-rw-r--r--doc/classes/Vector2i.xml12
-rw-r--r--doc/classes/Vector3.xml70
-rw-r--r--doc/classes/Vector3i.xml16
-rw-r--r--doc/classes/Viewport.xml2
-rw-r--r--doc/classes/VisibilityEnabler2D.xml3
-rw-r--r--doc/classes/VisibilityEnabler3D.xml3
-rw-r--r--doc/classes/VisibilityNotifier2D.xml3
-rw-r--r--doc/classes/VisibilityNotifier3D.xml3
-rw-r--r--doc/classes/VisualShaderNodeOutput.xml2
-rw-r--r--doc/classes/VisualShaderNodeVectorRefract.xml2
-rw-r--r--drivers/vulkan/SCsub32
-rw-r--r--drivers/vulkan/rendering_device_vulkan.cpp50
-rw-r--r--drivers/vulkan/rendering_device_vulkan.h8
-rw-r--r--drivers/vulkan/vulkan_context.cpp2
-rw-r--r--editor/animation_track_editor.cpp4
-rw-r--r--editor/code_editor.cpp3
-rw-r--r--editor/create_dialog.cpp3
-rw-r--r--editor/doc_data.cpp32
-rw-r--r--editor/doc_data.h1
-rw-r--r--editor/editor_autoload_settings.cpp4
-rw-r--r--editor/editor_file_dialog.cpp2
-rw-r--r--editor/editor_node.cpp12
-rw-r--r--editor/editor_properties.cpp70
-rw-r--r--editor/import/editor_scene_importer_gltf.cpp8
-rw-r--r--editor/import/resource_importer_layered_texture.cpp2
-rw-r--r--editor/input_map_editor.cpp70
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp2
-rw-r--r--editor/plugins/animation_tree_editor_plugin.cpp2
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp34
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h1
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp10
-rw-r--r--editor/plugins/script_editor_plugin.cpp67
-rw-r--r--editor/plugins/script_editor_plugin.h1
-rw-r--r--editor/plugins/script_text_editor.cpp234
-rw-r--r--editor/plugins/script_text_editor.h43
-rw-r--r--editor/plugins/text_editor.cpp42
-rw-r--r--editor/plugins/text_editor.h21
-rw-r--r--editor/plugins/theme_editor_plugin.cpp4
-rw-r--r--editor/plugins/tile_set_editor_plugin.cpp24
-rw-r--r--editor/rename_dialog.cpp20
-rw-r--r--editor/scene_tree_dock.cpp9
-rw-r--r--editor/script_create_dialog.cpp4
-rw-r--r--main/SCsub4
-rw-r--r--main/main.cpp396
-rw-r--r--main/main.h17
-rw-r--r--main/tests/test_gdscript.cpp1035
-rw-r--r--main/tests/test_string.cpp1207
-rw-r--r--main/tests/test_string.h42
-rw-r--r--methods.py2
-rwxr-xr-xmisc/ci/android-tools-linux.sh (renamed from misc/travis/android-tools-linux.sh)0
-rw-r--r--misc/ci/sources.list4
-rw-r--r--misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj12
-rw-r--r--misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json7
-rwxr-xr-xmisc/travis/ccache-osx.sh43
-rw-r--r--modules/arkit/arkit_interface.h34
-rw-r--r--modules/arkit/arkit_interface.mm371
-rw-r--r--modules/arkit/arkit_session_delegate.h6
-rw-r--r--modules/bullet/space_bullet.cpp15
-rw-r--r--modules/camera/camera_ios.mm93
-rw-r--r--modules/gdnative/gdnative/aabb.cpp7
-rw-r--r--modules/gdnative/gdnative/plane.cpp7
-rw-r--r--modules/gdnative/gdnative_api.json14
-rw-r--r--modules/gdnative/include/gdnative/aabb.h2
-rw-r--r--modules/gdnative/include/gdnative/plane.h2
-rw-r--r--modules/gdnative/register_types.cpp2
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml64
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp4
-rw-r--r--modules/gdscript/gdscript.cpp484
-rw-r--r--modules/gdscript/gdscript.h50
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp3062
-rw-r--r--modules/gdscript/gdscript_analyzer.h118
-rw-r--r--modules/gdscript/gdscript_cache.cpp244
-rw-r--r--modules/gdscript/gdscript_cache.h97
-rw-r--r--modules/gdscript/gdscript_compiler.cpp2771
-rw-r--r--modules/gdscript/gdscript_compiler.h22
-rw-r--r--modules/gdscript/gdscript_editor.cpp3474
-rw-r--r--modules/gdscript/gdscript_function.cpp194
-rw-r--r--modules/gdscript/gdscript_function.h6
-rw-r--r--modules/gdscript/gdscript_functions.cpp4
-rw-r--r--modules/gdscript/gdscript_parser.cpp11226
-rw-r--r--modules/gdscript/gdscript_parser.h1617
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp2458
-rw-r--r--modules/gdscript/gdscript_tokenizer.h445
-rw-r--r--modules/gdscript/gdscript_warning.cpp210
-rw-r--r--modules/gdscript/gdscript_warning.h87
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp595
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp2
-rw-r--r--modules/gdscript/register_types.cpp68
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs9
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs8
-rw-r--r--modules/visual_script/visual_script.cpp8
-rw-r--r--modules/visual_script/visual_script_editor.cpp5
-rw-r--r--modules/visual_script/visual_script_editor.h1
-rw-r--r--platform/android/android_keys_utils.h4
-rw-r--r--platform/android/detect.py2
-rw-r--r--platform/android/display_server_android.cpp26
-rw-r--r--platform/android/display_server_android.h7
-rw-r--r--platform/android/export/export.cpp204
-rw-r--r--platform/android/export/gradle_export_util.h146
-rw-r--r--platform/android/java/app/build.gradle2
-rw-r--r--platform/android/java/app/config.gradle16
-rw-r--r--platform/android/java/app/res/values-ar/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-ar/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-bg/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-bg/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-ca/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-ca/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-cs/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-cs/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-da/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-da/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-de/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-de/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-el/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-el/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-en/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-en/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-es-rES/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-es-rES/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-es/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-es/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-fa/godot_project_name_string.xml5
-rw-r--r--platform/android/java/app/res/values-fi/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-fi/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-fr/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-fr/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-hi/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-hi/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-hr/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-hr/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-hu/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-hu/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-in/godot_project_name_string.xml5
-rw-r--r--platform/android/java/app/res/values-it/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-it/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-iw/godot_project_name_string.xml5
-rw-r--r--platform/android/java/app/res/values-ja/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-ja/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-ko/godot_project_name_string.xml5
-rw-r--r--platform/android/java/app/res/values-lt/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-lt/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-lv/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-lv/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-nb/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-nb/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-nl/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-nl/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-pl/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-pl/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-pt/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-pt/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-ro/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-ro/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-ru/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-ru/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-sk/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-sk/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-sl/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-sl/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-sr/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-sr/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-sv/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-sv/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-th/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-th/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-tl/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-tl/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-tr/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-tr/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-uk/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-uk/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-vi/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-vi/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-zh-rHK/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-zh-rHK/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-zh-rTW/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-zh-rTW/strings.xml)1
-rw-r--r--platform/android/java/app/res/values-zh/godot_project_name_string.xml (renamed from platform/android/java/lib/res/values-zh-rCN/strings.xml)1
-rw-r--r--platform/android/java/app/res/values/godot_project_name_string.xml5
-rw-r--r--platform/android/java/lib/res/values-fa/strings.xml1
-rw-r--r--platform/android/java/lib/res/values-in/strings.xml4
-rw-r--r--platform/android/java/lib/res/values-iw/strings.xml4
-rw-r--r--platform/android/java/lib/res/values-ko/strings.xml1
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java1
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java29
-rw-r--r--platform/iphone/SCsub12
-rw-r--r--platform/iphone/app_delegate.h19
-rw-r--r--platform/iphone/app_delegate.mm659
-rw-r--r--platform/iphone/detect.py26
-rw-r--r--platform/iphone/display_layer.h58
-rw-r--r--platform/iphone/display_layer.mm186
-rw-r--r--platform/iphone/display_server_iphone.h202
-rw-r--r--platform/iphone/display_server_iphone.mm751
-rw-r--r--platform/iphone/game_center.h6
-rw-r--r--platform/iphone/game_center.mm61
-rw-r--r--platform/iphone/gl_view.h123
-rw-r--r--platform/iphone/gl_view.mm702
-rw-r--r--platform/iphone/godot_iphone.mm (renamed from platform/iphone/godot_iphone.cpp)59
-rw-r--r--platform/iphone/godot_view.h56
-rw-r--r--platform/iphone/godot_view.mm499
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.h44
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.m171
-rw-r--r--platform/iphone/godot_view_renderer.h44
-rw-r--r--platform/iphone/godot_view_renderer.mm146
-rw-r--r--platform/iphone/icloud.h6
-rw-r--r--platform/iphone/icloud.mm30
-rw-r--r--platform/iphone/in_app_store.h4
-rw-r--r--platform/iphone/in_app_store.mm40
-rw-r--r--platform/iphone/ios.mm18
-rw-r--r--platform/iphone/joypad_iphone.h50
-rw-r--r--platform/iphone/joypad_iphone.mm380
-rw-r--r--platform/iphone/main.m15
-rw-r--r--platform/iphone/os_iphone.cpp632
-rw-r--r--platform/iphone/os_iphone.h151
-rw-r--r--platform/iphone/os_iphone.mm369
-rw-r--r--platform/iphone/view_controller.h20
-rw-r--r--platform/iphone/view_controller.mm356
-rw-r--r--platform/iphone/vulkan_context_iphone.h5
-rw-r--r--platform/iphone/vulkan_context_iphone.mm22
-rw-r--r--platform/javascript/javascript_main.cpp4
-rw-r--r--platform/linuxbsd/detect.py2
-rw-r--r--platform/linuxbsd/display_server_x11.cpp4
-rw-r--r--platform/linuxbsd/godot_linuxbsd.cpp3
-rw-r--r--platform/osx/detect.py33
-rw-r--r--platform/osx/display_server_osx.mm24
-rw-r--r--platform/osx/godot_main_osx.mm5
-rw-r--r--platform/server/detect.py2
-rw-r--r--platform/server/godot_server.cpp3
-rw-r--r--platform/uwp/detect.py2
-rw-r--r--platform/windows/detect.py4
-rw-r--r--platform/windows/display_server_windows.cpp2
-rw-r--r--platform/windows/godot_windows.cpp8
-rw-r--r--scene/3d/navigation_3d.cpp2
-rw-r--r--scene/3d/physics_body_3d.cpp7
-rw-r--r--scene/3d/physics_body_3d.h3
-rw-r--r--scene/animation/tween.cpp14
-rw-r--r--scene/gui/box_container.cpp13
-rw-r--r--scene/gui/file_dialog.cpp2
-rw-r--r--scene/gui/line_edit.cpp57
-rw-r--r--scene/gui/line_edit.h9
-rw-r--r--scene/gui/rich_text_label.cpp3
-rw-r--r--scene/gui/scroll_bar.cpp2
-rw-r--r--scene/gui/tree.cpp8
-rw-r--r--scene/register_scene_types.cpp2
-rw-r--r--scene/resources/animation.cpp2
-rw-r--r--scene/resources/dynamic_font.cpp36
-rw-r--r--scene/resources/dynamic_font.h2
-rw-r--r--scene/resources/texture.cpp2
-rw-r--r--scene/resources/visual_shader.cpp71
-rw-r--r--scene/resources/visual_shader.h6
-rw-r--r--scene/resources/visual_shader_nodes.cpp4
-rw-r--r--scene/resources/visual_shader_nodes.h2
-rw-r--r--servers/display_server.cpp2
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp2
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp705
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h107
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp719
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_scene_rd.h125
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp8
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_storage_rd.h63
-rw-r--r--servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl2
-rw-r--r--servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl4
-rw-r--r--servers/rendering/rendering_device_binds.cpp2
-rw-r--r--servers/rendering/rendering_device_binds.h2
-rw-r--r--servers/rendering/rendering_server_scene.cpp12
-rw-r--r--tests/SCsub (renamed from main/tests/SCsub)0
-rw-r--r--tests/test_astar.cpp (renamed from main/tests/test_astar.cpp)0
-rw-r--r--tests/test_astar.h (renamed from main/tests/test_astar.h)0
-rw-r--r--tests/test_basis.cpp (renamed from main/tests/test_basis.cpp)0
-rw-r--r--tests/test_basis.h (renamed from main/tests/test_basis.h)0
-rw-r--r--tests/test_class_db.cpp (renamed from main/tests/test_class_db.cpp)0
-rw-r--r--tests/test_class_db.h (renamed from main/tests/test_class_db.h)0
-rw-r--r--tests/test_gdscript.cpp187
-rw-r--r--tests/test_gdscript.h (renamed from main/tests/test_gdscript.h)0
-rw-r--r--tests/test_gui.cpp (renamed from main/tests/test_gui.cpp)0
-rw-r--r--tests/test_gui.h (renamed from main/tests/test_gui.h)0
-rw-r--r--tests/test_main.cpp (renamed from main/tests/test_main.cpp)111
-rw-r--r--tests/test_main.h (renamed from main/tests/test_main.h)2
-rw-r--r--tests/test_math.cpp (renamed from main/tests/test_math.cpp)0
-rw-r--r--tests/test_math.h (renamed from main/tests/test_math.h)0
-rw-r--r--tests/test_oa_hash_map.cpp (renamed from main/tests/test_oa_hash_map.cpp)0
-rw-r--r--tests/test_oa_hash_map.h (renamed from main/tests/test_oa_hash_map.h)0
-rw-r--r--tests/test_ordered_hash_map.cpp (renamed from main/tests/test_ordered_hash_map.cpp)0
-rw-r--r--tests/test_ordered_hash_map.h (renamed from main/tests/test_ordered_hash_map.h)0
-rw-r--r--tests/test_physics_2d.cpp (renamed from main/tests/test_physics_2d.cpp)0
-rw-r--r--tests/test_physics_2d.h (renamed from main/tests/test_physics_2d.h)0
-rw-r--r--tests/test_physics_3d.cpp (renamed from main/tests/test_physics_3d.cpp)0
-rw-r--r--tests/test_physics_3d.h (renamed from main/tests/test_physics_3d.h)0
-rw-r--r--tests/test_render.cpp (renamed from main/tests/test_render.cpp)0
-rw-r--r--tests/test_render.h (renamed from main/tests/test_render.h)0
-rw-r--r--tests/test_shader_lang.cpp (renamed from main/tests/test_shader_lang.cpp)0
-rw-r--r--tests/test_shader_lang.h (renamed from main/tests/test_shader_lang.h)0
-rw-r--r--tests/test_string.h798
-rw-r--r--tests/test_validate_testing.h42
-rw-r--r--thirdparty/README.md14
-rw-r--r--thirdparty/doctest/LICENSE.txt21
-rw-r--r--thirdparty/doctest/doctest.h6211
-rw-r--r--thirdparty/doctest/patches/fix-arm64-mac.patch18
-rw-r--r--thirdparty/enet/godot.cpp91
-rw-r--r--thirdparty/misc/stb_vorbis.c15
-rw-r--r--thirdparty/vulkan/patches/VMA-assert-remove.patch29
-rw-r--r--thirdparty/vulkan/vk_mem_alloc.h18
347 files changed, 28133 insertions, 21056 deletions
diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index 0173657e80..0000000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-image: Visual Studio 2019
-
-platform: x64
-
-environment:
- HOME: "%HOMEDRIVE%%HOMEPATH%"
- PYTHON: C:\Python38
- SCONS_CACHE_ROOT: "%HOME%\\scons_cache"
- SCONS_CACHE_LIMIT: 1024
- OPTIONS: "debug_symbols=no verbose=yes progress=no"
- EXTRA_ARGS: "warnings=all werror=yes"
- matrix:
- - GD_PLATFORM: windows
- TARGET: release_debug
- TOOLS: yes
-# Disabled for performance reasons until master is more stable.
-# - GD_PLATFORM: windows
-# TARGET: release
-# TOOLS: no
-
-matrix:
- fast_finish: true
-
-init:
- - ps: if ($env:APPVEYOR_REPO_BRANCH -ne "master") { $env:APPVEYOR_CACHE_SKIP_SAVE = "true" }
-
-cache:
- - "%SCONS_CACHE_ROOT%"
-
-install:
- - SET "PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
- - pip install -U wheel # needed for pip install scons to work, otherwise a flag is missing
- - pip install scons # use stable scons
- - if defined VS call "%VS%" %ARCH% # if defined - so we can also use mingw
-
-before_build:
- - echo %GD_PLATFORM%
- - python --version
- - scons --version
- - set "SCONS_CACHE=%SCONS_CACHE_ROOT%\%APPVEYOR_REPO_BRANCH%"
-
-build_script:
- - scons platform=%GD_PLATFORM% target=%TARGET% tools=%TOOLS% %OPTIONS% %EXTRA_ARGS%
-
-after_build:
- - git rev-parse --short=9 HEAD > VERSION_HASH.txt
- - set /P VERSION_HASH= < VERSION_HASH.txt
- - cd bin
- - mv godot.windows.opt.tools.64.exe godot_%APPVEYOR_REPO_BRANCH%-%VERSION_HASH%_win64.exe
- - 7z a -mx9 godot_%APPVEYOR_REPO_BRANCH%-%VERSION_HASH%_win64.zip *.exe
-
-artifacts:
- - path: bin/godot_${APPVEYOR_REPO_BRANCH}-${VERSION_HASH}_win64.zip
- name: Win64 release_debug editor build
- type: zip
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000..332ed2b72f
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,11 @@
+<!--
+Pull requests should always be made for the `master` branch first, as that's
+where development happens and the source of all future stable release branches.
+
+Relevant fixes are cherry-picked for stable branches as needed.
+
+Do not create a pull request for stable branches unless the change is already
+available in the `master` branch and it cannot be easily cherry-picked.
+Alternatively, if the change is only relevant for that branch (e.g. rendering
+fixes for the 3.2 branch).
+-->
diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml
new file mode 100644
index 0000000000..c591b4bb39
--- /dev/null
+++ b/.github/workflows/android_builds.yml
@@ -0,0 +1,72 @@
+name: Android Builds
+on: [push, pull_request]
+
+# Global Cache Settings
+env:
+ GODOT_BASE_BRANCH: master
+ SCONS_CACHE_LIMIT: 4096
+
+jobs:
+ android-template:
+ runs-on: "ubuntu-20.04"
+
+ name: Template (target=release, tools=no)
+
+ steps:
+ - uses: actions/checkout@v2
+
+ # Azure repositories are not reliable, we need to prevent azure giving us packages.
+ - name: Make apt sources.list use the default Ubuntu repositories
+ run: |
+ sudo cp -f misc/ci/sources.list /etc/apt/sources.list
+ sudo apt-get update
+
+ # Install all packages (except scons)
+ - name: Configure dependencies
+ run: |
+ sudo apt-get install openjdk-8-jdk
+ echo "::set-env name=JAVA_HOME::usr/lib/jvm/java-8-openjdk-amd64"
+
+ - name: Install Android SDK and NDK
+ run: |
+ echo "::set-env name=PATH::/usr/lib/jvm/java-8-openjdk-amd64/jre/bin:${PATH}"
+ java -version
+ echo "::set-env name=ANDROID_HOME::$(pwd)/godot-dev/build-tools/android-sdk"
+ echo "::set-env name=ANDROID_NDK_ROOT::$(pwd)/godot-dev/build-tools/android-ndk"
+ misc/ci/android-tools-linux.sh
+ source ~/.bashrc
+
+ # Upload cache on completion and check it out now
+ - name: Load .scons_cache directory
+ id: android-template-cache
+ uses: actions/cache@v2
+ with:
+ path: ${{github.workspace}}/.scons_cache/
+ key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ restore-keys: |
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}
+
+ # Use python 3.x release (works cross platform)
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ # Semantic version range syntax or exact version of a Python version
+ python-version: '3.x'
+ # Optional - x64 or x86 architecture, defaults to x64
+ architecture: 'x64'
+
+ # You can test your matrix by printing the current Python version
+ - name: Configuring Python packages
+ run: |
+ python -c "import sys; print(sys.version)"
+ python -m pip install scons
+ python --version
+ scons --version
+
+ - name: Compilation
+ env:
+ SCONS_CACHE: ${{github.workspace}}/.scons_cache/
+ run: |
+ scons -j2 verbose=yes warnings=all werror=yes platform=android target=release tools=no
diff --git a/.github/workflows/ios_builds.yml b/.github/workflows/ios_builds.yml
new file mode 100644
index 0000000000..0657a6cb18
--- /dev/null
+++ b/.github/workflows/ios_builds.yml
@@ -0,0 +1,50 @@
+name: iOS Builds
+on: [push, pull_request]
+
+# Global Cache Settings
+env:
+ GODOT_BASE_BRANCH: master
+ SCONS_CACHE_LIMIT: 4096
+
+jobs:
+ ios-template:
+ runs-on: "macos-latest"
+ name: Template (target=release, tools=no)
+
+ steps:
+ - uses: actions/checkout@v2
+
+ # Upload cache on completion and check it out now
+ - name: Load .scons_cache directory
+ id: ios-template-cache
+ uses: actions/cache@v2
+ with:
+ path: ${{github.workspace}}/.scons_cache/
+ key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ restore-keys: |
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}
+
+ # Use python 3.x release (works cross platform)
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ # Semantic version range syntax or exact version of a Python version
+ python-version: '3.x'
+ # Optional - x64 or x86 architecture, defaults to x64
+ architecture: 'x64'
+
+ # You can test your matrix by printing the current Python version
+ - name: Configuring Python packages
+ run: |
+ python -c "import sys; print(sys.version)"
+ python -m pip install scons
+ python --version
+ scons --version
+
+ - name: Compilation
+ env:
+ SCONS_CACHE: ${{github.workspace}}/.scons_cache/
+ run: |
+ scons -j2 verbose=yes warnings=all werror=yes platform=iphone target=release tools=no
diff --git a/.github/workflows/javascript_builds.yml b/.github/workflows/javascript_builds.yml
new file mode 100644
index 0000000000..8167a48eae
--- /dev/null
+++ b/.github/workflows/javascript_builds.yml
@@ -0,0 +1,76 @@
+name: JavaScript Builds
+on: [push, pull_request]
+
+# Global Cache Settings
+env:
+ GODOT_BASE_BRANCH: master
+ SCONS_CACHE_LIMIT: 4096
+ EM_VERSION: latest
+ EM_CACHE_FOLDER: 'emsdk-cache'
+
+jobs:
+ javascript-template:
+ runs-on: "ubuntu-20.04"
+ name: Template (target=release, tools=no)
+
+ steps:
+ - uses: actions/checkout@v2
+
+ # Azure repositories are not reliable, we need to prevent azure giving us packages.
+ - name: Make apt sources.list use the default Ubuntu repositories
+ run: |
+ sudo cp -f misc/ci/sources.list /etc/apt/sources.list
+ sudo apt-get update
+
+ # Upload cache on completion and check it out now
+ - name: Load .scons_cache directory
+ id: javascript-template-cache
+ uses: actions/cache@v2
+ with:
+ path: ${{github.workspace}}/.scons_cache/
+ key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ restore-keys: |
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}
+
+ # Additional cache for Emscripten generated system libraries
+ - name: Load Emscripten cache
+ id: javascript-template-emscripten-cache
+ uses: actions/cache@v2
+ with:
+ path: ${{env.EM_CACHE_FOLDER}}
+ key: ${{env.EM_VERSION}}-${{github.job}}
+
+ # Use python 3.x release (works cross platform)
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ # Semantic version range syntax or exact version of a Python version
+ python-version: '3.x'
+ # Optional - x64 or x86 architecture, defaults to x64
+ architecture: 'x64'
+
+ # You can test your matrix by printing the current Python version
+ - name: Configuring Python packages
+ run: |
+ python -c "import sys; print(sys.version)"
+ python -m pip install scons
+ python --version
+ scons --version
+
+ - name: Set up Emscripten latest
+ uses: mymindstorm/setup-emsdk@v6
+ with:
+ version: ${{env.EM_VERSION}}
+ actions-cache-folder: ${{env.EM_CACHE_FOLDER}}
+
+ - name: Verify Emscripten setup
+ run: |
+ emcc -v
+
+ - name: Compilation
+ env:
+ SCONS_CACHE: ${{github.workspace}}/.scons_cache/
+ run: |
+ scons -j2 verbose=yes warnings=all werror=yes platform=javascript target=release tools=no use_closure_compiler=yes
diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml
new file mode 100644
index 0000000000..a05bb09931
--- /dev/null
+++ b/.github/workflows/linux_builds.yml
@@ -0,0 +1,122 @@
+name: Linux Builds
+on: [push, pull_request]
+
+# Global Cache Settings
+env:
+ GODOT_BASE_BRANCH: master
+ SCONS_CACHE_LIMIT: 4096
+
+jobs:
+ linux-editor:
+ runs-on: "ubuntu-20.04"
+ name: Editor w/ Mono (target=release_debug, tools=yes)
+
+ steps:
+ - uses: actions/checkout@v2
+
+ # Azure repositories are not reliable, we need to prevent azure giving us packages.
+ - name: Make apt sources.list use the default Ubuntu repositories
+ run: |
+ sudo cp -f misc/ci/sources.list /etc/apt/sources.list
+ sudo apt-get update
+
+ # Install all packages (except scons)
+ - name: Configure dependencies
+ run: |
+ sudo apt-get install build-essential pkg-config libx11-dev libxcursor-dev \
+ libxinerama-dev libgl1-mesa-dev libglu-dev libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev yasm
+
+ # Upload cache on completion and check it out now
+ - name: Load .scons_cache directory
+ id: linux-editor-cache
+ uses: actions/cache@v2
+ with:
+ path: ${{github.workspace}}/.scons_cache/
+ key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ restore-keys: |
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}
+
+ # Use python 3.x release (works cross platform; best to keep self contained in it's own step)
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ # Semantic version range syntax or exact version of a Python version
+ python-version: '3.x'
+ # Optional - x64 or x86 architecture, defaults to x64
+ architecture: 'x64'
+
+ # Setup scons, print python version and scons version info, so if anything is broken it won't run the build.
+ - name: Configuring Python packages
+ run: |
+ python -c "import sys; print(sys.version)"
+ python -m pip install scons
+ python --version
+ scons --version
+
+ # We should always be explicit with our flags usage here since it's gonna be sure to always set those flags
+ - name: Compilation
+ env:
+ SCONS_CACHE: ${{github.workspace}}/.scons_cache/
+ run: |
+ scons -j2 verbose=yes warnings=all werror=yes platform=linuxbsd tools=yes tests=yes target=release_debug module_mono_enabled=yes mono_glue=no
+
+ # Execute unit tests for the editor
+ - name: Unit Tests
+ run: |
+ ./bin/godot.linuxbsd.opt.tools.64.mono --test
+
+ linux-template:
+ runs-on: "ubuntu-20.04"
+ name: Template w/ Mono (target=release, tools=no)
+
+ steps:
+ - uses: actions/checkout@v2
+
+ # Azure repositories are not reliable, we need to prevent azure giving us packages.
+ - name: Make apt sources.list use the default Ubuntu repositories
+ run: |
+ sudo cp -f misc/ci/sources.list /etc/apt/sources.list
+ sudo apt-get update
+
+ # Install all packages (except scons)
+ - name: Configure dependencies
+ run: |
+ sudo apt-get install build-essential pkg-config libx11-dev libxcursor-dev \
+ libxinerama-dev libgl1-mesa-dev libglu-dev libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev yasm
+
+ # Upload cache on completion and check it out now
+ - name: Load .scons_cache directory
+ id: linux-template-cache
+ uses: actions/cache@v2
+ with:
+ path: ${{github.workspace}}/.scons_cache/
+ key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ restore-keys: |
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}
+
+ # Use python 3.x release (works cross platform)
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ # Semantic version range syntax or exact version of a Python version
+ python-version: '3.x'
+ # Optional - x64 or x86 architecture, defaults to x64
+ architecture: 'x64'
+
+ # You can test your matrix by printing the current Python version
+ - name: Configuring Python packages
+ run: |
+ python -c "import sys; print(sys.version)"
+ python -m pip install scons
+ python --version
+ scons --version
+
+ - name: Compilation
+ env:
+ SCONS_CACHE: ${{github.workspace}}/.scons_cache/
+ run: |
+ scons -j2 verbose=yes warnings=all werror=yes platform=linuxbsd target=release tools=no module_mono_enabled=yes mono_glue=no
diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml
new file mode 100644
index 0000000000..d9f41f09f7
--- /dev/null
+++ b/.github/workflows/macos_builds.yml
@@ -0,0 +1,99 @@
+name: macOS Builds
+on: [push, pull_request]
+
+# Global Cache Settings
+env:
+ GODOT_BASE_BRANCH: master
+ SCONS_CACHE_LIMIT: 4096
+
+jobs:
+ macos-editor:
+ runs-on: "macos-latest"
+
+ name: Editor (target=release_debug, tools=yes)
+
+ steps:
+ - uses: actions/checkout@v2
+
+ # Upload cache on completion and check it out now
+ - name: Load .scons_cache directory
+ id: macos-editor-cache
+ uses: actions/cache@v2
+ with:
+ path: ${{github.workspace}}/.scons_cache/
+ key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ restore-keys: |
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}
+
+ # Use python 3.x release (works cross platform; best to keep self contained in it's own step)
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ # Semantic version range syntax or exact version of a Python version
+ python-version: '3.x'
+ # Optional - x64 or x86 architecture, defaults to x64
+ architecture: 'x64'
+
+ # Setup scons, print python version and scons version info, so if anything is broken it won't run the build.
+ - name: Configuring Python packages
+ run: |
+ python -c "import sys; print(sys.version)"
+ python -m pip install scons
+ python --version
+ scons --version
+
+ # We should always be explicit with our flags usage here since it's gonna be sure to always set those flags
+ - name: Compilation
+ env:
+ SCONS_CACHE: ${{github.workspace}}/.scons_cache/
+ run: |
+ scons -j2 verbose=yes warnings=all werror=yes platform=osx tools=yes tests=yes target=release_debug
+
+ # Execute unit tests for the editor
+ - name: Unit Tests
+ run: |
+ ./bin/godot.osx.opt.tools.64 --test
+
+ macos-template:
+ runs-on: "macos-latest"
+ name: Template (target=release, tools=no)
+
+ steps:
+ - uses: actions/checkout@v2
+
+ # Upload cache on completion and check it out now
+ - name: Load .scons_cache directory
+ id: macos-template-cache
+ uses: actions/cache@v2
+ with:
+ path: ${{github.workspace}}/.scons_cache/
+ key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ restore-keys: |
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}
+
+ # Use python 3.x release (works cross platform)
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ # Semantic version range syntax or exact version of a Python version
+ python-version: '3.x'
+ # Optional - x64 or x86 architecture, defaults to x64
+ architecture: 'x64'
+
+ # You can test your matrix by printing the current Python version
+ - name: Configuring Python packages
+ run: |
+ python -c "import sys; print(sys.version)"
+ python -m pip install scons
+ python --version
+ scons --version
+
+ - name: Compilation
+ env:
+ SCONS_CACHE: ${{github.workspace}}/.scons_cache/
+ run: |
+ scons -j2 verbose=yes warnings=all werror=yes platform=osx target=release tools=no
diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml
index 587f5a6ba6..87339da776 100644
--- a/.github/workflows/static_checks.yml
+++ b/.github/workflows/static_checks.yml
@@ -1,8 +1,9 @@
-name: Static checks
+name: Static Checks
on: [push, pull_request]
jobs:
- build:
+ static-checks:
+ name: Static Checks (clang-format, black format, file format, documentation checks)
runs-on: ubuntu-20.04
steps:
- name: Checkout
diff --git a/.github/workflows/main.yml b/.github/workflows/windows_builds.yml
index 3b5d39bb12..2bc3fcfdaa 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/windows_builds.yml
@@ -1,43 +1,36 @@
-# Engine build CI
-name: Godot CI
-
-on:
- # will build EVERY pull request
- pull_request:
-
- # will only build explicit branches
- push:
- branches: [ master, 3.2, 3.1, 3.0 ]
+name: Windows Builds
+on: [push, pull_request]
# Global Cache Settings
# SCONS_CACHE for windows must be set in the build environment
env:
+ GODOT_BASE_BRANCH: master
SCONS_CACHE_MSVC_CONFIG: true
- SCONS_CACHE_LIMIT: 8192
+ SCONS_CACHE_LIMIT: 4096
+
jobs:
windows-editor:
# Windows 10 with latest image
runs-on: "windows-latest"
# Windows Editor - checkout with the plugin
- name: Windows Editor (target=release_debug, tools=yes)
+ name: Editor (target=release_debug, tools=yes)
steps:
- uses: actions/checkout@v2
- # Upload cache on completion and check it out now
- # Editing this is pretty dangerous for windows since it can break and needs properly tested with a fresh cache.
- # Linux with this will work reliably, so not as bad to edit for Linux.
+ # Upload cache on completion and check it out now
+ # Editing this is pretty dangerous for Windows since it can break and needs to be properly tested with a fresh cache.
- name: Load .scons_cache directory
id: windows-editor-cache
uses: RevoluPowered/cache@v2.1
with:
path: /.scons_cache/
- key: ${{runner.os}}-editor-${{github.sha}}
+ key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
restore-keys: |
- ${{runner.os}}-editor-${{github.sha}}
- ${{runner.os}}-editor
- ${{runner.os}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}
# Use python 3.x release (works cross platform; best to keep self contained in it's own step)
- name: Set up Python 3.x
@@ -61,7 +54,12 @@ jobs:
env:
SCONS_CACHE: /.scons_cache/
run: |
- scons -j2 verbose=yes warnings=all werror=yes platform=windows tools=yes target=release_debug
+ scons -j2 verbose=yes warnings=all werror=yes platform=windows tools=yes tests=yes target=release_debug
+
+ # Execute unit tests for the editor
+ - name: Unit Tests
+ run: |
+ ./bin/godot.windows.opt.tools.64.exe --test
# Build Product Upload (tested and working)
# sorry this is disabled until github can give us some more space as we would hit our limit very quickly
@@ -72,29 +70,27 @@ jobs:
# name: windows-editor (x64)
# path: bin/godot.windows.opt.tools.64.exe
-
windows-template:
runs-on: "windows-latest"
- name: Windows Template (target=release, tools=no)
+ name: Template (target=release, tools=no)
steps:
- uses: actions/checkout@v2
# Upload cache on completion and check it out now
- # Editing this is pretty dangerous for windows since it can break and needs properly tested with a fresh cache.
- # Linux with this will work reliably, so not as bad to edit for Linux.
+ # Editing this is pretty dangerous for Windows since it can break and needs to be properly tested with a fresh cache.
- name: Load .scons_cache directory
id: windows-template-cache
uses: RevoluPowered/cache@v2.1
with:
path: /.scons_cache/
- key: ${{runner.os}}-template-${{github.sha}}
+ key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
restore-keys: |
- ${{runner.os}}-template-${{github.sha}}
- ${{runner.os}}-template
- ${{runner.os}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}
- # Use python 3.x release (works cross platform)
+ # Use python 3.x release (works cross platform)
- name: Set up Python 3.x
uses: actions/setup-python@v2
with:
@@ -110,6 +106,7 @@ jobs:
python -m pip install scons pywin32
python --version
scons --version
+
- name: Compilation
env:
SCONS_CACHE: /.scons_cache/
diff --git a/.gitignore b/.gitignore
index d9537edbf2..6af581040c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,11 +11,13 @@ doc/_build/
# CLion
cmake-build-debug
+# clangd
+.clangd/
+
# Android specific
.gradle
local.properties
*.iml
-.idea
.gradletasknamecache
project.properties
platform/android/java/lib/.cxx/
@@ -34,6 +36,8 @@ platform/android/java/lib/.cxx/*
*.os
*.Plo
*.lo
+# Binutils tmp linker output of the form "stXXXXXX" where "X" is alphanumeric
+st[A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9]
# Libs generated files
.deps/*
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 9c085d4ce0..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,136 +0,0 @@
-language: cpp
-
-# OS config, depends on actual 'os' in build matrix
-dist: bionic
-
-stages:
- - build
-
-env:
- global:
- - SCONS_CACHE=$HOME/.scons_cache/$TRAVIS_BRANCH
- - SCONS_CACHE_LIMIT=1024
- - OPTIONS="debug_symbols=no verbose=yes progress=no"
- - secure: "uch9QszCgsl1qVbuzY41P7S2hWL2IiNFV4SbAYRCdi0oJ9MIu+pVyrQdpf3+jG4rH6j4Rffl+sN17Zz4dIDDioFL1JwqyCqyCyswR8uACC0Rr8gr4Mi3+HIRbv+2s2P4cIQq41JM8FJe84k9jLEMGCGh69w+ibCWoWs74CokYVA="
-
-cache:
- directories:
- - $SCONS_CACHE
-
-matrix:
- include:
- - name: Linux editor (debug, GCC 9, with Mono)
- stage: build
- env: PLATFORM=linuxbsd TOOLS=yes TARGET=debug CACHE_NAME=${PLATFORM}-tools-mono-gcc-9 MATRIX_EVAL="CC=gcc-9 && CXX=g++-9" EXTRA_ARGS="module_mono_enabled=yes mono_glue=no warnings=extra werror=yes"
- os: linux
- compiler: gcc-9
- addons:
- apt:
- sources:
- - sourceline: "deb https://download.mono-project.com/repo/ubuntu stable-bionic main"
- key_url: "https://raw.githubusercontent.com/travis-ci/apt-source-safelist/master/keys/mono.asc"
- - sourceline: "ppa:ubuntu-toolchain-r/test"
- packages:
- - &gcc9_deps [gcc-9, g++-9]
- - &linux_deps [libasound2-dev, libgl1-mesa-dev, libglu1-mesa-dev, libx11-dev, libxcursor-dev, libxi-dev, libxinerama-dev, libxrandr-dev]
- - &linux_mono_deps [mono-devel, msbuild, nuget]
-
- - name: Linux export template (release, Clang 7)
- stage: build
- env: PLATFORM=linuxbsd TOOLS=no TARGET=release CACHE_NAME=${PLATFORM}-clang EXTRA_ARGS="warnings=extra werror=yes"
- os: linux
- compiler: clang
- addons:
- apt:
- packages:
- - *linux_deps
-
- - name: Android export template (release_debug, Clang)
- stage: build
- env: PLATFORM=android TOOLS=no TARGET=release_debug CACHE_NAME=${PLATFORM}-clang EXTRA_ARGS="warnings=extra werror=yes"
- os: linux
- compiler: clang
- addons:
- apt:
- packages:
- - openjdk-8-jdk
-
- - name: macOS editor (debug, Clang)
- stage: build
- env: PLATFORM=osx TOOLS=yes TARGET=debug CACHE_NAME=${PLATFORM}-tools-clang EXTRA_ARGS="warnings=extra werror=yes"
- os: osx
- osx_image: xcode11.5
- compiler: clang
- addons:
- homebrew:
- packages:
- - scons
- update: true
-
-# TODO: iOS MoltenVK support
-
-# - name: iOS export template (debug, Clang)
-# stage: build
-# env: PLATFORM=iphone TOOLS=no TARGET=debug CACHE_NAME=${PLATFORM}-clang
-# os: osx
-# osx_image: xcode11.5
-# compiler: clang
-# addons:
-# homebrew:
-# packages:
-# - scons
-
- - name: Linux export template (release_debug, GCC 7, without 3D support)
- stage: build
- env: PLATFORM=linuxbsd TOOLS=no TARGET=release_debug CACHE_NAME=${PLATFORM}-gcc-7 EXTRA_ARGS="disable_3d=yes"
- os: linux
- compiler: gcc
- addons:
- apt:
- packages:
- - *linux_deps
-
- - name: JavaScript export template (release, emscripten latest)
- stage: build
- env: PLATFORM=javascript TOOLS=no TARGET=release CACHE_NAME=${PLATFORM}-emcc-latest EXTRA_ARGS="use_closure_compiler=yes"
- os: linux
- compiler: clang
- addons:
- apt:
- packages:
- - *linux_deps
-
-before_install:
- - eval "${MATRIX_EVAL}"
-
-install:
- - if [ "$TRAVIS_OS_NAME" = "linux" ]; then
- pyenv global 3.8 system;
- pip3 install --user scons;
- fi
- - scons --version
- - if [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$PLATFORM" = "android" ]; then
- export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64;
- export PATH=/usr/lib/jvm/java-8-openjdk-amd64/jre/bin:${PATH};
- java -version;
- misc/travis/android-tools-linux.sh;
- fi
- - if [ "$PLATFORM" = "javascript" ]; then
- git clone --depth 1 "https://github.com/emscripten-core/emsdk.git";
- ./emsdk/emsdk install latest;
- ./emsdk/emsdk activate --no-embedded latest;
- fi
-
-before_script:
- - if [ "$PLATFORM" = "android" ]; then
- export ANDROID_HOME=$TRAVIS_BUILD_DIR/godot-dev/build-tools/android-sdk;
- export ANDROID_NDK_ROOT=$TRAVIS_BUILD_DIR/godot-dev/build-tools/android-ndk;
- fi
-
-script:
- - scons -j2 CC=$CC CXX=$CXX platform=$PLATFORM tools=$TOOLS target=$TARGET $OPTIONS $EXTRA_ARGS &&
- if [ "$TEST_PROJECT" = "yes" ]; then
- git clone --depth 1 "https://github.com/godotengine/godot-tests.git";
- sed -i "s:custom_template/release=\"\":custom_template/release=\"$(readlink -e bin/godot_server.linuxbsd.opt.tools.64)\":" godot-tests/tests/project_export/export_presets.cfg;
- godot-tests/tests/project_export/test_project.sh "bin/godot_server.linuxbsd.opt.tools.64";
- fi
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15a73f7021..1f8f8bd676 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -217,7 +217,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- This makes it possible to change its value at runtime, rather than just defining it once in the Project Settings.
- `SceneTree.quit()` now accepts an optional argument with an exit code.
- If set to a value greater than or equal to 0, it will override the `OS.exit_code` property.
-- `VisualServer.get_video_adapter_name()` and `VisualServer.get_video_adapter_vendor()` methods to retreive the user's graphics card model and vendor.
+- `VisualServer.get_video_adapter_name()` and `VisualServer.get_video_adapter_vendor()` methods to retrieve the user's graphics card model and vendor.
- `VisualServer.multimesh_create()` is now exposed to scripting.
- Ability to override how scripted objects are converted to strings by defining a `_to_string()` method.
- Export hints for 2D and 3D physics/render layers.
@@ -885,7 +885,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `editor` and `standalone` feature tags to check whether the project is running from an editor or non-editor binary.
- `android_add_asset_dir("...")` method to Android module Gradle build configuration.
- **iOS:** Support for exporting to the iPhone X.
-- **iOS:** Readded support for in-app purchases.
+- **iOS:** Re-added support for in-app purchases.
### Changed
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index fc3079c361..e5d30e3328 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -126,6 +126,11 @@ Copyright: 2018, Eric Lasota
2018, Microsoft Corp.
License: Expat
+Files: ./thirdparty/doctest/
+Comment: doctest
+Copyright: 2016-2019, Viktor Kirilov
+License: Expat
+
Files: ./thirdparty/enet/
Comment: ENet
Copyright: 2002-2020, Lee Salzman
diff --git a/README.md b/README.md
index f9855beaf6..e962f90333 100644
--- a/README.md
+++ b/README.md
@@ -66,8 +66,7 @@ There are also a number of other learning resources provided by the community,
such as text and video tutorials, demos, etc. Consult the [community channels](https://godotengine.org/community)
for more info.
-[![Travis Build Status](https://travis-ci.org/godotengine/godot.svg?branch=master)](https://travis-ci.org/godotengine/godot)
-[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/bfiihqq6byxsjxxh/branch/master?svg=true)](https://ci.appveyor.com/project/akien-mga/godot)
+[![Actions Build Status](https://github.com/godotengine/godot/workflows/Godot/badge.svg?branch=master)](https://github.com/godotengine/godot/actions)
[![Code Triagers Badge](https://www.codetriage.com/godotengine/godot/badges/users.svg)](https://www.codetriage.com/godotengine/godot)
[![Translate on Weblate](https://hosted.weblate.org/widgets/godot-engine/-/godot/svg-badge.svg)](https://hosted.weblate.org/engage/godot-engine/?utm_source=widget)
[![Total alerts on LGTM](https://img.shields.io/lgtm/alerts/g/godotengine/godot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/godotengine/godot/alerts)
diff --git a/SConstruct b/SConstruct
index e7ca8b3030..98438d2c60 100644
--- a/SConstruct
+++ b/SConstruct
@@ -115,6 +115,7 @@ opts.Add(EnumVariable("target", "Compilation target", "debug", ("debug", "releas
opts.Add(EnumVariable("optimize", "Optimization type", "speed", ("speed", "size")))
opts.Add(BoolVariable("tools", "Build the tools (a.k.a. the Godot editor)", True))
+opts.Add(BoolVariable("tests", "Build the unit tests", False))
opts.Add(BoolVariable("use_lto", "Use link-time optimization", False))
opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise epsilon (debug option)", False))
@@ -249,6 +250,10 @@ if env_base["target"] == "debug":
# http://scons.org/doc/production/HTML/scons-user/ch06s04.html
env_base.SetOption("implicit_cache", 1)
+if not env_base["tools"]:
+ # Export templates can't run unit test tool.
+ env_base["tests"] = False
+
if env_base["no_editor_splash"]:
env_base.Append(CPPDEFINES=["NO_EDITOR_SPLASH"])
@@ -312,6 +317,8 @@ if selected_platform in platform_list:
env["verbose"] = True
env["warnings"] = "extra"
env["werror"] = True
+ if env["tools"]:
+ env["tests"] = True
if env["vsproj"]:
env.vs_incs = []
@@ -586,6 +593,8 @@ if selected_platform in platform_list:
env.Append(CPPDEFINES=["PTRCALL_ENABLED"])
if env["tools"]:
env.Append(CPPDEFINES=["TOOLS_ENABLED"])
+ if env["tests"]:
+ env.Append(CPPDEFINES=["TESTS_ENABLED"])
if env["disable_3d"]:
if env["tools"]:
print(
@@ -641,6 +650,10 @@ if selected_platform in platform_list:
}
)
+ # Enable test framework globally and inform it of configuration method.
+ if env["tests"]:
+ env.Append(CPPDEFINES=["DOCTEST_CONFIG_IMPLEMENT"])
+
scons_cache_path = os.environ.get("SCONS_CACHE")
if scons_cache_path != None:
CacheDir(scons_cache_path)
@@ -648,8 +661,7 @@ if selected_platform in platform_list:
Export("env")
- # build subdirs, the build order is dependent on link order.
-
+ # Build subdirs, the build order is dependent on link order.
SConscript("core/SCsub")
SConscript("servers/SCsub")
SConscript("scene/SCsub")
@@ -658,9 +670,11 @@ if selected_platform in platform_list:
SConscript("platform/SCsub")
SConscript("modules/SCsub")
+ if env["tests"]:
+ SConscript("tests/SCsub")
SConscript("main/SCsub")
- SConscript("platform/" + selected_platform + "/SCsub") # build selected platform
+ SConscript("platform/" + selected_platform + "/SCsub") # Build selected platform.
# Microsoft Visual Studio Project Generation
if env["vsproj"]:
diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp
index 94da74cbda..045d7d5872 100644
--- a/core/bind/core_bind.cpp
+++ b/core/bind/core_bind.cpp
@@ -781,6 +781,7 @@ void _OS::_bind_methods() {
// Those default values need to be specified for the docs generator,
// to avoid using values from the documentation writer's own OS instance.
+ ADD_PROPERTY_DEFAULT("tablet_driver", "");
ADD_PROPERTY_DEFAULT("exit_code", 0);
ADD_PROPERTY_DEFAULT("low_processor_usage_mode", false);
ADD_PROPERTY_DEFAULT("low_processor_usage_mode_sleep_usec", 6900);
@@ -1690,7 +1691,7 @@ String _Directory::get_current_dir() {
}
Error _Directory::make_dir(String p_dir) {
- ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use.");
+ ERR_FAIL_COND_V_MSG(!d, ERR_UNCONFIGURED, "Directory is not configured properly.");
if (!p_dir.is_rel_path()) {
DirAccess *d = DirAccess::create_for_path(p_dir);
Error err = d->make_dir(p_dir);
@@ -1701,7 +1702,7 @@ Error _Directory::make_dir(String p_dir) {
}
Error _Directory::make_dir_recursive(String p_dir) {
- ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use.");
+ ERR_FAIL_COND_V_MSG(!d, ERR_UNCONFIGURED, "Directory is not configured properly.");
if (!p_dir.is_rel_path()) {
DirAccess *d = DirAccess::create_for_path(p_dir);
Error err = d->make_dir_recursive(p_dir);
diff --git a/core/callable_method_pointer.h b/core/callable_method_pointer.h
index 22db7d1c82..1bb89e53e1 100644
--- a/core/callable_method_pointer.h
+++ b/core/callable_method_pointer.h
@@ -131,7 +131,7 @@ void call_with_variant_args_helper(T *p_instance, void (T::*p_method)(P...), con
#ifdef DEBUG_METHODS_ENABLED
(p_instance->*p_method)(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...);
#else
- (p_instance->*p_method)(VariantCaster<P>::cast(p_args[Is])...);
+ (p_instance->*p_method)(VariantCaster<P>::cast(*p_args[Is])...);
#endif
}
@@ -228,7 +228,7 @@ void call_with_variant_args_ret_helper(T *p_instance, R (T::*p_method)(P...), co
#ifdef DEBUG_METHODS_ENABLED
r_ret = (p_instance->*p_method)(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...);
#else
- (p_instance->*p_method)(VariantCaster<P>::cast(p_args[Is])...);
+ (p_instance->*p_method)(VariantCaster<P>::cast(*p_args[Is])...);
#endif
}
diff --git a/core/class_db.cpp b/core/class_db.cpp
index 05c9850c39..88f1df3457 100644
--- a/core/class_db.cpp
+++ b/core/class_db.cpp
@@ -548,6 +548,29 @@ void ClassDB::_add_class2(const StringName &p_class, const StringName &p_inherit
}
}
+#ifdef DEBUG_METHODS_ENABLED
+static MethodInfo info_from_bind(MethodBind *p_method) {
+ MethodInfo minfo;
+ minfo.name = p_method->get_name();
+ minfo.id = p_method->get_method_id();
+
+ for (int i = 0; i < p_method->get_argument_count(); i++) {
+ minfo.arguments.push_back(p_method->get_argument_info(i));
+ }
+
+ minfo.return_val = p_method->get_return_info();
+ minfo.flags = p_method->get_hint_flags();
+
+ for (int i = 0; i < p_method->get_argument_count(); i++) {
+ if (p_method->has_default_argument(i)) {
+ minfo.default_arguments.push_back(p_method->get_default_argument(i));
+ }
+ }
+
+ return minfo;
+}
+#endif
+
void ClassDB::get_method_list(StringName p_class, List<MethodInfo> *p_methods, bool p_no_inheritance, bool p_exclude_from_properties) {
OBJTYPE_RLOCK;
@@ -570,29 +593,12 @@ void ClassDB::get_method_list(StringName p_class, List<MethodInfo> *p_methods, b
}
for (List<StringName>::Element *E = type->method_order.front(); E; E = E->next()) {
- MethodBind *method = type->method_map.get(E->get());
- MethodInfo minfo;
- minfo.name = E->get();
- minfo.id = method->get_method_id();
-
- if (p_exclude_from_properties && type->methods_in_properties.has(minfo.name)) {
+ if (p_exclude_from_properties && type->methods_in_properties.has(E->get())) {
continue;
}
- for (int i = 0; i < method->get_argument_count(); i++) {
- //Variant::Type t=method->get_argument_type(i);
-
- minfo.arguments.push_back(method->get_argument_info(i));
- }
-
- minfo.return_val = method->get_return_info();
- minfo.flags = method->get_hint_flags();
-
- for (int i = 0; i < method->get_argument_count(); i++) {
- if (method->has_default_argument(i)) {
- minfo.default_arguments.push_back(method->get_default_argument(i));
- }
- }
+ MethodBind *method = type->method_map.get(E->get());
+ MethodInfo minfo = info_from_bind(method);
p_methods->push_back(minfo);
}
@@ -618,6 +624,57 @@ void ClassDB::get_method_list(StringName p_class, List<MethodInfo> *p_methods, b
}
}
+bool ClassDB::get_method_info(StringName p_class, StringName p_method, MethodInfo *r_info, bool p_no_inheritance, bool p_exclude_from_properties) {
+ OBJTYPE_RLOCK;
+
+ ClassInfo *type = classes.getptr(p_class);
+
+ while (type) {
+ if (type->disabled) {
+ if (p_no_inheritance) {
+ break;
+ }
+
+ type = type->inherits_ptr;
+ continue;
+ }
+
+#ifdef DEBUG_METHODS_ENABLED
+ MethodBind **method = type->method_map.getptr(p_method);
+ if (method && *method) {
+ if (r_info != nullptr) {
+ MethodInfo minfo = info_from_bind(*method);
+ *r_info = minfo;
+ }
+ return true;
+ } else if (type->virtual_methods_map.has(p_method)) {
+ if (r_info) {
+ *r_info = type->virtual_methods_map[p_method];
+ }
+ return true;
+ }
+#else
+ if (type->method_map.has(p_method)) {
+ if (r_info) {
+ MethodBind *m = type->method_map[p_method];
+ MethodInfo mi;
+ mi.name = m->get_name();
+ *r_info = mi;
+ }
+ return true;
+ }
+#endif
+
+ if (p_no_inheritance) {
+ break;
+ }
+
+ type = type->inherits_ptr;
+ }
+
+ return false;
+}
+
MethodBind *ClassDB::get_method(StringName p_class, StringName p_name) {
OBJTYPE_RLOCK;
@@ -718,6 +775,25 @@ int ClassDB::get_integer_constant(const StringName &p_class, const StringName &p
return 0;
}
+bool ClassDB::has_integer_constant(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) {
+ OBJTYPE_RLOCK;
+
+ ClassInfo *type = classes.getptr(p_class);
+
+ while (type) {
+ if (type->constant_map.has(p_name)) {
+ return true;
+ }
+ if (p_no_inheritance) {
+ return false;
+ }
+
+ type = type->inherits_ptr;
+ }
+
+ return false;
+}
+
StringName ClassDB::get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) {
OBJTYPE_RLOCK;
@@ -784,6 +860,25 @@ void ClassDB::get_enum_constants(const StringName &p_class, const StringName &p_
}
}
+bool ClassDB::has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) {
+ OBJTYPE_RLOCK;
+
+ ClassInfo *type = classes.getptr(p_class);
+
+ while (type) {
+ if (type->enum_map.has(p_name)) {
+ return true;
+ }
+ if (p_no_inheritance) {
+ return false;
+ }
+
+ type = type->inherits_ptr;
+ }
+
+ return false;
+}
+
void ClassDB::add_signal(StringName p_class, const MethodInfo &p_signal) {
OBJTYPE_WLOCK;
@@ -825,7 +920,7 @@ void ClassDB::get_signal_list(StringName p_class, List<MethodInfo> *p_signals, b
}
}
-bool ClassDB::has_signal(StringName p_class, StringName p_signal) {
+bool ClassDB::has_signal(StringName p_class, StringName p_signal, bool p_no_inheritance) {
OBJTYPE_RLOCK;
ClassInfo *type = classes.getptr(p_class);
ClassInfo *check = type;
@@ -833,6 +928,9 @@ bool ClassDB::has_signal(StringName p_class, StringName p_signal) {
if (check->signal_map.has(p_signal)) {
return true;
}
+ if (p_no_inheritance) {
+ return false;
+ }
check = check->inherits_ptr;
}
@@ -910,6 +1008,7 @@ void ClassDB::add_property(StringName p_class, const PropertyInfo &p_pinfo, cons
OBJTYPE_WLOCK
type->property_list.push_back(p_pinfo);
+ type->property_map[p_pinfo.name] = p_pinfo;
#ifdef DEBUG_METHODS_ENABLED
if (mb_get) {
type->methods_in_properties.insert(p_getter);
@@ -959,6 +1058,30 @@ void ClassDB::get_property_list(StringName p_class, List<PropertyInfo> *p_list,
}
}
+bool ClassDB::get_property_info(StringName p_class, StringName p_property, PropertyInfo *r_info, bool p_no_inheritance, const Object *p_validator) {
+ OBJTYPE_RLOCK;
+
+ ClassInfo *check = classes.getptr(p_class);
+ while (check) {
+ if (check->property_map.has(p_property)) {
+ PropertyInfo pinfo = check->property_map[p_property];
+ if (p_validator) {
+ p_validator->_validate_property(pinfo);
+ }
+ if (r_info) {
+ *r_info = pinfo;
+ }
+ return true;
+ }
+ if (p_no_inheritance) {
+ break;
+ }
+ check = check->inherits_ptr;
+ }
+
+ return false;
+}
+
bool ClassDB::set_property(Object *p_object, const StringName &p_property, const Variant &p_value, bool *r_valid) {
ClassInfo *type = classes.getptr(p_object->get_class_name());
ClassInfo *check = type;
@@ -1239,6 +1362,7 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
mi.flags |= METHOD_FLAG_VIRTUAL;
}
classes[p_class].virtual_methods.push_back(mi);
+ classes[p_class].virtual_methods_map[p_method.name] = mi;
#endif
}
diff --git a/core/class_db.h b/core/class_db.h
index eae2a9afd4..86ac2aa001 100644
--- a/core/class_db.h
+++ b/core/class_db.h
@@ -120,11 +120,13 @@ public:
HashMap<StringName, List<StringName>> enum_map;
HashMap<StringName, MethodInfo> signal_map;
List<PropertyInfo> property_list;
+ HashMap<StringName, PropertyInfo> property_map;
#ifdef DEBUG_METHODS_ENABLED
List<StringName> constant_order;
List<StringName> method_order;
Set<StringName> methods_in_properties;
List<MethodInfo> virtual_methods;
+ Map<StringName, MethodInfo> virtual_methods_map;
StringName category;
#endif
HashMap<StringName, PropertySetGet> property_setget;
@@ -328,7 +330,7 @@ public:
}
static void add_signal(StringName p_class, const MethodInfo &p_signal);
- static bool has_signal(StringName p_class, StringName p_signal);
+ static bool has_signal(StringName p_class, StringName p_signal, bool p_no_inheritance = false);
static bool get_signal(StringName p_class, StringName p_signal, MethodInfo *r_signal);
static void get_signal_list(StringName p_class, List<MethodInfo> *p_signals, bool p_no_inheritance = false);
@@ -337,6 +339,7 @@ public:
static void add_property(StringName p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index = -1);
static void set_property_default_value(StringName p_class, const StringName &p_name, const Variant &p_default);
static void get_property_list(StringName p_class, List<PropertyInfo> *p_list, bool p_no_inheritance = false, const Object *p_validator = nullptr);
+ static bool get_property_info(StringName p_class, StringName p_property, PropertyInfo *r_info, bool p_no_inheritance = false, const Object *p_validator = nullptr);
static bool set_property(Object *p_object, const StringName &p_property, const Variant &p_value, bool *r_valid = nullptr);
static bool get_property(Object *p_object, const StringName &p_property, Variant &r_value);
static bool has_property(const StringName &p_class, const StringName &p_property, bool p_no_inheritance = false);
@@ -349,6 +352,7 @@ public:
static void set_method_flags(StringName p_class, StringName p_method, int p_flags);
static void get_method_list(StringName p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false, bool p_exclude_from_properties = false);
+ static bool get_method_info(StringName p_class, StringName p_method, MethodInfo *r_info, bool p_no_inheritance = false, bool p_exclude_from_properties = false);
static MethodBind *get_method(StringName p_class, StringName p_name);
static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true);
@@ -357,10 +361,12 @@ public:
static void bind_integer_constant(const StringName &p_class, const StringName &p_enum, const StringName &p_name, int p_constant);
static void get_integer_constant_list(const StringName &p_class, List<String> *p_constants, bool p_no_inheritance = false);
static int get_integer_constant(const StringName &p_class, const StringName &p_name, bool *p_success = nullptr);
+ static bool has_integer_constant(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false);
static StringName get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false);
static void get_enum_list(const StringName &p_class, List<StringName> *p_enums, bool p_no_inheritance = false);
static void get_enum_constants(const StringName &p_class, const StringName &p_enum, List<StringName> *p_constants, bool p_no_inheritance = false);
+ static bool has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false);
static Variant class_get_default_property_value(const StringName &p_class, const StringName &p_property, bool *r_valid = nullptr);
diff --git a/core/container_type_validate.h b/core/container_type_validate.h
index f2724e884d..8a361aa0ef 100644
--- a/core/container_type_validate.h
+++ b/core/container_type_validate.h
@@ -38,7 +38,7 @@ struct ContainerTypeValidate {
Variant::Type type = Variant::NIL;
StringName class_name;
Ref<Script> script;
- const char *where = "conatiner";
+ const char *where = "container";
_FORCE_INLINE_ bool can_reference(const ContainerTypeValidate &p_type) const {
if (type == p_type.type) {
diff --git a/core/global_constants.cpp b/core/global_constants.cpp
index 6281e56395..b30685539a 100644
--- a/core/global_constants.cpp
+++ b/core/global_constants.cpp
@@ -38,6 +38,7 @@
struct _GlobalConstant {
#ifdef DEBUG_METHODS_ENABLED
StringName enum_name;
+ bool ignore_value_in_docs;
#endif
const char *name;
int value;
@@ -45,8 +46,9 @@ struct _GlobalConstant {
_GlobalConstant() {}
#ifdef DEBUG_METHODS_ENABLED
- _GlobalConstant(const StringName &p_enum_name, const char *p_name, int p_value) :
+ _GlobalConstant(const StringName &p_enum_name, const char *p_name, int p_value, bool p_ignore_value_in_docs = false) :
enum_name(p_enum_name),
+ ignore_value_in_docs(p_ignore_value_in_docs),
name(p_name),
value(p_value) {
}
@@ -71,6 +73,15 @@ static Vector<_GlobalConstant> _global_constants;
#define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \
_global_constants.push_back(_GlobalConstant(__constant_get_enum_name(m_constant, #m_constant), m_custom_name, m_constant));
+#define BIND_GLOBAL_CONSTANT_NO_VAL(m_constant) \
+ _global_constants.push_back(_GlobalConstant(StringName(), #m_constant, m_constant, true));
+
+#define BIND_GLOBAL_ENUM_CONSTANT_NO_VAL(m_constant) \
+ _global_constants.push_back(_GlobalConstant(__constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant, true));
+
+#define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \
+ _global_constants.push_back(_GlobalConstant(__constant_get_enum_name(m_constant, #m_constant), m_custom_name, m_constant, true));
+
#else
#define BIND_GLOBAL_CONSTANT(m_constant) \
@@ -82,6 +93,15 @@ static Vector<_GlobalConstant> _global_constants;
#define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \
_global_constants.push_back(_GlobalConstant(m_custom_name, m_constant));
+#define BIND_GLOBAL_CONSTANT_NO_VAL(m_constant) \
+ _global_constants.push_back(_GlobalConstant(#m_constant, m_constant));
+
+#define BIND_GLOBAL_ENUM_CONSTANT_NO_VAL(m_constant) \
+ _global_constants.push_back(_GlobalConstant(#m_constant, m_constant));
+
+#define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \
+ _global_constants.push_back(_GlobalConstant(m_custom_name, m_constant));
+
#endif
VARIANT_ENUM_CAST(KeyList);
@@ -368,7 +388,7 @@ void register_global_constants() {
BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_ALT);
BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_META);
BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_CTRL);
- BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_CMD);
+ BIND_GLOBAL_ENUM_CONSTANT_NO_VAL(KEY_MASK_CMD);
BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_KPAD);
BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_GROUP_SWITCH);
@@ -648,10 +668,18 @@ int GlobalConstants::get_global_constant_count() {
StringName GlobalConstants::get_global_constant_enum(int p_idx) {
return _global_constants[p_idx].enum_name;
}
+
+bool GlobalConstants::get_ignore_value_in_docs(int p_idx) {
+ return _global_constants[p_idx].ignore_value_in_docs;
+}
#else
StringName GlobalConstants::get_global_constant_enum(int p_idx) {
return StringName();
}
+
+bool GlobalConstants::get_ignore_value_in_docs(int p_idx) {
+ return false;
+}
#endif
const char *GlobalConstants::get_global_constant_name(int p_idx) {
diff --git a/core/global_constants.h b/core/global_constants.h
index a20b5ecd9a..989633a6fa 100644
--- a/core/global_constants.h
+++ b/core/global_constants.h
@@ -37,6 +37,7 @@ class GlobalConstants {
public:
static int get_global_constant_count();
static StringName get_global_constant_enum(int p_idx);
+ static bool get_ignore_value_in_docs(int p_idx);
static const char *get_global_constant_name(int p_idx);
static int get_global_constant_value(int p_idx);
};
diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp
index 40debae9e5..46e45500bf 100644
--- a/core/io/http_client.cpp
+++ b/core/io/http_client.cpp
@@ -451,7 +451,7 @@ Error HTTPClient::poll() {
}
}
- // This is a HEAD request, we wont receive anything.
+ // This is a HEAD request, we won't receive anything.
if (head_request) {
body_size = 0;
body_left = 0;
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 1c603865ad..b90841a5ef 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -371,6 +371,7 @@ Error JSON::_parse_array(Array &array, const CharType *p_str, int &index, int p_
need_comma = true;
}
+ r_err_str = "Expected ']'";
return ERR_PARSE_ERROR;
}
@@ -433,6 +434,7 @@ Error JSON::_parse_object(Dictionary &object, const CharType *p_str, int &index,
}
}
+ r_err_str = "Expected '}'";
return ERR_PARSE_ERROR;
}
diff --git a/core/math/aabb.h b/core/math/aabb.h
index 4106fbb93c..bd1f3a1a36 100644
--- a/core/math/aabb.h
+++ b/core/math/aabb.h
@@ -99,6 +99,10 @@ public:
_FORCE_INLINE_ void project_range_in_plane(const Plane &p_plane, real_t &r_min, real_t &r_max) const;
_FORCE_INLINE_ void expand_to(const Vector3 &p_vector); /** expand to contain a point if necessary */
+ _FORCE_INLINE_ AABB abs() const {
+ return AABB(Vector3(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0), position.z + MIN(size.z, 0)), size.abs());
+ }
+
operator String() const;
_FORCE_INLINE_ AABB() {}
diff --git a/core/math/geometry_3d.cpp b/core/math/geometry_3d.cpp
index 7807ab19a7..2c19fe2085 100644
--- a/core/math/geometry_3d.cpp
+++ b/core/math/geometry_3d.cpp
@@ -648,7 +648,7 @@ Geometry3D::MeshData Geometry3D::build_convex_mesh(const Vector<Plane> &p_planes
Vector<Vector3> vertices;
- Vector3 center = p.get_any_point();
+ Vector3 center = p.center();
// make a quad clockwise
vertices.push_back(center - up * subplane_size + right * subplane_size);
vertices.push_back(center - up * subplane_size - right * subplane_size);
diff --git a/core/math/plane.cpp b/core/math/plane.cpp
index df37ceb0e5..4200484c59 100644
--- a/core/math/plane.cpp
+++ b/core/math/plane.cpp
@@ -52,10 +52,6 @@ Plane Plane::normalized() const {
return p;
}
-Vector3 Plane::get_any_point() const {
- return get_normal() * d;
-}
-
Vector3 Plane::get_any_perpendicular_normal() const {
static const Vector3 p1 = Vector3(1, 0, 0);
static const Vector3 p2 = Vector3(0, 1, 0);
diff --git a/core/math/plane.h b/core/math/plane.h
index 9a3e5a485f..70a6111edd 100644
--- a/core/math/plane.h
+++ b/core/math/plane.h
@@ -47,7 +47,6 @@ public:
/* Plane-Point operations */
_FORCE_INLINE_ Vector3 center() const { return normal * d; }
- Vector3 get_any_point() const;
Vector3 get_any_perpendicular_normal() const;
_FORCE_INLINE_ bool is_point_over(const Vector3 &p_point) const; ///< Point is over plane
diff --git a/core/object.cpp b/core/object.cpp
index 8abea9ca7e..ba002024e6 100644
--- a/core/object.cpp
+++ b/core/object.cpp
@@ -675,7 +675,7 @@ Variant Object::_call_deferred_bind(const Variant **p_args, int p_argcount, Call
StringName method = *p_args[0];
- MessageQueue::get_singleton()->push_call(get_instance_id(), method, &p_args[1], p_argcount - 1);
+ MessageQueue::get_singleton()->push_call(get_instance_id(), method, &p_args[1], p_argcount - 1, true);
return Variant();
}
diff --git a/core/project_settings.cpp b/core/project_settings.cpp
index 638987bb2f..e08a44d0c1 100644
--- a/core/project_settings.cpp
+++ b/core/project_settings.cpp
@@ -122,6 +122,22 @@ void ProjectSettings::set_restart_if_changed(const String &p_name, bool p_restar
props[p_name].restart_if_changed = p_restart;
}
+void ProjectSettings::set_ignore_value_in_docs(const String &p_name, bool p_ignore) {
+ ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+#ifdef DEBUG_METHODS_ENABLED
+ props[p_name].ignore_value_in_docs = p_ignore;
+#endif
+}
+
+bool ProjectSettings::get_ignore_value_in_docs(const String &p_name) const {
+ ERR_FAIL_COND_V_MSG(!props.has(p_name), false, "Request for nonexistent project setting: " + p_name + ".");
+#ifdef DEBUG_METHODS_ENABLED
+ return props[p_name].ignore_value_in_docs;
+#else
+ return false;
+#endif
+}
+
String ProjectSettings::globalize_path(const String &p_path) const {
if (p_path.begins_with("res://")) {
if (resource_path != "") {
@@ -876,7 +892,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
}
}
-Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed) {
+Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed, bool p_ignore_value_in_docs) {
Variant ret;
if (!ProjectSettings::get_singleton()->has_setting(p_var)) {
ProjectSettings::get_singleton()->set(p_var, p_default);
@@ -886,6 +902,7 @@ Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restar
ProjectSettings::get_singleton()->set_initial_value(p_var, p_default);
ProjectSettings::get_singleton()->set_builtin_order(p_var);
ProjectSettings::get_singleton()->set_restart_if_changed(p_var, p_restart_if_changed);
+ ProjectSettings::get_singleton()->set_ignore_value_in_docs(p_var, p_ignore_value_in_docs);
return ret;
}
diff --git a/core/project_settings.h b/core/project_settings.h
index 4aceafe3c0..659ee402b3 100644
--- a/core/project_settings.h
+++ b/core/project_settings.h
@@ -62,6 +62,9 @@ protected:
bool hide_from_editor = false;
bool overridden = false;
bool restart_if_changed = false;
+#ifdef DEBUG_METHODS_ENABLED
+ bool ignore_value_in_docs = false;
+#endif
VariantContainer() {}
@@ -125,6 +128,9 @@ public:
void set_initial_value(const String &p_name, const Variant &p_value);
void set_restart_if_changed(const String &p_name, bool p_restart);
+ void set_ignore_value_in_docs(const String &p_name, bool p_ignore);
+ bool get_ignore_value_in_docs(const String &p_name) const;
+
bool property_can_revert(const String &p_name);
Variant property_get_revert(const String &p_name);
@@ -167,9 +173,11 @@ public:
};
//not a macro any longer
-Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed = false);
+Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed = false, bool p_ignore_value_in_docs = false);
#define GLOBAL_DEF(m_var, m_value) _GLOBAL_DEF(m_var, m_value)
#define GLOBAL_DEF_RST(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true)
+#define GLOBAL_DEF_NOVAL(m_var, m_value) _GLOBAL_DEF(m_var, m_value, false, true)
+#define GLOBAL_DEF_RST_NOVAL(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true, true)
#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var)
#endif // PROJECT_SETTINGS_H
diff --git a/core/script_language.h b/core/script_language.h
index 314b047027..6ba38399a1 100644
--- a/core/script_language.h
+++ b/core/script_language.h
@@ -294,7 +294,8 @@ public:
/* EDITOR FUNCTIONS */
struct Warning {
- int line;
+ int start_line = -1, end_line = -1;
+ int leftmost_column = -1, rightmost_column = -1;
int code;
String string_code;
String message;
diff --git a/core/string_name.h b/core/string_name.h
index df6b458581..886ddd0ee7 100644
--- a/core/string_name.h
+++ b/core/string_name.h
@@ -35,6 +35,8 @@
#include "core/safe_refcount.h"
#include "core/ustring.h"
+class Main;
+
struct StaticCString {
const char *ptr;
static StaticCString create(const char *p_ptr);
@@ -73,7 +75,7 @@ class StringName {
void unref();
friend void register_core_types();
friend void unregister_core_types();
-
+ friend class Main;
static Mutex mutex;
static void setup();
static void cleanup();
diff --git a/core/ustring.cpp b/core/ustring.cpp
index 5d3cf5f1a4..572ad1af59 100644
--- a/core/ustring.cpp
+++ b/core/ustring.cpp
@@ -1025,6 +1025,9 @@ String String::chr(CharType p_char) {
}
String String::num(double p_num, int p_decimals) {
+ if (Math::is_nan(p_num)) {
+ return "nan";
+ }
#ifndef NO_USE_STDLIB
if (p_decimals > 16) {
@@ -1313,6 +1316,9 @@ String String::num_real(double p_num) {
}
String String::num_scientific(double p_num) {
+ if (Math::is_nan(p_num)) {
+ return "nan";
+ }
#ifndef NO_USE_STDLIB
char buf[256];
diff --git a/core/variant.cpp b/core/variant.cpp
index f6b7e2821a..afd01b3359 100644
--- a/core/variant.cpp
+++ b/core/variant.cpp
@@ -1454,7 +1454,7 @@ Variant::operator signed long() const {
case INT:
return _data._int;
case FLOAT:
- return _data._real;
+ return _data._float;
case STRING:
return operator String().to_int();
default: {
@@ -1474,7 +1474,7 @@ Variant::operator unsigned long() const {
case INT:
return _data._int;
case FLOAT:
- return _data._real;
+ return _data._float;
case STRING:
return operator String().to_int();
default: {
diff --git a/core/variant_call.cpp b/core/variant_call.cpp
index 0aa1339401..8afa24e63d 100644
--- a/core/variant_call.cpp
+++ b/core/variant_call.cpp
@@ -455,7 +455,6 @@ struct _VariantCall {
VCALL_LOCALMEM0R(Plane, normalized);
VCALL_LOCALMEM0R(Plane, center);
- VCALL_LOCALMEM0R(Plane, get_any_point);
VCALL_LOCALMEM1R(Plane, is_equal_approx);
VCALL_LOCALMEM1R(Plane, is_point_over);
VCALL_LOCALMEM1R(Plane, distance_to);
@@ -843,6 +842,7 @@ struct _VariantCall {
#define VCALL_PTR5R(m_type, m_method) \
static void _call_##m_type##_##m_method(Variant &r_ret, Variant &p_self, const Variant **p_args) { r_ret = reinterpret_cast<m_type *>(p_self._data._ptr)->m_method(*p_args[0], *p_args[1], *p_args[2], *p_args[3], *p_args[4]); }
+ VCALL_PTR0R(AABB, abs);
VCALL_PTR0R(AABB, get_area);
VCALL_PTR0R(AABB, has_no_area);
VCALL_PTR0R(AABB, has_no_surface);
@@ -1980,7 +1980,6 @@ void register_variant_methods() {
ADDFUNC0R(PLANE, PLANE, Plane, normalized, varray());
ADDFUNC0R(PLANE, VECTOR3, Plane, center, varray());
- ADDFUNC0R(PLANE, VECTOR3, Plane, get_any_point, varray());
ADDFUNC1R(PLANE, BOOL, Plane, is_equal_approx, PLANE, "plane", varray());
ADDFUNC1R(PLANE, BOOL, Plane, is_point_over, VECTOR3, "point", varray());
ADDFUNC1R(PLANE, FLOAT, Plane, distance_to, VECTOR3, "point", varray());
@@ -2220,6 +2219,7 @@ void register_variant_methods() {
//pointerbased
+ ADDFUNC0R(AABB, AABB, AABB, abs, varray());
ADDFUNC0R(AABB, FLOAT, AABB, get_area, varray());
ADDFUNC0R(AABB, BOOL, AABB, has_no_area, varray());
ADDFUNC0R(AABB, BOOL, AABB, has_no_surface, varray());
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 9a28a0d085..7f7df33471 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -898,7 +898,7 @@
<constant name="KEY_MASK_CTRL" value="268435456" enum="KeyModifierMask">
Ctrl key mask.
</constant>
- <constant name="KEY_MASK_CMD" value="268435456" enum="KeyModifierMask">
+ <constant name="KEY_MASK_CMD" value="platform-dependent" enum="KeyModifierMask">
Command key mask. On macOS, this is equivalent to [constant KEY_MASK_META]. On other platforms, this is equivalent to [constant KEY_MASK_CTRL]. This mask should be preferred to [constant KEY_MASK_META] or [constant KEY_MASK_CTRL] for system shortcuts as it handles all platforms correctly.
</constant>
<constant name="KEY_MASK_KPAD" value="536870912" enum="KeyModifierMask">
diff --git a/doc/classes/AABB.xml b/doc/classes/AABB.xml
index 3f2f27d121..ae80abc5d2 100644
--- a/doc/classes/AABB.xml
+++ b/doc/classes/AABB.xml
@@ -18,7 +18,14 @@
<argument index="1" name="size" type="Vector3">
</argument>
<description>
- Optional constructor, accepts position and size.
+ Constructs an [AABB] from a position and size.
+ </description>
+ </method>
+ <method name="abs">
+ <return type="AABB">
+ </return>
+ <description>
+ Returns an AABB with equivalent position and size, modified so that the most-negative corner is the origin and the size is positive.
</description>
</method>
<method name="encloses">
@@ -197,13 +204,14 @@
</methods>
<members>
<member name="end" type="Vector3" setter="" getter="" default="Vector3( 0, 0, 0 )">
- Ending corner. This is calculated as [code]position + size[/code]. Changing this property changes [member size] accordingly.
+ Ending corner. This is calculated as [code]position + size[/code]. Setting this value will change the size.
</member>
<member name="position" type="Vector3" setter="" getter="" default="Vector3( 0, 0, 0 )">
- Beginning corner.
+ Beginning corner. Typically has values lower than [member end].
</member>
<member name="size" type="Vector3" setter="" getter="" default="Vector3( 0, 0, 0 )">
- Size from position to end.
+ Size from [member position] to [member end]. Typically all components are positive.
+ If the size is negative, you can use [method abs] to fix it.
</member>
</members>
<constants>
diff --git a/doc/classes/AESContext.xml b/doc/classes/AESContext.xml
index ab4d0e0bc3..ff433370bd 100644
--- a/doc/classes/AESContext.xml
+++ b/doc/classes/AESContext.xml
@@ -51,7 +51,7 @@
<return type="PackedByteArray">
</return>
<description>
- Get the current IV state for this context (IV gets updated when calling [method update]). You normally don't need this funciton.
+ Get the current IV state for this context (IV gets updated when calling [method update]). You normally don't need this function.
Note: This function only makes sense when the context is started with [constant MODE_CBC_ENCRYPT] or [constant MODE_CBC_DECRYPT].
</description>
</method>
diff --git a/doc/classes/BaseButton.xml b/doc/classes/BaseButton.xml
index 5e908b0e53..3812b45b13 100644
--- a/doc/classes/BaseButton.xml
+++ b/doc/classes/BaseButton.xml
@@ -13,7 +13,7 @@
<return type="void">
</return>
<description>
- Called when the button is pressed.
+ Called when the button is pressed. If you need to know the button's pressed state (and [member toggle_mode] is active), use [method _toggled] instead.
</description>
</method>
<method name="_toggled" qualifiers="virtual">
@@ -89,6 +89,7 @@
<signal name="pressed">
<description>
Emitted when the button is toggled or pressed. This is on [signal button_down] if [member action_mode] is [constant ACTION_MODE_BUTTON_PRESS] and on [signal button_up] otherwise.
+ If you need to know the button's pressed state (and [member toggle_mode] is active), use [signal toggled] instead.
</description>
</signal>
<signal name="toggled">
diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml
index 3952ea2d27..47433d7adc 100644
--- a/doc/classes/Basis.xml
+++ b/doc/classes/Basis.xml
@@ -4,10 +4,13 @@
3×3 matrix datatype.
</brief_description>
<description>
- 3×3 matrix used for 3D rotation and scale. Contains 3 vector fields X, Y and Z as its columns, which can be interpreted as the local basis vectors of a transformation. Can also be accessed as array of 3D vectors. These vectors are orthogonal to each other, but are not necessarily normalized (due to scaling). Almost always used as an orthogonal basis for a [Transform].
- For such use, it is composed of a scaling and a rotation matrix, in that order (M = R.S).
+ 3×3 matrix used for 3D rotation and scale. Almost always used as an orthogonal basis for a Transform.
+ Contains 3 vector fields X, Y and Z as its columns, which are typically interpreted as the local basis vectors of a transformation. For such use, it is composed of a scaling and a rotation matrix, in that order (M = R.S).
+ Can also be accessed as array of 3D vectors. These vectors are normally orthogonal to each other, but are not necessarily normalized (due to scaling).
+ For more information, read the "Matrices and transforms" documentation article.
</description>
<tutorials>
+ <link>https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html</link>
<link>https://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html</link>
</tutorials>
<methods>
@@ -17,7 +20,7 @@
<argument index="0" name="from" type="Quat">
</argument>
<description>
- Create a rotation matrix from the given quaternion.
+ Constructs a pure rotation basis matrix from the given quaternion.
</description>
</method>
<method name="Basis">
@@ -26,7 +29,8 @@
<argument index="0" name="from" type="Vector3">
</argument>
<description>
- Create a rotation matrix (in the YXZ convention: first Z, then X, and Y last) from the specified Euler angles, given in the vector format as (X angle, Y angle, Z angle).
+ Constructs a pure rotation basis matrix from the given Euler angles (in the YXZ convention: when *composing*, first Y, then X, and Z last), given in the vector format as (X angle, Y angle, Z angle).
+ Consider using the [Quat] constructor instead, which uses a quaternion instead of Euler angles.
</description>
</method>
<method name="Basis">
@@ -37,7 +41,7 @@
<argument index="1" name="phi" type="float">
</argument>
<description>
- Create a rotation matrix which rotates around the given axis by the specified angle, in radians. The axis must be a normalized vector.
+ Constructs a pure rotation basis matrix, rotated around the given [code]axis[/code] by [code]phi[/code], in radians. The axis must be a normalized vector.
</description>
</method>
<method name="Basis">
@@ -50,28 +54,30 @@
<argument index="2" name="z_axis" type="Vector3">
</argument>
<description>
- Create a matrix from 3 axis vectors.
+ Constructs a basis matrix from 3 axis vectors (matrix columns).
</description>
</method>
<method name="determinant">
<return type="float">
</return>
<description>
- Returns the determinant of the matrix.
+ Returns the determinant of the basis matrix. If the basis is uniformly scaled, its determinant is the square of the scale.
+ A negative determinant means the basis has a negative scale. A zero determinant means the basis isn't invertible, and is usually considered invalid.
</description>
</method>
<method name="get_euler">
<return type="Vector3">
</return>
<description>
- Returns the basis's rotation in the form of Euler angles (in the YXZ convention: first Z, then X, and Y last). The returned vector contains the rotation angles in the format (X angle, Y angle, Z angle). See [method get_rotation_quat] if you need a quaternion instead.
+ Returns the basis's rotation in the form of Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last). The returned vector contains the rotation angles in the format (X angle, Y angle, Z angle).
+ Consider using the [method get_rotation_quat] method instead, which returns a [Quat] quaternion instead of Euler angles.
</description>
</method>
<method name="get_orthogonal_index">
<return type="int">
</return>
<description>
- This function considers a discretization of rotations into 24 points on unit sphere, lying along the vectors (x,y,z) with each component being either -1, 0, or 1, and returns the index of the point best representing the orientation of the object. It is mainly used by the grid map editor. For further details, refer to the Godot source code.
+ This function considers a discretization of rotations into 24 points on unit sphere, lying along the vectors (x,y,z) with each component being either -1, 0, or 1, and returns the index of the point best representing the orientation of the object. It is mainly used by the [GridMap] editor. For further details, refer to the Godot source code.
</description>
</method>
<method name="get_rotation_quat">
@@ -193,25 +199,26 @@
<argument index="0" name="v" type="Vector3">
</argument>
<description>
- Returns a vector transformed (multiplied) by the transposed matrix.
+ Returns a vector transformed (multiplied) by the transposed basis matrix.
[b]Note:[/b] This results in a multiplication by the inverse of the matrix only if it represents a rotation-reflection.
</description>
</method>
</methods>
<members>
<member name="x" type="Vector3" setter="" getter="" default="Vector3( 1, 0, 0 )">
- The basis matrix's X vector.
+ The basis matrix's X vector (column 0). Equivalent to array index [code]0[/code].
</member>
<member name="y" type="Vector3" setter="" getter="" default="Vector3( 0, 1, 0 )">
- The basis matrix's Y vector.
+ The basis matrix's Y vector (column 1). Equivalent to array index [code]1[/code].
</member>
<member name="z" type="Vector3" setter="" getter="" default="Vector3( 0, 0, 1 )">
- The basis matrix's Z vector.
+ The basis matrix's Z vector (column 2). Equivalent to array index [code]2[/code].
</member>
</members>
<constants>
<constant name="IDENTITY" value="Basis( 1, 0, 0, 0, 1, 0, 0, 0, 1 )">
- The identity basis. This is identical to calling [code]Basis()[/code] without any parameters. This constant can be used to make your code clearer.
+ The identity basis, with no rotation or scaling applied.
+ This is identical to calling [code]Basis()[/code] without any parameters. This constant can be used to make your code clearer, and for consistency with C#.
</constant>
<constant name="FLIP_X" value="Basis( -1, 0, 0, 0, 1, 0, 0, 0, 1 )">
The basis that will flip something along the X axis when used in a transformation.
diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml
index 17b474531e..240e70db9f 100644
--- a/doc/classes/Color.xml
+++ b/doc/classes/Color.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="Color" version="4.0">
<brief_description>
- Color in RGBA format with some support for ARGB format.
+ Color in RGBA format using floats on the range of 0 to 1.
</brief_description>
<description>
- A color is represented by red, green, and blue [code](r, g, b)[/code] components. Additionally, [code]a[/code] represents the alpha component, often used for transparency. Values are in floating-point and usually range from 0 to 1. Some properties (such as [member CanvasItem.modulate]) may accept values greater than 1.
+ A color represented by red, green, blue, and alpha (RGBA) components. The alpha component is often used for transparency. Values are in floating-point and usually range from 0 to 1. Some properties (such as CanvasItem.modulate) may accept values greater than 1 (overbright or HDR colors).
You can also create a color from standardized color names by using [method @GDScript.ColorN] or directly using the color constants defined here. The standardized color set is based on the [url=https://en.wikipedia.org/wiki/X11_color_names]X11 color names[/url].
If you want to supply values in a range of 0 to 255, you should use [method @GDScript.Color8].
[b]Note:[/b] In a boolean context, a Color will evaluate to [code]false[/code] if it's equal to [code]Color(0, 0, 0, 1)[/code] (opaque black). Otherwise, a Color will always evaluate to [code]true[/code].
@@ -277,37 +277,37 @@
</methods>
<members>
<member name="a" type="float" setter="" getter="" default="1.0">
- Alpha value (range 0 to 1).
+ The color's alpha (transparency) component, typically on the range of 0 to 1.
</member>
<member name="a8" type="int" setter="" getter="" default="255">
- Alpha value (range 0 to 255).
+ Wrapper for [member a] that uses the range 0 to 255 instead of 0 to 1.
</member>
<member name="b" type="float" setter="" getter="" default="0.0">
- Blue value (range 0 to 1).
+ The color's blue component, typically on the range of 0 to 1.
</member>
<member name="b8" type="int" setter="" getter="" default="0">
- Blue value (range 0 to 255).
+ Wrapper for [member b] that uses the range 0 to 255 instead of 0 to 1.
</member>
<member name="g" type="float" setter="" getter="" default="0.0">
- Green value (range 0 to 1).
+ The color's green component, typically on the range of 0 to 1.
</member>
<member name="g8" type="int" setter="" getter="" default="0">
- Green value (range 0 to 255).
+ Wrapper for [member g] that uses the range 0 to 255 instead of 0 to 1.
</member>
<member name="h" type="float" setter="" getter="" default="0.0">
- HSV hue value (range 0 to 1).
+ The HSV hue of this color, on the range 0 to 1.
</member>
<member name="r" type="float" setter="" getter="" default="0.0">
- Red value (range 0 to 1).
+ The color's red component, typically on the range of 0 to 1.
</member>
<member name="r8" type="int" setter="" getter="" default="0">
- Red value (range 0 to 255).
+ Wrapper for [member r] that uses the range 0 to 255 instead of 0 to 1.
</member>
<member name="s" type="float" setter="" getter="" default="0.0">
- HSV saturation value (range 0 to 1).
+ The HSV saturation of this color, on the range 0 to 1.
</member>
<member name="v" type="float" setter="" getter="" default="0.0">
- HSV value (range 0 to 1).
+ The HSV value (brightness) of this color, on the range 0 to 1.
</member>
</members>
<constants>
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index 9dbb843902..1f495bf91a 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -856,7 +856,9 @@
The node's rotation around its pivot, in degrees. See [member rect_pivot_offset] to change the pivot's position.
</member>
<member name="rect_scale" type="Vector2" setter="set_scale" getter="get_scale" default="Vector2( 1, 1 )">
- The node's scale, relative to its [member rect_size]. Change this property to scale the node around its [member rect_pivot_offset].
+ The node's scale, relative to its [member rect_size]. Change this property to scale the node around its [member rect_pivot_offset]. The Control's [member hint_tooltip] will also scale according to this value.
+ [b]Note:[/b] This property is mainly intended to be used for animation purposes. Text inside the Control will look pixelated or blurry when the Control is scaled. To support multiple resolutions in your project, use an appropriate viewport stretch mode as described in the [url=https://docs.godotengine.org/en/latest/tutorials/viewports/multiple_resolutions.html]documentation[/url] instead of scaling Controls individually.
+ [b]Note:[/b] If the Control node is a child of a [Container] node, the scale will be reset to [code]Vector2(1, 1)[/code] when the scene is instanced. To set the Control's scale when it's instanced, wait for one frame using [code]yield(get_tree(), "idle_frame")[/code] then set its [member rect_scale] property.
</member>
<member name="rect_size" type="Vector2" setter="_set_size" getter="get_size" default="Vector2( 0, 0 )">
The size of the node's bounding rectangle, in pixels. [Container] nodes update this property automatically.
diff --git a/doc/classes/DynamicFont.xml b/doc/classes/DynamicFont.xml
index 0864c3ba36..24df056c6b 100644
--- a/doc/classes/DynamicFont.xml
+++ b/doc/classes/DynamicFont.xml
@@ -26,6 +26,14 @@
Adds a fallback font.
</description>
</method>
+ <method name="get_available_chars" qualifiers="const">
+ <return type="String">
+ </return>
+ <description>
+ Returns a string containing all the characters available in the main and all the fallback fonts.
+ If a given character is included in more than one font, it appears only once in the returned string.
+ </description>
+ </method>
<method name="get_fallback" qualifiers="const">
<return type="DynamicFontData">
</return>
diff --git a/doc/classes/EditorFileSystem.xml b/doc/classes/EditorFileSystem.xml
index 9bb51af2d0..5461dccd27 100644
--- a/doc/classes/EditorFileSystem.xml
+++ b/doc/classes/EditorFileSystem.xml
@@ -97,6 +97,7 @@
<argument index="0" name="resources" type="PackedStringArray">
</argument>
<description>
+ Emitted if at least one resource is reloaded when the filesystem is scanned.
</description>
</signal>
<signal name="sources_changed">
diff --git a/doc/classes/EditorFileSystemDirectory.xml b/doc/classes/EditorFileSystemDirectory.xml
index 096fe5df8f..b852ae1063 100644
--- a/doc/classes/EditorFileSystemDirectory.xml
+++ b/doc/classes/EditorFileSystemDirectory.xml
@@ -67,6 +67,7 @@
<argument index="0" name="idx" type="int">
</argument>
<description>
+ Returns the base class of the script class defined in the file at index [code]idx[/code]. If the file doesn't define a script class using the [code]class_name[/code] syntax, this will return an empty string.
</description>
</method>
<method name="get_file_script_class_name" qualifiers="const">
@@ -75,6 +76,7 @@
<argument index="0" name="idx" type="int">
</argument>
<description>
+ Returns the name of the script class defined in the file at index [code]idx[/code]. If the file doesn't define a script class using the [code]class_name[/code] syntax, this will return an empty string.
</description>
</method>
<method name="get_file_type" qualifiers="const">
diff --git a/doc/classes/EditorImportPlugin.xml b/doc/classes/EditorImportPlugin.xml
index 0aac59c727..ea2dfae9a5 100644
--- a/doc/classes/EditorImportPlugin.xml
+++ b/doc/classes/EditorImportPlugin.xml
@@ -83,6 +83,16 @@
<argument index="1" name="options" type="Dictionary">
</argument>
<description>
+ This method can be overridden to hide specific import options if conditions are met. This is mainly useful for hiding options that depend on others if one of them is disabled. For example:
+ [codeblock]
+ func get_option_visibility(option, options):
+ # Only show the lossy quality setting if the compression mode is set to "Lossy".
+ if option == "compress/lossy_quality" and options.has("compress/mode"):
+ return int(options["compress/mode"]) == COMPRESS_LOSSY
+
+ return true
+ [/codeblock]
+ Return [code]true[/code] to make all options always visible.
</description>
</method>
<method name="get_preset_count" qualifiers="virtual">
@@ -150,6 +160,8 @@
<argument index="4" name="gen_files" type="Array">
</argument>
<description>
+ Imports [code]source_file[/code] into [code]save_path[/code] with the import [code]options[/code] specified. The [code]platform_variants[/code] and [code]gen_files[/code] arrays will be modified by this function.
+ This method must be overridden to do the actual importing work. See this class' description for an example of overriding this method.
</description>
</method>
</methods>
diff --git a/doc/classes/EditorInspector.xml b/doc/classes/EditorInspector.xml
index 2f62fe9e40..5fbe427f67 100644
--- a/doc/classes/EditorInspector.xml
+++ b/doc/classes/EditorInspector.xml
@@ -14,41 +14,50 @@
<return type="void">
</return>
<description>
+ Refreshes the inspector.
+ [b]Note:[/b] To save on CPU resources, calling this method will do nothing if the time specified in [code]docks/property_editor/auto_refresh_interval[/code] editor setting hasn't passed yet since this method was last called. (By default, this interval is set to 0.3 seconds.)
</description>
</method>
</methods>
<members>
- <member name="scroll_horizontal_enabled" type="bool" setter="set_enable_h_scroll" getter="is_h_scroll_enabled" override="true" default="false" />
+ <member name="scroll_horizontal_enabled" type="bool" setter="set_enable_h_scroll" getter="is_h_scroll_enabled" override="true" default="false">
+ If [code]true[/code], horizontal scrolling is enabled. An horizontal scroll bar will display at the bottom of the inspector.
+ </member>
</members>
<signals>
<signal name="object_id_selected">
<argument index="0" name="id" type="int">
</argument>
<description>
+ Emitted when the Edit button of an [Object] has been pressed in the inspector. This is mainly used in the remote scene tree inspector.
</description>
</signal>
<signal name="property_deleted">
<argument index="0" name="property" type="String">
</argument>
<description>
+ Emitted when a property is removed from the inspector.
</description>
</signal>
<signal name="property_edited">
<argument index="0" name="property" type="String">
</argument>
<description>
+ Emitted when a property is edited in the inspector.
</description>
</signal>
<signal name="property_keyed">
<argument index="0" name="property" type="String">
</argument>
<description>
+ Emitted when a property is keyed in the inspector. Properties can be keyed by clicking the "key" icon next to a property when the Animation panel is toggled.
</description>
</signal>
<signal name="property_selected">
<argument index="0" name="property" type="String">
</argument>
<description>
+ Emitted when a property is selected in the inspector.
</description>
</signal>
<signal name="property_toggled">
@@ -57,6 +66,8 @@
<argument index="1" name="checked" type="bool">
</argument>
<description>
+ Emitted when a boolean property is toggled in the inspector.
+ [b]Note:[/b] This signal is never emitted if the internal [code]autoclear[/code] property enabled. Since this property is always enabled in the editor inspector, this signal is never emitted by the editor itself.
</description>
</signal>
<signal name="resource_selected">
@@ -65,10 +76,12 @@
<argument index="1" name="prop" type="String">
</argument>
<description>
+ Emitted when a resource is selected in the inspector.
</description>
</signal>
<signal name="restart_requested">
<description>
+ Emitted when a property that requires a restart to be applied is edited in the inspector. This is only used in the Project Settings and Editor Settings.
</description>
</signal>
</signals>
diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml
index 1d877e632e..8b2b9c9350 100644
--- a/doc/classes/EditorInterface.xml
+++ b/doc/classes/EditorInterface.xml
@@ -23,13 +23,14 @@
<return type="Control">
</return>
<description>
- Returns the main container of Godot editor's window. You can use it, for example, to retrieve the size of the container and place your controls accordingly.
+ Returns the main container of Godot editor's window. For example, you can use it to retrieve the size of the container and place your controls accordingly.
</description>
</method>
<method name="get_current_path" qualifiers="const">
<return type="String">
</return>
<description>
+ Returns the current path being viewed in the [FileSystemDock].
</description>
</method>
<method name="get_edited_scene_root">
@@ -43,26 +44,29 @@
<return type="EditorSettings">
</return>
<description>
- Returns the [EditorSettings].
+ Returns the editor's [EditorSettings] instance.
</description>
</method>
<method name="get_editor_viewport">
<return type="Control">
</return>
<description>
- Returns the editor [Viewport].
+ Returns the editor's [Viewport] instance.
+ [b]Note:[/b] This returns the main editor viewport containing the whole editor, not the 2D or 3D viewports specifically.
</description>
</method>
<method name="get_file_system_dock">
<return type="FileSystemDock">
</return>
<description>
+ Returns the editor's [FileSystemDock] instance.
</description>
</method>
<method name="get_inspector" qualifiers="const">
<return type="EditorInspector">
</return>
<description>
+ Returns the editor's [EditorInspector] instance.
</description>
</method>
<method name="get_open_scenes" qualifiers="const">
@@ -83,34 +87,35 @@
<return type="EditorFileSystem">
</return>
<description>
- Returns the [EditorFileSystem].
+ Returns the editor's [EditorFileSystem] instance.
</description>
</method>
<method name="get_resource_previewer">
<return type="EditorResourcePreview">
</return>
<description>
- Returns the [EditorResourcePreview].
+ Returns the editor's [EditorResourcePreview] instance.
</description>
</method>
<method name="get_script_editor">
<return type="ScriptEditor">
</return>
<description>
- Returns the [ScriptEditor].
+ Returns the editor's [ScriptEditor] instance.
</description>
</method>
<method name="get_selected_path" qualifiers="const">
<return type="String">
</return>
<description>
+ Returns the path of the directory currently selected in the [FileSystemDock]. If a file is selected, its base directory will be returned using [method String.get_base_dir] instead.
</description>
</method>
<method name="get_selection">
<return type="EditorSelection">
</return>
<description>
- Returns the [EditorSelection].
+ Returns the editor's [EditorSelection] instance.
</description>
</method>
<method name="inspect_object">
@@ -121,14 +126,14 @@
<argument index="1" name="for_property" type="String" default="&quot;&quot;">
</argument>
<description>
- Shows the given property on the given [code]object[/code] in the Editor's Inspector dock.
+ Shows the given property on the given [code]object[/code] in the editor's Inspector dock.
</description>
</method>
<method name="is_playing_scene" qualifiers="const">
<return type="bool">
</return>
<description>
- Returns [code]true[/code], if a scene is currently being played; [code]false[/code] otherwise. Paused scenes are considered as being played.
+ Returns [code]true[/code] if a scene is currently being played, [code]false[/code] otherwise. Paused scenes are considered as being played.
</description>
</method>
<method name="is_plugin_enabled" qualifiers="const">
@@ -137,7 +142,7 @@
<argument index="0" name="plugin" type="String">
</argument>
<description>
- Returns the enabled status of a plugin. The plugin name is the same as its directory name.
+ Returns [code]true[/code] if the specified [code]plugin[/code] is enabled. The plugin name is the same as its directory name.
</description>
</method>
<method name="make_mesh_previews">
@@ -225,6 +230,7 @@
<argument index="0" name="name" type="String">
</argument>
<description>
+ Sets the editor's current main screen to the one specified in [code]name[/code]. [code]name[/code] must match the text of the tab in question exactly ([code]2D[/code], [code]3D[/code], [code]Script[/code], [code]AssetLib[/code]).
</description>
</method>
<method name="set_plugin_enabled">
diff --git a/doc/classes/EditorNode3DGizmo.xml b/doc/classes/EditorNode3DGizmo.xml
index 6d695ddeea..45541b9263 100644
--- a/doc/classes/EditorNode3DGizmo.xml
+++ b/doc/classes/EditorNode3DGizmo.xml
@@ -15,6 +15,7 @@
<argument index="0" name="segments" type="PackedVector3Array">
</argument>
<description>
+ Adds the specified [code]segments[/code] to the gizmo's collision shape for picking. Call this function during [method redraw].
</description>
</method>
<method name="add_collision_triangles">
@@ -69,6 +70,7 @@
<argument index="3" name="material" type="Material" default="null">
</argument>
<description>
+ Adds a mesh to the gizmo with the specified [code]billboard[/code] state, [code]skeleton[/code] and [code]material[/code]. If [code]billboard[/code] is [code]true[/code], the mesh will rotate to always face the camera. Call this function during [method redraw].
</description>
</method>
<method name="add_unscaled_billboard">
@@ -88,6 +90,7 @@
<return type="void">
</return>
<description>
+ Removes everything in the gizmo including meshes, collisions and handles.
</description>
</method>
<method name="commit_handle" qualifiers="virtual">
@@ -143,14 +146,14 @@
<argument index="0" name="index" type="int">
</argument>
<description>
- Gets whether a handle is highlighted or not.
+ Returns [code]true[/code] if the handle at index [code]index[/code] is highlighted by being hovered with the mouse.
</description>
</method>
<method name="redraw" qualifiers="virtual">
<return type="void">
</return>
<description>
- This function is called when the Node3D this gizmo refers to changes (the [method Node3D.update_gizmo] is called).
+ This function is called when the [Node3D] this gizmo refers to changes (the [method Node3D.update_gizmo] is called).
</description>
</method>
<method name="set_handle" qualifiers="virtual">
@@ -173,6 +176,7 @@
<argument index="0" name="hidden" type="bool">
</argument>
<description>
+ Sets the gizmo's hidden state. If [code]true[/code], the gizmo will be hidden. If [code]false[/code], it will be shown.
</description>
</method>
<method name="set_spatial_node">
@@ -181,6 +185,7 @@
<argument index="0" name="node" type="Node">
</argument>
<description>
+ Sets the reference [Node3D] node for the gizmo. [code]node[/code] must inherit from [Node3D].
</description>
</method>
</methods>
diff --git a/doc/classes/EditorNode3DGizmoPlugin.xml b/doc/classes/EditorNode3DGizmoPlugin.xml
index ca75b47fbf..b81281b5b6 100644
--- a/doc/classes/EditorNode3DGizmoPlugin.xml
+++ b/doc/classes/EditorNode3DGizmoPlugin.xml
@@ -139,6 +139,8 @@
<return type="String">
</return>
<description>
+ Override this method to set the gizmo's priority. Higher values correspond to higher priority. If a gizmo with higher priority conflicts with another gizmo, only the gizmo with higher priority will be used.
+ All built-in editor gizmos return a priority of [code]-1[/code]. If not overridden, this method will return [code]0[/code], which means custom gizmos will automatically override built-in gizmos.
</description>
</method>
<method name="has_gizmo" qualifiers="virtual">
diff --git a/doc/classes/EditorProperty.xml b/doc/classes/EditorProperty.xml
index 4da3b58b94..f568263ff8 100644
--- a/doc/classes/EditorProperty.xml
+++ b/doc/classes/EditorProperty.xml
@@ -73,24 +73,25 @@
</methods>
<members>
<member name="checkable" type="bool" setter="set_checkable" getter="is_checkable" default="false">
- Used by the inspector, set when property is checkable.
+ Used by the inspector, set to [code]true[/code] when the property is checkable.
</member>
<member name="checked" type="bool" setter="set_checked" getter="is_checked" default="false">
- Used by the inspector, when the property is checked.
+ Used by the inspector, set to [code]true[/code] when the property is checked.
</member>
<member name="deletable" type="bool" setter="set_deletable" getter="is_deletable" default="false">
+ Used by the inspector, set to [code]true[/code] when the property can be deleted by the user.
</member>
<member name="draw_red" type="bool" setter="set_draw_red" getter="is_draw_red" default="false">
- Used by the inspector, when the property must draw with error color.
+ Used by the inspector, set to [code]true[/code] when the property must draw with error color. This is used for editable children's properties.
</member>
<member name="keying" type="bool" setter="set_keying" getter="is_keying" default="false">
- Used by the inspector, when the property can add keys for animation.
+ Used by the inspector, set to [code]true[/code] when the property can add keys for animation.
</member>
<member name="label" type="String" setter="set_label" getter="get_label" default="&quot;&quot;">
- Sets this property to change the label (if you want to show one).
+ Set this property to change the label (if you want to show one).
</member>
<member name="read_only" type="bool" setter="set_read_only" getter="is_read_only" default="false">
- Used by the inspector, when the property is read-only.
+ Used by the inspector, set to [code]true[/code] when the property is read-only.
</member>
</members>
<signals>
@@ -134,6 +135,7 @@
<argument index="0" name="property" type="StringName">
</argument>
<description>
+ Emitted when a property was deleted. Used internally.
</description>
</signal>
<signal name="property_keyed">
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index 19921ff5c8..0c6a2d61cd 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -5,11 +5,16 @@
</brief_description>
<description>
Object that holds the project-independent editor settings. These settings are generally visible in the [b]Editor &gt; Editor Settings[/b] menu.
- Accessing the settings is done by using the regular [Object] API, such as:
+ Property names use slash delimiters to distinguish sections. Setting values can be of any [Variant] type. It's recommended to use [code]snake_case[/code] for editor settings to be consistent with the Godot editor itself.
+ Accessing the settings can be done using the following methods, such as:
[codeblock]
- settings.set(prop,value)
- settings.get(prop)
- list_of_settings = settings.get_property_list()
+ # `settings.set("some/property", value)` also works as this class overrides `_set()` internally.
+ settings.set_setting("some/property",value)
+
+ # `settings.get("some/property", value)` also works as this class overrides `_get()` internally.
+ settings.get_setting("some/property")
+
+ var list_of_settings = settings.get_property_list()
[/codeblock]
[b]Note:[/b] This class shouldn't be instantiated directly. Instead, access the singleton using [method EditorInterface.get_editor_settings].
</description>
@@ -47,14 +52,14 @@
<argument index="0" name="property" type="String">
</argument>
<description>
- Erase a given setting (pass full property path).
+ Erases the setting whose name is specified by [code]property[/code].
</description>
</method>
<method name="get_favorites" qualifiers="const">
<return type="PackedStringArray">
</return>
<description>
- Gets the list of favorite files and directories for this project.
+ Returns the list of favorite files and directories for this project.
</description>
</method>
<method name="get_project_metadata" qualifiers="const">
@@ -67,20 +72,21 @@
<argument index="2" name="default" type="Variant" default="null">
</argument>
<description>
+ Returns project-specific metadata for the [code]section[/code] and [code]key[/code] specified. If the metadata doesn't exist, [code]default[/code] will be returned instead. See also [method set_project_metadata].
</description>
</method>
<method name="get_project_settings_dir" qualifiers="const">
<return type="String">
</return>
<description>
- Gets the specific project settings path. Projects all have a unique sub-directory inside the settings path where project specific settings are saved.
+ Returns the project-specific settings path. Projects all have a unique subdirectory inside the settings path where project-specific settings are saved.
</description>
</method>
<method name="get_recent_dirs" qualifiers="const">
<return type="PackedStringArray">
</return>
<description>
- Gets the list of recently visited folders in the file dialog for this project.
+ Returns the list of recently visited folders in the file dialog for this project.
</description>
</method>
<method name="get_setting" qualifiers="const">
@@ -89,6 +95,7 @@
<argument index="0" name="name" type="String">
</argument>
<description>
+ Returns the value of the setting specified by [code]name[/code]. This is equivalent to using [method Object.get] on the EditorSettings instance.
</description>
</method>
<method name="get_settings_dir" qualifiers="const">
@@ -106,6 +113,7 @@
<argument index="0" name="name" type="String">
</argument>
<description>
+ Returns [code]true[/code] if the setting specified by [code]name[/code] exists, [code]false[/code] otherwise.
</description>
</method>
<method name="property_can_revert">
@@ -114,6 +122,7 @@
<argument index="0" name="name" type="String">
</argument>
<description>
+ Returns [code]true[/code] if the setting specified by [code]name[/code] can have its value reverted to the default value, [code]false[/code] otherwise. When this method returns [code]true[/code], a Revert button will display next to the setting in the Editor Settings.
</description>
</method>
<method name="property_get_revert">
@@ -122,6 +131,7 @@
<argument index="0" name="name" type="String">
</argument>
<description>
+ Returns the default value of the setting specified by [code]name[/code]. This is the value that would be applied when clicking the Revert button in the Editor Settings.
</description>
</method>
<method name="set_favorites">
@@ -143,6 +153,7 @@
<argument index="2" name="update_current" type="bool">
</argument>
<description>
+ Sets the initial value of the setting specified by [code]name[/code] to [code]value[/code]. This is used to provide a value for the Revert button in the Editor Settings. If [code]update_current[/code] is true, the current value of the setting will be set to [code]value[/code] as well.
</description>
</method>
<method name="set_project_metadata">
@@ -155,6 +166,7 @@
<argument index="2" name="data" type="Variant">
</argument>
<description>
+ Sets project-specific metadata with the [code]section[/code], [code]key[/code] and [code]data[/code] specified. This metadata is stored outside the project folder and therefore won't be checked into version control. See also [method get_project_metadata].
</description>
</method>
<method name="set_recent_dirs">
@@ -174,19 +186,20 @@
<argument index="1" name="value" type="Variant">
</argument>
<description>
+ Sets the [code]value[/code] of the setting specified by [code]name[/code]. This is equivalent to using [method Object.set] on the EditorSettings instance.
</description>
</method>
</methods>
<signals>
<signal name="settings_changed">
<description>
- Emitted when editor settings change.
+ Emitted after any editor setting has changed.
</description>
</signal>
</signals>
<constants>
<constant name="NOTIFICATION_EDITOR_SETTINGS_CHANGED" value="10000">
- Emitted when editor settings change. It used by various editor plugins to update their visuals on theme changes or logic on configuration changes.
+ Emitted after any editor setting has changed. It's used by various editor plugins to update their visuals on theme changes or logic on configuration changes.
</constant>
</constants>
</class>
diff --git a/doc/classes/File.xml b/doc/classes/File.xml
index b90039e496..20bc39ef1f 100644
--- a/doc/classes/File.xml
+++ b/doc/classes/File.xml
@@ -20,6 +20,7 @@
file.close()
return content
[/codeblock]
+ In the example above, the file will be saved in the user data folder as specified in the [url=https://docs.godotengine.org/en/latest/tutorials/io/data_paths.html]Data paths[/url] documentation.
</description>
<tutorials>
<link>https://docs.godotengine.org/en/latest/getting_started/step_by_step/filesystem.html</link>
diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml
index 76153ae041..7e8f0807ac 100644
--- a/doc/classes/HTTPClient.xml
+++ b/doc/classes/HTTPClient.xml
@@ -8,6 +8,7 @@
[b]Note:[/b] This client only needs to connect to a host once (see [method connect_to_host]) to send multiple requests. Because of this, methods that take URLs usually take just the part after the host instead of the full URL, as the client is already connected to a host. See [method request] for a full example and to get started.
A [HTTPClient] should be reused between multiple requests or to connect to different hosts instead of creating one client per request. Supports SSL and SSL server certificate verification. HTTP status codes in the 2xx range indicate success, 3xx redirection (i.e. "try again, but over here"), 4xx something was wrong with the request, and 5xx something went wrong on the server's side.
For more information on HTTP, see https://developer.mozilla.org/en-US/docs/Web/HTTP (or read RFC 2616 to get it straight from the source: https://tools.ietf.org/html/rfc2616).
+ [b]Note:[/b] When performing HTTP requests from a project exported to HTML5, keep in mind the remote server may not allow requests from foreign origins due to [url=https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS]CORS[/url]. If you host the server in question, you should modify its backend to allow requests from foreign origins by adding the [code]Access-Control-Allow-Origin: *[/code] HTTP header.
</description>
<tutorials>
<link>https://docs.godotengine.org/en/latest/tutorials/networking/http_client_class.html</link>
diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml
index 0b0d71fccf..61e0d2e2b9 100644
--- a/doc/classes/HTTPRequest.xml
+++ b/doc/classes/HTTPRequest.xml
@@ -64,6 +64,7 @@
add_child(texture_rect)
texture_rect.texture = texture
[/codeblock]
+ [b]Note:[/b] When performing HTTP requests from a project exported to HTML5, keep in mind the remote server may not allow requests from foreign origins due to [url=https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS]CORS[/url]. If you host the server in question, you should modify its backend to allow requests from foreign origins by adding the [code]Access-Control-Allow-Origin: *[/code] HTTP header.
</description>
<tutorials>
<link>https://docs.godotengine.org/en/latest/tutorials/networking/http_request_class.html</link>
diff --git a/doc/classes/InputEventMouseMotion.xml b/doc/classes/InputEventMouseMotion.xml
index 53e6517c7a..c36089fe9b 100644
--- a/doc/classes/InputEventMouseMotion.xml
+++ b/doc/classes/InputEventMouseMotion.xml
@@ -5,6 +5,7 @@
</brief_description>
<description>
Contains mouse and pen motion information. Supports relative, absolute positions and speed. See [method Node._input].
+ [b]Note:[/b] By default, this event is only emitted once per frame rendered at most. If you need more precise input reporting, call [method Input.set_use_accumulated_input] with [code]false[/code] to make events emitted as often as possible. If you use InputEventMouseMotion to draw lines, consider implementing [url=https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm]Bresenham's line algorithm[/url] as well to avoid visible gaps in lines if the user is moving the mouse quickly.
</description>
<tutorials>
<link>https://docs.godotengine.org/en/latest/tutorials/inputs/mouse_and_input_coordinates.html</link>
diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml
index c1c54dd11b..feac483fc4 100644
--- a/doc/classes/LineEdit.xml
+++ b/doc/classes/LineEdit.xml
@@ -167,6 +167,9 @@
String value of the [LineEdit].
[b]Note:[/b] Changing text using this property won't emit the [signal text_changed] signal.
</member>
+ <member name="virtual_keyboard_enabled" type="bool" setter="set_virtual_keyboard_enabled" getter="is_virtual_keyboard_enabled" default="true">
+ If [code]true[/code], the native virtual keyboard is shown when focused on platforms that support it.
+ </member>
</members>
<signals>
<signal name="text_change_rejected">
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index 105def21ca..5b4b5d02fb 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -112,7 +112,18 @@
<return type="PackedStringArray">
</return>
<description>
- Returns the command line arguments passed to the engine.
+ Returns the command-line arguments passed to the engine.
+ Command-line arguments can be written in any form, including both [code]--key value[/code] and [code]--key=value[/code] forms so they can be properly parsed, as long as custom command-line arguments do not conflict with engine arguments.
+ You can also incorporate environment variables using the [method get_environment] method.
+ You can set [code]editor/main_run_args[/code] in the Project Settings to define command-line arguments to be passed by the editor when running the project.
+ Here's a minimal example on how to parse command-line arguments into a dictionary using the [code]--key=value[/code] form for arguments:
+ [codeblock]
+ var arguments = {}
+ for argument in OS.get_cmdline_args():
+ if argument.find("=") > -1:
+ var key_value = argument.split("=")
+ arguments[key_value[0].lstrip("--")] = key_value[1]
+ [/codeblock]
</description>
</method>
<method name="get_connected_midi_inputs">
@@ -505,7 +516,7 @@
The amount of sleeping between frames when the low-processor usage mode is enabled (in microseconds). Higher values will result in lower CPU usage.
</member>
<member name="tablet_driver" type="String" setter="set_current_tablet_driver" getter="get_current_tablet_driver" default="&quot;&quot;">
- The current tablet drvier in use.
+ The current tablet driver in use.
</member>
</members>
<constants>
diff --git a/doc/classes/Plane.xml b/doc/classes/Plane.xml
index 292acd8b5d..ce0680523c 100644
--- a/doc/classes/Plane.xml
+++ b/doc/classes/Plane.xml
@@ -65,13 +65,6 @@
Returns the shortest distance from the plane to the position [code]point[/code].
</description>
</method>
- <method name="get_any_point">
- <return type="Vector3">
- </return>
- <description>
- Returns a point on the plane.
- </description>
- </method>
<method name="has_point">
<return type="bool">
</return>
@@ -80,7 +73,7 @@
<argument index="1" name="epsilon" type="float" default="1e-05">
</argument>
<description>
- Returns [code]true[/code] if [code]point[/code] is inside the plane (by a very minimum [code]epsilon[/code] threshold).
+ Returns [code]true[/code] if [code]point[/code] is inside the plane. Comparison uses a custom minimum [code]epsilon[/code] threshold.
</description>
</method>
<method name="intersect_3">
@@ -147,36 +140,38 @@
<argument index="0" name="point" type="Vector3">
</argument>
<description>
- Returns the orthogonal projection of point [code]p[/code] into a point in the plane.
+ Returns the orthogonal projection of [code]point[/code] into a point in the plane.
</description>
</method>
</methods>
<members>
<member name="d" type="float" setter="" getter="" default="0.0">
- Distance from the origin to the plane, in the direction of [member normal].
+ The distance from the origin to the plane, in the direction of [member normal]. This value is typically non-negative.
+ In the scalar equation of the plane [code]ax + by + cz = d[/code], this is [code]d[/code], while the [code](a, b, c)[/code] coordinates are represented by the [member normal] property.
</member>
<member name="normal" type="Vector3" setter="" getter="" default="Vector3( 0, 0, 0 )">
- The normal of the plane. "Over" or "Above" the plane is considered the side of the plane towards where the normal is pointing.
+ The normal of the plane, which must be normalized.
+ In the scalar equation of the plane [code]ax + by + cz = d[/code], this is the vector [code](a, b, c)[/code], where [code]d[/code] is the [member d] property.
</member>
<member name="x" type="float" setter="" getter="" default="0.0">
- The [member normal]'s X component.
+ The X component of the plane's [member normal] vector.
</member>
<member name="y" type="float" setter="" getter="" default="0.0">
- The [member normal]'s Y component.
+ The Y component of the plane's [member normal] vector.
</member>
<member name="z" type="float" setter="" getter="" default="0.0">
- The [member normal]'s Z component.
+ The Z component of the plane's [member normal] vector.
</member>
</members>
<constants>
<constant name="PLANE_YZ" value="Plane( 1, 0, 0, 0 )">
- A plane that extends in the Y and Z axes.
+ A plane that extends in the Y and Z axes (normal vector points +X).
</constant>
<constant name="PLANE_XZ" value="Plane( 0, 1, 0, 0 )">
- A plane that extends in the X and Z axes.
+ A plane that extends in the X and Z axes (normal vector points +Y).
</constant>
<constant name="PLANE_XY" value="Plane( 0, 0, 1, 0 )">
- A plane that extends in the X and Y axes.
+ A plane that extends in the X and Y axes (normal vector points +Z).
</constant>
</constants>
</class>
diff --git a/doc/classes/PrimitiveMesh.xml b/doc/classes/PrimitiveMesh.xml
index 77915bc538..9e7f26ed4f 100644
--- a/doc/classes/PrimitiveMesh.xml
+++ b/doc/classes/PrimitiveMesh.xml
@@ -13,7 +13,12 @@
<return type="Array">
</return>
<description>
- Returns mesh arrays used to constitute surface of [Mesh]. Mesh arrays can be used with [ArrayMesh] to create new surfaces.
+ Returns mesh arrays used to constitute surface of [Mesh]. The result can be passed to [method ArrayMesh.add_surface_from_arrays] to create a new surface. For example:
+ [codeblock]
+ var c := CylinderMesh.new()
+ var arr_mesh := ArrayMesh.new()
+ arr_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, c.get_mesh_arrays())
+ [/codeblock]
</description>
</method>
</methods>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index e255ce2e1a..733bb559cb 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -244,7 +244,7 @@
<member name="audio/default_bus_layout" type="String" setter="" getter="" default="&quot;res://default_bus_layout.tres&quot;">
Default [AudioBusLayout] resource file to use in the project, unless overridden by the scene.
</member>
- <member name="audio/driver" type="String" setter="" getter="" default="&quot;PulseAudio&quot;">
+ <member name="audio/driver" type="String" setter="" getter="">
Specifies the audio driver to use. This setting is platform-dependent as each platform supports different audio drivers. If left empty, the default audio driver will be used.
</member>
<member name="audio/enable_audio_input" type="bool" setter="" getter="" default="false">
@@ -453,7 +453,7 @@
<member name="display/window/size/width" type="int" setter="" getter="" default="1024">
Sets the game's main viewport width. On desktop platforms, this is the default window size. Stretch mode settings also use this as a reference when enabled.
</member>
- <member name="display/window/tablet_driver" type="String" setter="" getter="" default="&quot;&quot;">
+ <member name="display/window/tablet_driver" type="String" setter="" getter="">
Specifies the tablet driver to use. If left empty, the default driver will be used.
</member>
<member name="display/window/vsync/use_vsync" type="bool" setter="" getter="" default="true">
@@ -472,7 +472,7 @@
<member name="gui/common/default_scroll_deadzone" type="int" setter="" getter="" default="0">
Default value for [member ScrollContainer.scroll_deadzone], which will be used for all [ScrollContainer]s unless overridden.
</member>
- <member name="gui/common/swap_cancel_ok" type="bool" setter="" getter="" default="false">
+ <member name="gui/common/swap_cancel_ok" type="bool" setter="" getter="">
If [code]true[/code], swaps Cancel and OK buttons in dialogs on Windows and UWP to follow interface conventions.
</member>
<member name="gui/common/text_edit_undo_stack_max_size" type="int" setter="" getter="" default="1024">
diff --git a/doc/classes/Quat.xml b/doc/classes/Quat.xml
index 327fa882e5..730edb00d9 100644
--- a/doc/classes/Quat.xml
+++ b/doc/classes/Quat.xml
@@ -4,9 +4,9 @@
Quaternion.
</brief_description>
<description>
- A unit quaternion used for representing 3D rotations.
- It is similar to [Basis], which implements matrix representation of rotations, and can be parametrized using both an axis-angle pair or Euler angles. But due to its compactness and the way it is stored in memory, certain operations (obtaining axis-angle and performing SLERP, in particular) are more efficient and robust against floating-point errors.
- Quaternions need to be (re)normalized.
+ A unit quaternion used for representing 3D rotations. Quaternions need to be normalized to be used for rotation.
+ It is similar to Basis, which implements matrix representation of rotations, and can be parametrized using both an axis-angle pair or Euler angles. Basis stores rotation, scale, and shearing, while Quat only stores rotation.
+ Due to its compactness and the way it is stored in memory, certain operations (obtaining axis-angle and performing SLERP, in particular) are more efficient and robust against floating-point errors.
</description>
<tutorials>
<link>https://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html#interpolating-with-quaternions</link>
@@ -18,7 +18,7 @@
<argument index="0" name="from" type="Basis">
</argument>
<description>
- Returns the rotation matrix corresponding to the given quaternion.
+ Constructs a quaternion from the given [Basis].
</description>
</method>
<method name="Quat">
@@ -27,7 +27,7 @@
<argument index="0" name="euler" type="Vector3">
</argument>
<description>
- Returns a quaternion that will perform a rotation specified by Euler angles (in the YXZ convention: first Z, then X, and Y last), given in the vector format as (X angle, Y angle, Z angle).
+ Constructs a quaternion that will perform a rotation specified by Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last), given in the vector format as (X angle, Y angle, Z angle).
</description>
</method>
<method name="Quat">
@@ -38,7 +38,7 @@
<argument index="1" name="angle" type="float">
</argument>
<description>
- Returns a quaternion that will rotate around the given axis by the specified angle. The axis must be a normalized vector.
+ Constructs a quaternion that will rotate around the given axis by the specified angle. The axis must be a normalized vector.
</description>
</method>
<method name="Quat">
@@ -53,7 +53,7 @@
<argument index="3" name="w" type="float">
</argument>
<description>
- Returns a quaternion defined by these values.
+ Constructs a quaternion defined by the given values.
</description>
</method>
<method name="cubic_slerp">
@@ -68,7 +68,7 @@
<argument index="3" name="t" type="float">
</argument>
<description>
- Performs a cubic spherical-linear interpolation with another quaternion.
+ Performs a cubic spherical interpolation between quaternions [code]preA[/code], this vector, [code]b[/code], and [code]postB[/code], by the given amount [code]t[/code].
</description>
</method>
<method name="dot">
@@ -84,7 +84,7 @@
<return type="Vector3">
</return>
<description>
- Returns Euler angles (in the YXZ convention: first Z, then X, and Y last) corresponding to the rotation represented by the unit quaternion. Returned vector contains the rotation angles in the format (X angle, Y angle, Z angle).
+ Returns Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last) corresponding to the rotation represented by the unit quaternion. Returned vector contains the rotation angles in the format (X angle, Y angle, Z angle).
</description>
</method>
<method name="inverse">
@@ -148,7 +148,7 @@
<argument index="0" name="euler" type="Vector3">
</argument>
<description>
- Sets the quaternion to a rotation specified by Euler angles (in the YXZ convention: first Z, then X, and Y last), given in the vector format as (X angle, Y angle, Z angle).
+ Sets the quaternion to a rotation specified by Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last), given in the vector format as (X angle, Y angle, Z angle).
</description>
</method>
<method name="slerp">
@@ -159,7 +159,8 @@
<argument index="1" name="t" type="float">
</argument>
<description>
- Performs a spherical-linear interpolation with another quaternion.
+ Returns the result of the spherical linear interpolation between this quaternion and [code]to[/code] by amount [code]weight[/code].
+ [b]Note:[/b] Both quaternions must be normalized.
</description>
</method>
<method name="slerpni">
@@ -170,7 +171,7 @@
<argument index="1" name="t" type="float">
</argument>
<description>
- Performs a spherical-linear interpolation with another quaterion without checking if the rotation path is not bigger than 90°.
+ Returns the result of the spherical linear interpolation between this quaternion and [code]to[/code] by amount [code]weight[/code], but without checking if the rotation path is not bigger than 90 degrees.
</description>
</method>
<method name="xform">
@@ -179,27 +180,31 @@
<argument index="0" name="v" type="Vector3">
</argument>
<description>
- Transforms the vector [code]v[/code] by this quaternion.
+ Returns a vector transformed (multiplied) by this quaternion.
</description>
</method>
</methods>
<members>
<member name="w" type="float" setter="" getter="" default="1.0">
- W component of the quaternion.
+ W component of the quaternion (real part).
+ Quaternion components should usually not be manipulated directly.
</member>
<member name="x" type="float" setter="" getter="" default="0.0">
- X component of the quaternion.
+ X component of the quaternion (imaginary [code]i[/code] axis part).
+ Quaternion components should usually not be manipulated directly.
</member>
<member name="y" type="float" setter="" getter="" default="0.0">
- Y component of the quaternion.
+ Y component of the quaternion (imaginary [code]j[/code] axis part).
+ Quaternion components should usually not be manipulated directly.
</member>
<member name="z" type="float" setter="" getter="" default="0.0">
- Z component of the quaternion.
+ Z component of the quaternion (imaginary [code]k[/code] axis part).
+ Quaternion components should usually not be manipulated directly.
</member>
</members>
<constants>
<constant name="IDENTITY" value="Quat( 0, 0, 0, 1 )">
- The identity rotation. Equivalent to an identity matrix. If a vector is transformed by an identity quaternion, it will not change.
+ The identity quaternion, representing no rotation. Equivalent to an identity [Basis] matrix. If a vector is transformed by an identity quaternion, it will not change.
</constant>
</constants>
</class>
diff --git a/doc/classes/Rect2.xml b/doc/classes/Rect2.xml
index dbf461cdf1..8599aad95e 100644
--- a/doc/classes/Rect2.xml
+++ b/doc/classes/Rect2.xml
@@ -171,13 +171,14 @@
</methods>
<members>
<member name="end" type="Vector2" setter="" getter="" default="Vector2( 0, 0 )">
- Ending corner.
+ Ending corner. This is calculated as [code]position + size[/code]. Setting this value will change the size.
</member>
<member name="position" type="Vector2" setter="" getter="" default="Vector2( 0, 0 )">
- Position (starting corner).
+ Beginning corner. Typically has values lower than [member end].
</member>
<member name="size" type="Vector2" setter="" getter="" default="Vector2( 0, 0 )">
- Size from position to end.
+ Size from [member position] to [member end]. Typically all components are positive.
+ If the size is negative, you can use [method abs] to fix it.
</member>
</members>
<constants>
diff --git a/doc/classes/Rect2i.xml b/doc/classes/Rect2i.xml
index f3a7ba0476..7362580c02 100644
--- a/doc/classes/Rect2i.xml
+++ b/doc/classes/Rect2i.xml
@@ -160,13 +160,14 @@
</methods>
<members>
<member name="end" type="Vector2i" setter="" getter="" default="Vector2i( 0, 0 )">
- Ending corner.
+ Ending corner. This is calculated as [code]position + size[/code]. Setting this value will change the size.
</member>
<member name="position" type="Vector2i" setter="" getter="" default="Vector2i( 0, 0 )">
- Position (starting corner).
+ Beginning corner. Typically has values lower than [member end].
</member>
<member name="size" type="Vector2i" setter="" getter="" default="Vector2i( 0, 0 )">
- Size from position to end.
+ Size from [member position] to [member end]. Typically all components are positive.
+ If the size is negative, you can use [method abs] to fix it.
</member>
</members>
<constants>
diff --git a/doc/classes/RigidBody3D.xml b/doc/classes/RigidBody3D.xml
index efd55f5566..370e6bd19c 100644
--- a/doc/classes/RigidBody3D.xml
+++ b/doc/classes/RigidBody3D.xml
@@ -100,6 +100,13 @@
[b]Note:[/b] The result of this test is not immediate after moving objects. For performance, list of collisions is updated once per frame and before the physics step. Consider using signals instead.
</description>
</method>
+ <method name="get_inverse_inertia_tensor">
+ <return type="Basis">
+ </return>
+ <description>
+ Returns the inverse inertia tensor basis. This is used to calculate the angular acceleration resulting from a torque applied to the [RigidBody3D].
+ </description>
+ </method>
<method name="set_axis_lock">
<return type="void">
</return>
diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml
index 183fd5396f..7ec8fe12fb 100644
--- a/doc/classes/Skeleton3D.xml
+++ b/doc/classes/Skeleton3D.xml
@@ -239,7 +239,7 @@
</argument>
<description>
Sets the global pose transform, [code]pose[/code], for the bone at [code]bone_idx[/code].
- [code]amount[/code] is the interpolation strengh that will be used when applying the pose, and [code]persistent[/code] determines if the applied pose will remain.
+ [code]amount[/code] is the interpolation strength that will be used when applying the pose, and [code]persistent[/code] determines if the applied pose will remain.
[b]Note[/b]: The pose transform needs to be in bone space. Use [method world_transform_to_bone_transform] to convert a world transform, like one you can get from a [Node3D], to bone space.
</description>
</method>
@@ -263,7 +263,7 @@
<argument index="1" name="pose" type="Transform">
</argument>
<description>
- Returns the pose transform for bone [code]bone_idx[/code].
+ Sets the pose transform for bone [code]bone_idx[/code].
[b]Note[/b]: The pose transform needs to be in bone space. Use [method world_transform_to_bone_transform] to convert a world transform, like one you can get from a [Node3D], to bone space.
</description>
</method>
diff --git a/doc/classes/SpringArm3D.xml b/doc/classes/SpringArm3D.xml
index 8305494c2b..15caff9eeb 100644
--- a/doc/classes/SpringArm3D.xml
+++ b/doc/classes/SpringArm3D.xml
@@ -32,7 +32,7 @@
<return type="float">
</return>
<description>
- Returns the proportion between the current arm length (after checking for collisions) and the [member spring_length]. Ranges from 0 to 1.
+ Returns the spring arm's current length.
</description>
</method>
<method name="remove_excluded_object">
diff --git a/doc/classes/String.xml b/doc/classes/String.xml
index 78168562f1..71db03e84f 100644
--- a/doc/classes/String.xml
+++ b/doc/classes/String.xml
@@ -325,7 +325,8 @@
<return type="String">
</return>
<description>
- Returns a copy of the string with escaped characters replaced by their meanings according to the C language standard.
+ Returns a copy of the string with escaped characters replaced by their meanings. Supported escape sequences are [code]\'[/code], [code]\"[/code], [code]\?[/code], [code]\\[/code], [code]\a[/code], [code]\b[/code], [code]\f[/code], [code]\n[/code], [code]\r[/code], [code]\t[/code], [code]\v[/code].
+ [b]Note:[/b] Unlike the GDScript parser, this method doesn't support the [code]\uXXXX[/code] escape sequence.
</description>
</method>
<method name="capitalize">
@@ -413,7 +414,6 @@
</argument>
<description>
Returns the index of the [b]first[/b] case-sensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the end of the string.
-
[b]Note:[/b] If you just want to know whether a string contains a substring, use the [code]in[/code] operator as follows:
[codeblock]
# Will evaluate to `false`.
diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml
index 9a78e45d46..4991a1e58b 100644
--- a/doc/classes/TileSet.xml
+++ b/doc/classes/TileSet.xml
@@ -478,7 +478,17 @@
<argument index="0" name="id" type="int">
</argument>
<description>
- Returns an array of the tile's shapes.
+ Returns an array of dictionaries describing the tile's shapes.
+ [b]Dictionary structure in the array returned by this method:[/b]
+ [codeblock]
+ {
+ "autotile_coord": Vector2,
+ "one_way": bool,
+ "one_way_margin": int,
+ "shape": CollisionShape2D,
+ "shape_transform": Transform2D,
+ }
+ [/codeblock]
</description>
</method>
<method name="tile_get_texture" qualifiers="const">
diff --git a/doc/classes/Transform.xml b/doc/classes/Transform.xml
index 4175f01eb4..26c190bfa9 100644
--- a/doc/classes/Transform.xml
+++ b/doc/classes/Transform.xml
@@ -4,10 +4,12 @@
3D transformation (3×4 matrix).
</brief_description>
<description>
- Represents one or many transformations in 3D space such as translation, rotation, or scaling. It consists of a [member basis] and an [member origin]. It is similar to a 3×4 matrix.
+ 3×4 matrix (3 rows, 4 columns) used for 3D linear transformations. It can represent transformations such as translation, rotation, or scaling. It consists of a [member basis] (first 3 columns) and a [Vector3] for the [member origin] (last column).
+ For more information, read the "Matrices and transforms" documentation article.
</description>
<tutorials>
<link>https://docs.godotengine.org/en/latest/tutorials/math/index.html</link>
+ <link>https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html</link>
<link>https://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html</link>
</tutorials>
<methods>
@@ -23,7 +25,7 @@
<argument index="3" name="origin" type="Vector3">
</argument>
<description>
- Constructs the Transform from four [Vector3]. Each axis corresponds to local basis vectors (some of which may be scaled).
+ Constructs a Transform from four [Vector3] values (matrix columns). Each axis corresponds to local basis vectors (some of which may be scaled).
</description>
</method>
<method name="Transform">
@@ -34,7 +36,7 @@
<argument index="1" name="origin" type="Vector3">
</argument>
<description>
- Constructs the Transform from a [Basis] and [Vector3].
+ Constructs a Transform from a [Basis] and [Vector3].
</description>
</method>
<method name="Transform">
@@ -43,7 +45,7 @@
<argument index="0" name="from" type="Transform2D">
</argument>
<description>
- Constructs the Transform from a [Transform2D].
+ Constructs a Transform from a [Transform2D].
</description>
</method>
<method name="Transform">
@@ -52,7 +54,7 @@
<argument index="0" name="from" type="Quat">
</argument>
<description>
- Constructs the Transform from a [Quat]. The origin will be Vector3(0, 0, 0).
+ Constructs a Transform from a [Quat]. The origin will be [code]Vector3(0, 0, 0)[/code].
</description>
</method>
<method name="Transform">
@@ -79,7 +81,7 @@
<argument index="1" name="weight" type="float">
</argument>
<description>
- Interpolates the transform to other Transform by weight amount (0-1).
+ Interpolates the transform to other Transform by weight amount (on the range of 0.0 to 1.0).
</description>
</method>
<method name="inverse">
@@ -172,7 +174,7 @@
The basis is a matrix containing 3 [Vector3] as its columns: X axis, Y axis, and Z axis. These vectors can be interpreted as the basis vectors of local coordinate system traveling with the object.
</member>
<member name="origin" type="Vector3" setter="" getter="" default="Vector3( 0, 0, 0 )">
- The translation offset of the transform.
+ The translation offset of the transform (column 3, the fourth column). Equivalent to array index [code]3[/code].
</member>
</members>
<constants>
diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml
index af93d4c654..f630df7afe 100644
--- a/doc/classes/Transform2D.xml
+++ b/doc/classes/Transform2D.xml
@@ -1,12 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="Transform2D" version="4.0">
<brief_description>
- 2D transformation (3×2 matrix).
+ 2D transformation (2×3 matrix).
</brief_description>
<description>
- Represents one or many transformations in 2D space such as translation, rotation, or scaling. It consists of two [member x] and [member y] [Vector2]s and an [member origin]. It is similar to a 3×2 matrix.
+ 2×3 matrix (2 rows, 3 columns) used for 2D linear transformations. It can represent transformations such as translation, rotation, or scaling. It consists of a three [Vector2] values: [member x], [member y], and the [member origin].
+ For more information, read the "Matrices and transforms" documentation article.
</description>
<tutorials>
+ <link>https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html</link>
</tutorials>
<methods>
<method name="Transform2D">
@@ -28,7 +30,7 @@
<argument index="2" name="origin" type="Vector2">
</argument>
<description>
- Constructs the transform from 3 [Vector2]s representing x, y, and origin.
+ Constructs the transform from 3 [Vector2] values representing [member x], [member y], and the [member origin] (the three column vectors).
</description>
</method>
<method name="Transform2D">
@@ -46,7 +48,7 @@
<return type="Transform2D">
</return>
<description>
- Returns the inverse of the matrix.
+ Returns the inverse of the transform, under the assumption that the transformation is composed of rotation, scaling and translation.
</description>
</method>
<method name="basis_xform">
@@ -55,7 +57,8 @@
<argument index="0" name="v" type="Vector2">
</argument>
<description>
- Transforms the given vector by this transform's basis (no translation).
+ Returns a vector transformed (multiplied) by the basis matrix.
+ This method does not account for translation (the origin vector).
</description>
</method>
<method name="basis_xform_inv">
@@ -64,7 +67,8 @@
<argument index="0" name="v" type="Vector2">
</argument>
<description>
- Inverse-transforms the given vector by this transform's basis (no translation).
+ Returns a vector transformed (multiplied) by the inverse basis matrix.
+ This method does not account for translation (the origin vector).
</description>
</method>
<method name="get_origin">
@@ -96,14 +100,14 @@
<argument index="1" name="weight" type="float">
</argument>
<description>
- Returns a transform interpolated between this transform and another by a given weight (0-1).
+ Returns a transform interpolated between this transform and another by a given weight (on the range of 0.0 to 1.0).
</description>
</method>
<method name="inverse">
<return type="Transform2D">
</return>
<description>
- Returns the inverse of the transform, under the assumption that the transformation is composed of rotation and translation (no scaling, use affine_inverse for transforms with scaling).
+ Returns the inverse of the transform, under the assumption that the transformation is composed of rotation and translation (no scaling, use [method affine_inverse] for transforms with scaling).
</description>
</method>
<method name="is_equal_approx">
@@ -119,7 +123,7 @@
<return type="Transform2D">
</return>
<description>
- Returns the transform with the basis orthogonal (90 degrees), and normalized axis vectors.
+ Returns the transform with the basis orthogonal (90 degrees), and normalized axis vectors (scale of 1 or -1).
</description>
</method>
<method name="rotated">
@@ -171,24 +175,24 @@
</methods>
<members>
<member name="origin" type="Vector2" setter="" getter="" default="Vector2( 0, 0 )">
- The transform's translation offset.
+ The origin vector (column 2, the third column). Equivalent to array index [code]2[/code]. The origin vector represents translation.
</member>
<member name="x" type="Vector2" setter="" getter="" default="Vector2( 1, 0 )">
- The X axis of 2×2 basis matrix containing 2 [Vector2]s as its columns: X axis and Y axis. These vectors can be interpreted as the basis vectors of local coordinate system traveling with the object.
+ The basis matrix's X vector (column 0). Equivalent to array index [code]0[/code].
</member>
<member name="y" type="Vector2" setter="" getter="" default="Vector2( 0, 1 )">
- The Y axis of 2×2 basis matrix containing 2 [Vector2]s as its columns: X axis and Y axis. These vectors can be interpreted as the basis vectors of local coordinate system traveling with the object.
+ The basis matrix's Y vector (column 1). Equivalent to array index [code]1[/code].
</member>
</members>
<constants>
<constant name="IDENTITY" value="Transform2D( 1, 0, 0, 1, 0, 0 )">
- [Transform2D] with no translation, rotation or scaling applied. When applied to other data structures, [constant IDENTITY] performs no transformation.
+ The identity [Transform2D] with no translation, rotation or scaling applied. When applied to other data structures, [constant IDENTITY] performs no transformation.
</constant>
<constant name="FLIP_X" value="Transform2D( -1, 0, 0, 1, 0, 0 )">
- [Transform2D] with mirroring applied parallel to the X axis.
+ The [Transform2D] that will flip something along the X axis.
</constant>
<constant name="FLIP_Y" value="Transform2D( 1, 0, 0, -1, 0, 0 )">
- [Transform2D] with mirroring applied parallel to the Y axis.
+ The [Transform2D] that will flip something along the Y axis.
</constant>
</constants>
</class>
diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml
index 32895310d1..c0d8628d88 100644
--- a/doc/classes/Vector2.xml
+++ b/doc/classes/Vector2.xml
@@ -43,7 +43,7 @@
<return type="float">
</return>
<description>
- Returns the vector's angle in radians with respect to the X axis, or [code](1, 0)[/code] vector.
+ Returns this vector's angle with respect to the X axis, or [code](1, 0)[/code] vector, in radians.
Equivalent to the result of [method @GDScript.atan2] when called with the vector's [member y] and [member x] as parameters: [code]atan2(y, x)[/code].
</description>
</method>
@@ -53,7 +53,7 @@
<argument index="0" name="to" type="Vector2">
</argument>
<description>
- Returns the angle in radians between the two vectors.
+ Returns the angle to the given vector, in radians.
</description>
</method>
<method name="angle_to_point">
@@ -62,14 +62,14 @@
<argument index="0" name="to" type="Vector2">
</argument>
<description>
- Returns the angle in radians between the line connecting the two points and the X coordinate.
+ Returns the angle between the line connecting the two points and the X axis, in radians.
</description>
</method>
<method name="aspect">
<return type="float">
</return>
<description>
- Returns the ratio of [member x] to [member y].
+ Returns the aspect ratio of this vector, the ratio of [member x] to [member y].
</description>
</method>
<method name="bounce">
@@ -85,7 +85,7 @@
<return type="Vector2">
</return>
<description>
- Returns the vector with all components rounded up.
+ Returns the vector with all components rounded up (towards positive infinity).
</description>
</method>
<method name="clamped">
@@ -94,7 +94,7 @@
<argument index="0" name="length" type="float">
</argument>
<description>
- Returns the vector with a maximum length.
+ Returns the vector with a maximum length by limiting its length to [code]length[/code].
</description>
</method>
<method name="cross">
@@ -103,7 +103,7 @@
<argument index="0" name="with" type="Vector2">
</argument>
<description>
- Returns the 2-dimensional analog of the cross product with the given vector.
+ Returns the cross product of this vector and [code]with[/code].
</description>
</method>
<method name="cubic_interpolate">
@@ -118,7 +118,7 @@
<argument index="3" name="t" type="float">
</argument>
<description>
- Cubically interpolates between this vector and [code]b[/code] using [code]pre_a[/code] and [code]post_b[/code] as handles, and returns the result at position [code]t[/code]. [code]t[/code] is in the range of [code]0.0 - 1.0[/code], representing the amount of interpolation.
+ Cubically interpolates between this vector and [code]b[/code] using [code]pre_a[/code] and [code]post_b[/code] as handles, and returns the result at position [code]t[/code]. [code]t[/code] is on the range of 0.0 to 1.0, representing the amount of interpolation.
</description>
</method>
<method name="direction_to">
@@ -136,7 +136,8 @@
<argument index="0" name="to" type="Vector2">
</argument>
<description>
- Returns the squared distance to vector [code]b[/code]. Prefer this function over [method distance_to] if you need to sort vectors or need the squared distance for some formula.
+ Returns the squared distance between this vector and [code]b[/code].
+ This method runs faster than [method distance_to], so prefer it if you need to compare vectors or need the squared distance for some formula.
</description>
</method>
<method name="distance_to">
@@ -145,7 +146,7 @@
<argument index="0" name="to" type="Vector2">
</argument>
<description>
- Returns the distance to vector [code]b[/code].
+ Returns the distance between this vector and [code]to[/code].
</description>
</method>
<method name="dot">
@@ -154,14 +155,17 @@
<argument index="0" name="with" type="Vector2">
</argument>
<description>
- Returns the dot product with vector [code]b[/code].
+ Returns the dot product of this vector and [code]with[/code]. This can be used to compare the angle between two vectors. For example, this can be used to determine whether an enemy is facing the player.
+ The dot product will be [code]0[/code] for a straight angle (90 degrees), greater than 0 for angles narrower than 90 degrees and lower than 0 for angles wider than 90 degrees.
+ When using unit (normalized) vectors, the result will always be between [code]-1.0[/code] (180 degree angle) when the vectors are facing opposite directions, and [code]1.0[/code] (0 degree angle) when the vectors are aligned.
+ [b]Note:[/b] [code]a.dot(b)[/code] is equivalent to [code]b.dot(a)[/code].
</description>
</method>
<method name="floor">
<return type="Vector2">
</return>
<description>
- Returns the vector with all components rounded down.
+ Returns the vector with all components rounded down (towards negative infinity).
</description>
</method>
<method name="is_equal_approx">
@@ -177,21 +181,22 @@
<return type="bool">
</return>
<description>
- Returns [code]true[/code] if the vector is normalized.
+ Returns [code]true[/code] if the vector is normalized, and false otherwise.
</description>
</method>
<method name="length">
<return type="float">
</return>
<description>
- Returns the vector's length.
+ Returns the length (magnitude) of this vector.
</description>
</method>
<method name="length_squared">
<return type="float">
</return>
<description>
- Returns the vector's length squared. Prefer this method over [method length] if you need to sort vectors or need the squared length for some formula.
+ Returns the squared length (squared magnitude) of this vector.
+ This method runs faster than [method length], so prefer it if you need to compare vectors or need the squared distance for some formula.
</description>
</method>
<method name="lerp">
@@ -202,7 +207,7 @@
<argument index="1" name="t" type="float">
</argument>
<description>
- Returns the result of the linear interpolation between this vector and [code]b[/code] by amount [code]t[/code]. [code]t[/code] is in the range of [code]0.0 - 1.0[/code], representing the amount of interpolation.
+ Returns the result of the linear interpolation between this vector and [code]b[/code] by amount [code]t[/code]. [code]t[/code] is on the range of 0.0 to 1.0, representing the amount of interpolation.
</description>
</method>
<method name="move_toward">
@@ -229,7 +234,7 @@
<argument index="0" name="mod" type="float">
</argument>
<description>
- Returns a vector composed of the [code]fposmod[/code] of this vector's components and [code]mod[/code].
+ Returns a vector composed of the [method @GDScript.fposmod] of this vector's components and [code]mod[/code].
</description>
</method>
<method name="posmodv">
@@ -238,7 +243,7 @@
<argument index="0" name="modv" type="Vector2">
</argument>
<description>
- Returns a vector composed of the [code]fposmod[/code] of this vector's components and [code]modv[/code]'s components.
+ Returns a vector composed of the [method @GDScript.fposmod] of this vector's components and [code]modv[/code]'s components.
</description>
</method>
<method name="project">
@@ -279,7 +284,7 @@
<return type="Vector2">
</return>
<description>
- Returns the vector with each component set to one or negative one, depending on the signs of the components.
+ Returns the vector with each component set to one or negative one, depending on the signs of the components, or zero if the component is zero, by calling [method @GDScript.sign] on each component.
</description>
</method>
<method name="slerp">
@@ -290,7 +295,7 @@
<argument index="1" name="t" type="float">
</argument>
<description>
- Returns the result of spherical linear interpolation between this vector and [code]b[/code], by amount [code]t[/code]. [code]t[/code] is in the range of [code]0.0 - 1.0[/code], representing the amount of interpolation.
+ Returns the result of spherical linear interpolation between this vector and [code]b[/code], by amount [code]t[/code]. [code]t[/code] is on the range of 0.0 to 1.0, representing the amount of interpolation.
[b]Note:[/b] Both vectors must be normalized.
</description>
</method>
@@ -300,7 +305,7 @@
<argument index="0" name="n" type="Vector2">
</argument>
<description>
- Returns the component of the vector along a plane defined by the given normal.
+ Returns this vector slid along a plane defined by the given normal.
</description>
</method>
<method name="snapped">
@@ -309,14 +314,14 @@
<argument index="0" name="by" type="Vector2">
</argument>
<description>
- Returns the vector snapped to a grid with the given size.
+ Returns this vector with each component snapped to the nearest multiple of [code]step[/code]. This can also be used to round to an arbitrary number of decimals.
</description>
</method>
<method name="tangent">
<return type="Vector2">
</return>
<description>
- Returns a perpendicular vector.
+ Returns a perpendicular vector rotated 90 degrees counter-clockwise compared to the original, with the same length.
</description>
</method>
</methods>
@@ -336,25 +341,25 @@
Enumerated value for the Y axis.
</constant>
<constant name="ZERO" value="Vector2( 0, 0 )">
- Zero vector.
+ Zero vector, a vector with all components set to [code]0[/code].
</constant>
<constant name="ONE" value="Vector2( 1, 1 )">
- One vector.
+ One vector, a vector with all components set to [code]1[/code].
</constant>
<constant name="INF" value="Vector2( inf, inf )">
- Infinity vector.
+ Infinity vector, a vector with all components set to [constant @GDScript.INF].
</constant>
<constant name="LEFT" value="Vector2( -1, 0 )">
- Left unit vector.
+ Left unit vector. Represents the direction of left.
</constant>
<constant name="RIGHT" value="Vector2( 1, 0 )">
- Right unit vector.
+ Right unit vector. Represents the direction of right.
</constant>
<constant name="UP" value="Vector2( 0, -1 )">
- Up unit vector.
+ Up unit vector. Y is down in 2D, so this vector points -Y.
</constant>
<constant name="DOWN" value="Vector2( 0, 1 )">
- Down unit vector.
+ Down unit vector. Y is down in 2D, so this vector points +Y.
</constant>
</constants>
</class>
diff --git a/doc/classes/Vector2i.xml b/doc/classes/Vector2i.xml
index 2f7ca985b2..d03db712fc 100644
--- a/doc/classes/Vector2i.xml
+++ b/doc/classes/Vector2i.xml
@@ -70,22 +70,22 @@
Enumerated value for the Y axis.
</constant>
<constant name="ZERO" value="Vector2i( 0, 0 )">
- Zero vector.
+ Zero vector, a vector with all components set to [code]0[/code].
</constant>
<constant name="ONE" value="Vector2i( 1, 1 )">
- One vector.
+ One vector, a vector with all components set to [code]1[/code].
</constant>
<constant name="LEFT" value="Vector2i( -1, 0 )">
- Left unit vector.
+ Left unit vector. Represents the direction of left.
</constant>
<constant name="RIGHT" value="Vector2i( 1, 0 )">
- Right unit vector.
+ Right unit vector. Represents the direction of right.
</constant>
<constant name="UP" value="Vector2i( 0, -1 )">
- Up unit vector.
+ Up unit vector. Y is down in 2D, so this vector points -Y.
</constant>
<constant name="DOWN" value="Vector2i( 0, 1 )">
- Down unit vector.
+ Down unit vector. Y is down in 2D, so this vector points +Y.
</constant>
</constants>
</class>
diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml
index 0c861e5ee2..776dfd929e 100644
--- a/doc/classes/Vector3.xml
+++ b/doc/classes/Vector3.xml
@@ -47,7 +47,7 @@
<argument index="0" name="to" type="Vector3">
</argument>
<description>
- Returns the minimum angle to the given vector.
+ Returns the minimum angle to the given vector, in radians.
</description>
</method>
<method name="bounce">
@@ -63,7 +63,7 @@
<return type="Vector3">
</return>
<description>
- Returns a new vector with all components rounded up.
+ Returns a new vector with all components rounded up (towards positive infinity).
</description>
</method>
<method name="cross">
@@ -72,7 +72,7 @@
<argument index="0" name="b" type="Vector3">
</argument>
<description>
- Returns the cross product with [code]b[/code].
+ Returns the cross product of this vector and [code]b[/code].
</description>
</method>
<method name="cubic_interpolate">
@@ -87,7 +87,7 @@
<argument index="3" name="t" type="float">
</argument>
<description>
- Performs a cubic interpolation between vectors [code]pre_a[/code], [code]a[/code], [code]b[/code], [code]post_b[/code] ([code]a[/code] is current), by the given amount [code]t[/code]. [code]t[/code] is in the range of [code]0.0 - 1.0[/code], representing the amount of interpolation.
+ Performs a cubic interpolation between vectors [code]pre_a[/code], [code]a[/code], [code]b[/code], [code]post_b[/code] ([code]a[/code] is current), by the given amount [code]t[/code]. [code]t[/code] is on the range of 0.0 to 1.0, representing the amount of interpolation.
</description>
</method>
<method name="direction_to">
@@ -105,7 +105,8 @@
<argument index="0" name="b" type="Vector3">
</argument>
<description>
- Returns the squared distance to [code]b[/code]. Prefer this function over [method distance_to] if you need to sort vectors or need the squared distance for some formula.
+ Returns the squared distance between this vector and [code]b[/code].
+ This method runs faster than [method distance_to], so prefer it if you need to compare vectors or need the squared distance for some formula.
</description>
</method>
<method name="distance_to">
@@ -114,7 +115,7 @@
<argument index="0" name="b" type="Vector3">
</argument>
<description>
- Returns the distance to [code]b[/code].
+ Returns the distance between this vector and [code]b[/code].
</description>
</method>
<method name="dot">
@@ -123,14 +124,17 @@
<argument index="0" name="b" type="Vector3">
</argument>
<description>
- Returns the dot product with [code]b[/code].
+ Returns the dot product of this vector and [code]b[/code]. This can be used to compare the angle between two vectors. For example, this can be used to determine whether an enemy is facing the player.
+ The dot product will be [code]0[/code] for a straight angle (90 degrees), greater than 0 for angles narrower than 90 degrees and lower than 0 for angles wider than 90 degrees.
+ When using unit (normalized) vectors, the result will always be between [code]-1.0[/code] (180 degree angle) when the vectors are facing opposite directions, and [code]1.0[/code] (0 degree angle) when the vectors are aligned.
+ [b]Note:[/b] [code]a.dot(b)[/code] is equivalent to [code]b.dot(a)[/code].
</description>
</method>
<method name="floor">
<return type="Vector3">
</return>
<description>
- Returns a new vector with all components rounded down.
+ Returns a new vector with all components rounded down (towards negative infinity).
</description>
</method>
<method name="inverse">
@@ -153,21 +157,22 @@
<return type="bool">
</return>
<description>
- Returns [code]true[/code] if the vector is normalized.
+ Returns [code]true[/code] if the vector is normalized, and false otherwise.
</description>
</method>
<method name="length">
<return type="float">
</return>
<description>
- Returns the vector's length.
+ Returns the length (magnitude) of this vector.
</description>
</method>
<method name="length_squared">
<return type="float">
</return>
<description>
- Returns the vector's length squared. Prefer this function over [method length] if you need to sort vectors or need the squared length for some formula.
+ Returns the squared length (squared magnitude) of this vector.
+ This method runs faster than [method length], so prefer it if you need to compare vectors or need the squared distance for some formula.
</description>
</method>
<method name="lerp">
@@ -178,21 +183,21 @@
<argument index="1" name="t" type="float">
</argument>
<description>
- Returns the result of the linear interpolation between this vector and [code]b[/code] by amount [code]t[/code]. [code]t[/code] is in the range of [code]0.0 - 1.0[/code], representing the amount of interpolation..
+ Returns the result of the linear interpolation between this vector and [code]b[/code] by amount [code]t[/code]. [code]t[/code] is on the range of 0.0 to 1.0, representing the amount of interpolation.
</description>
</method>
<method name="max_axis">
<return type="int">
</return>
<description>
- Returns the axis of the vector's largest value. See [code]AXIS_*[/code] constants.
+ Returns the axis of the vector's largest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_X].
</description>
</method>
<method name="min_axis">
<return type="int">
</return>
<description>
- Returns the axis of the vector's smallest value. See [code]AXIS_*[/code] constants.
+ Returns the axis of the vector's smallest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_Z].
</description>
</method>
<method name="move_toward">
@@ -203,7 +208,7 @@
<argument index="1" name="delta" type="float">
</argument>
<description>
- Moves the vector toward [code]to[/code] by the fixed [code]delta[/code] amount.
+ Moves this vector toward [code]to[/code] by the fixed [code]delta[/code] amount.
</description>
</method>
<method name="normalized">
@@ -228,7 +233,7 @@
<argument index="0" name="mod" type="float">
</argument>
<description>
- Returns a vector composed of the [code]fposmod[/code] of this vector's components and [code]mod[/code].
+ Returns a vector composed of the [method @GDScript.fposmod] of this vector's components and [code]mod[/code].
</description>
</method>
<method name="posmodv">
@@ -237,7 +242,7 @@
<argument index="0" name="modv" type="Vector3">
</argument>
<description>
- Returns a vector composed of the [code]fposmod[/code] of this vector's components and [code]modv[/code]'s components.
+ Returns a vector composed of the [method @GDScript.fposmod] of this vector's components and [code]modv[/code]'s components.
</description>
</method>
<method name="project">
@@ -246,7 +251,7 @@
<argument index="0" name="b" type="Vector3">
</argument>
<description>
- Returns the vector projected onto the vector [code]b[/code].
+ Returns this vector projected onto another vector [code]b[/code].
</description>
</method>
<method name="reflect">
@@ -255,7 +260,7 @@
<argument index="0" name="n" type="Vector3">
</argument>
<description>
- Returns the vector reflected from a plane defined by the given normal.
+ Returns this vector reflected from a plane defined by the given normal.
</description>
</method>
<method name="rotated">
@@ -266,21 +271,21 @@
<argument index="1" name="phi" type="float">
</argument>
<description>
- Rotates the vector around a given axis by [code]phi[/code] radians. The axis must be a normalized vector.
+ Rotates this vector around a given axis by [code]phi[/code] radians. The axis must be a normalized vector.
</description>
</method>
<method name="round">
<return type="Vector3">
</return>
<description>
- Returns the vector with all components rounded to the nearest integer, with halfway cases rounded away from zero.
+ Returns this vector with all components rounded to the nearest integer, with halfway cases rounded away from zero.
</description>
</method>
<method name="sign">
<return type="Vector3">
</return>
<description>
- Returns the vector with each component set to one or negative one, depending on the signs of the components.
+ Returns a vector with each component set to one or negative one, depending on the signs of this vector's components, or zero if the component is zero, by calling [method @GDScript.sign] on each component.
</description>
</method>
<method name="slerp">
@@ -291,7 +296,7 @@
<argument index="1" name="t" type="float">
</argument>
<description>
- Returns the result of spherical linear interpolation between this vector and [code]b[/code], by amount [code]t[/code]. [code]t[/code] is in the range of [code]0.0 - 1.0[/code], representing the amount of interpolation.
+ Returns the result of spherical linear interpolation between this vector and [code]b[/code], by amount [code]t[/code]. [code]t[/code] is on the range of 0.0 to 1.0, representing the amount of interpolation.
[b]Note:[/b] Both vectors must be normalized.
</description>
</method>
@@ -301,7 +306,7 @@
<argument index="0" name="n" type="Vector3">
</argument>
<description>
- Returns the component of the vector along a plane defined by the given normal.
+ Returns this vector slid along a plane defined by the given normal.
</description>
</method>
<method name="snapped">
@@ -310,7 +315,7 @@
<argument index="0" name="by" type="Vector3">
</argument>
<description>
- Returns the vector snapped to a grid with the given size.
+ Returns this vector with each component snapped to the nearest multiple of [code]step[/code]. This can also be used to round to an arbitrary number of decimals.
</description>
</method>
<method name="to_diagonal_matrix">
@@ -318,6 +323,7 @@
</return>
<description>
Returns a diagonal matrix with the vector as main diagonal.
+ This is equivalent to a Basis with no rotation or shearing and this vector's components set as the scale.
</description>
</method>
</methods>
@@ -343,19 +349,19 @@
Enumerated value for the Z axis. Returned by [method max_axis] and [method min_axis].
</constant>
<constant name="ZERO" value="Vector3( 0, 0, 0 )">
- Zero vector.
+ Zero vector, a vector with all components set to [code]0[/code].
</constant>
<constant name="ONE" value="Vector3( 1, 1, 1 )">
- One vector.
+ One vector, a vector with all components set to [code]1[/code].
</constant>
<constant name="INF" value="Vector3( inf, inf, inf )">
- Infinity vector.
+ Infinity vector, a vector with all components set to [constant @GDScript.INF].
</constant>
<constant name="LEFT" value="Vector3( -1, 0, 0 )">
- Left unit vector.
+ Left unit vector. Represents the local direction of left, and the global direction of west.
</constant>
<constant name="RIGHT" value="Vector3( 1, 0, 0 )">
- Right unit vector.
+ Right unit vector. Represents the local direction of right, and the global direction of east.
</constant>
<constant name="UP" value="Vector3( 0, 1, 0 )">
Up unit vector.
@@ -364,10 +370,10 @@
Down unit vector.
</constant>
<constant name="FORWARD" value="Vector3( 0, 0, -1 )">
- Forward unit vector.
+ Forward unit vector. Represents the local direction of forward, and the global direction of north.
</constant>
<constant name="BACK" value="Vector3( 0, 0, 1 )">
- Back unit vector.
+ Back unit vector. Represents the local direction of back, and the global direction of south.
</constant>
</constants>
</class>
diff --git a/doc/classes/Vector3i.xml b/doc/classes/Vector3i.xml
index 91d64ea609..94551e1c2f 100644
--- a/doc/classes/Vector3i.xml
+++ b/doc/classes/Vector3i.xml
@@ -38,14 +38,14 @@
<return type="int">
</return>
<description>
- Returns the axis of the vector's largest value. See [code]AXIS_*[/code] constants.
+ Returns the axis of the vector's largest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_X].
</description>
</method>
<method name="min_axis">
<return type="int">
</return>
<description>
- Returns the axis of the vector's smallest value. See [code]AXIS_*[/code] constants.
+ Returns the axis of the vector's smallest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_Z].
</description>
</method>
<method name="sign">
@@ -78,16 +78,16 @@
Enumerated value for the Z axis.
</constant>
<constant name="ZERO" value="Vector3i( 0, 0, 0 )">
- Zero vector.
+ Zero vector, a vector with all components set to [code]0[/code].
</constant>
<constant name="ONE" value="Vector3i( 1, 1, 1 )">
- One vector.
+ One vector, a vector with all components set to [code]1[/code].
</constant>
<constant name="LEFT" value="Vector3i( -1, 0, 0 )">
- Left unit vector.
+ Left unit vector. Represents the local direction of left, and the global direction of west.
</constant>
<constant name="RIGHT" value="Vector3i( 1, 0, 0 )">
- Right unit vector.
+ Right unit vector. Represents the local direction of right, and the global direction of east.
</constant>
<constant name="UP" value="Vector3i( 0, 1, 0 )">
Up unit vector.
@@ -96,10 +96,10 @@
Down unit vector.
</constant>
<constant name="FORWARD" value="Vector3i( 0, 0, -1 )">
- Forward unit vector.
+ Forward unit vector. Represents the local direction of forward, and the global direction of north.
</constant>
<constant name="BACK" value="Vector3i( 0, 0, 1 )">
- Back unit vector.
+ Back unit vector. Represents the local direction of back, and the global direction of south.
</constant>
</constants>
</class>
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index 8a2c6b73d8..3294ab4cc7 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -315,7 +315,7 @@
Do not perform any antialiasing in the full screen post-process.
</constant>
<constant name="SCREEN_SPACE_AA_FXAA" value="1" enum="ScreenSpaceAA">
- Use fast approximate antialiasing. FXAA is a popular screen-space antialising method, which is fast but will make the image look blurry, especially at lower resolutions. It can still work relatively well at large resolutions such as 1440p and 4K.
+ Use fast approximate antialiasing. FXAA is a popular screen-space antialiasing method, which is fast but will make the image look blurry, especially at lower resolutions. It can still work relatively well at large resolutions such as 1440p and 4K.
</constant>
<constant name="SCREEN_SPACE_AA_MAX" value="2" enum="ScreenSpaceAA">
Represents the size of the [enum ScreenSpaceAA] enum.
diff --git a/doc/classes/VisibilityEnabler2D.xml b/doc/classes/VisibilityEnabler2D.xml
index a5abf16a8d..02fe7a7cd0 100644
--- a/doc/classes/VisibilityEnabler2D.xml
+++ b/doc/classes/VisibilityEnabler2D.xml
@@ -5,7 +5,8 @@
</brief_description>
<description>
The VisibilityEnabler2D will disable [RigidBody2D], [AnimationPlayer], and other nodes when they are not visible. It will only affect nodes with the same root node as the VisibilityEnabler2D, and the root node itself.
- [b]Note:[/b] For performance reasons, VisibilityEnabler2D uses an approximate heuristic with precision determined by [member ProjectSettings.world/2d/cell_size]. If you need exact visibility checking, use another method such as adding an [Area2D] node as a child of a [Camera2D] node.
+ If you just want to receive notifications, use [VisibilityNotifier2D] instead.
+ [b]Note:[/b] For performance reasons, VisibilityEnabler2D uses an approximate heuristic with precision determined by [member ProjectSettings.world/2d/cell_size]. If you need precise visibility checking, use another method such as adding an [Area2D] node as a child of a [Camera2D] node.
[b]Note:[/b] VisibilityEnabler2D will not affect nodes added after scene initialization.
</description>
<tutorials>
diff --git a/doc/classes/VisibilityEnabler3D.xml b/doc/classes/VisibilityEnabler3D.xml
index 342a37e7a4..d78ebb55f2 100644
--- a/doc/classes/VisibilityEnabler3D.xml
+++ b/doc/classes/VisibilityEnabler3D.xml
@@ -5,7 +5,8 @@
</brief_description>
<description>
The VisibilityEnabler3D will disable [RigidBody3D] and [AnimationPlayer] nodes when they are not visible. It will only affect other nodes within the same scene as the VisibilityEnabler3D itself.
- [b]Note:[/b] VisibilityEnabler3D uses an approximate heuristic for performance reasons. It doesn't take walls and other occlusion into account. If you need exact visibility checking, use another method such as adding an [Area3D] node as a child of a [Camera3D] node.
+ If you just want to receive notifications, use [VisibilityNotifier3D] instead.
+ [b]Note:[/b] VisibilityEnabler3D uses an approximate heuristic for performance reasons. It doesn't take walls and other occlusion into account. The heuristic is an implementation detail and may change in future versions. If you need precise visibility checking, use another method such as adding an [Area3D] node as a child of a [Camera3D] node and/or [method Vector3.dot].
[b]Note:[/b] VisibilityEnabler3D will not affect nodes added after scene initialization.
</description>
<tutorials>
diff --git a/doc/classes/VisibilityNotifier2D.xml b/doc/classes/VisibilityNotifier2D.xml
index 391163ef94..314a100989 100644
--- a/doc/classes/VisibilityNotifier2D.xml
+++ b/doc/classes/VisibilityNotifier2D.xml
@@ -5,7 +5,8 @@
</brief_description>
<description>
The VisibilityNotifier2D detects when it is visible on the screen. It also notifies when its bounding rectangle enters or exits the screen or a viewport.
- [b]Note:[/b] For performance reasons, VisibilityNotifier2D uses an approximate heuristic with precision determined by [member ProjectSettings.world/2d/cell_size]. If you need exact visibility checking, use another method such as adding an [Area2D] node as a child of a [Camera2D] node.
+ If you want nodes to be disabled automatically when they exit the screen, use [VisibilityEnabler2D] instead.
+ [b]Note:[/b] For performance reasons, VisibilityNotifier2D uses an approximate heuristic with precision determined by [member ProjectSettings.world/2d/cell_size]. If you need precise visibility checking, use another method such as adding an [Area2D] node as a child of a [Camera2D] node.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/VisibilityNotifier3D.xml b/doc/classes/VisibilityNotifier3D.xml
index eb7bb91f26..e5d3116612 100644
--- a/doc/classes/VisibilityNotifier3D.xml
+++ b/doc/classes/VisibilityNotifier3D.xml
@@ -5,7 +5,8 @@
</brief_description>
<description>
The VisibilityNotifier3D detects when it is visible on the screen. It also notifies when its bounding rectangle enters or exits the screen or a [Camera3D]'s view.
- [b]Note:[/b] VisibilityNotifier3D uses an approximate heuristic for performance reasons. It doesn't take walls and other occlusion into account. If you need exact visibility checking, use another method such as adding an [Area3D] node as a child of a [Camera3D] node.
+ If you want nodes to be disabled automatically when they exit the screen, use [VisibilityEnabler3D] instead.
+ [b]Note:[/b] VisibilityNotifier3D uses an approximate heuristic for performance reasons. It doesn't take walls and other occlusion into account. The heuristic is an implementation detail and may change in future versions. If you need precise visibility checking, use another method such as adding an [Area3D] node as a child of a [Camera3D] node and/or [method Vector3.dot].
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/VisualShaderNodeOutput.xml b/doc/classes/VisualShaderNodeOutput.xml
index 2b4aed9ae4..83da6f29f9 100644
--- a/doc/classes/VisualShaderNodeOutput.xml
+++ b/doc/classes/VisualShaderNodeOutput.xml
@@ -4,7 +4,7 @@
Represents the output shader parameters within the visual shader graph.
</brief_description>
<description>
- This visual shader node is present in all shader graphs in form of "Output" block with mutliple output value ports.
+ This visual shader node is present in all shader graphs in form of "Output" block with multiple output value ports.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/VisualShaderNodeVectorRefract.xml b/doc/classes/VisualShaderNodeVectorRefract.xml
index 0fa90a69cf..178c35f49a 100644
--- a/doc/classes/VisualShaderNodeVectorRefract.xml
+++ b/doc/classes/VisualShaderNodeVectorRefract.xml
@@ -4,7 +4,7 @@
Returns the [Vector3] that points in the direction of refraction. For use within the visual shader graph.
</brief_description>
<description>
- Translated to [code]refract(I, N, eta)[/code] in the shader language, where [code]I[/code] is the incident vector, [code]N[/code] is the normal vector and [code]eta[/code] is the ratio of the indicies of the refraction.
+ Translated to [code]refract(I, N, eta)[/code] in the shader language, where [code]I[/code] is the incident vector, [code]N[/code] is the normal vector and [code]eta[/code] is the ratio of the indices of the refraction.
</description>
<tutorials>
</tutorials>
diff --git a/drivers/vulkan/SCsub b/drivers/vulkan/SCsub
index 91d0e42f80..61d91711da 100644
--- a/drivers/vulkan/SCsub
+++ b/drivers/vulkan/SCsub
@@ -4,6 +4,7 @@ Import("env")
env.add_source_files(env.drivers_sources, "*.cpp")
+# FIXME: Refactor all this to reduce code duplication.
if env["platform"] == "android":
# Use NDK Vulkan headers
thirdparty_dir = env["ANDROID_NDK_ROOT"] + "/sources/third_party/vulkan/src"
@@ -22,6 +23,17 @@ if env["platform"] == "android":
thirdparty_dir = "#thirdparty/vulkan"
vma_sources = [thirdparty_dir + "/android/vk_mem_alloc.cpp"]
env_thirdparty.add_source_files(env.drivers_sources, vma_sources)
+elif env["platform"] == "iphone":
+ # Use bundled Vulkan headers
+ thirdparty_dir = "#thirdparty/vulkan"
+ env.Prepend(CPPPATH=[thirdparty_dir, thirdparty_dir + "/include", thirdparty_dir + "/loader"])
+
+ # Build Vulkan memory allocator
+ env_thirdparty = env.Clone()
+ env_thirdparty.disable_warnings()
+
+ vma_sources = [thirdparty_dir + "/vk_mem_alloc.cpp"]
+ env_thirdparty.add_source_files(env.drivers_sources, vma_sources)
elif env["builtin_vulkan"]:
# Use bundled Vulkan headers
thirdparty_dir = "#thirdparty/vulkan"
@@ -70,16 +82,6 @@ elif env["builtin_vulkan"]:
'FALLBACK_CONFIG_DIRS=\\"%s\\"' % "/etc/xdg",
]
)
- elif env["platform"] == "iphone":
- env_thirdparty.AppendUnique(
- CPPDEFINES=[
- "VK_USE_PLATFORM_IOS_MVK",
- "VULKAN_NON_CMAKE_BUILD",
- 'SYSCONFDIR=\\"%s\\"' % "/etc",
- 'FALLBACK_DATA_DIRS=\\"%s\\"' % "/usr/local/share:/usr/share",
- 'FALLBACK_CONFIG_DIRS=\\"%s\\"' % "/etc/xdg",
- ]
- )
elif env["platform"] == "linuxbsd":
env_thirdparty.AppendUnique(
CPPDEFINES=[
@@ -99,3 +101,13 @@ elif env["builtin_vulkan"]:
loader_sources = [thirdparty_dir + "/loader/" + file for file in loader_sources]
env_thirdparty.add_source_files(env.drivers_sources, loader_sources)
env_thirdparty.add_source_files(env.drivers_sources, vma_sources)
+else: # Always build VMA.
+ thirdparty_dir = "#thirdparty/vulkan"
+ env.Prepend(CPPPATH=[thirdparty_dir])
+
+ # Build Vulkan loader library
+ env_thirdparty = env.Clone()
+ env_thirdparty.disable_warnings()
+ vma_sources = [thirdparty_dir + "/vk_mem_alloc.cpp"]
+
+ env_thirdparty.add_source_files(env.drivers_sources, vma_sources)
diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp
index f37e21aa93..fb890491a4 100644
--- a/drivers/vulkan/rendering_device_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_vulkan.cpp
@@ -65,7 +65,7 @@ void RenderingDeviceVulkan::_free_dependencies(RID p_id) {
dependency_map.erase(E);
}
- //reverse depenencies must be unreferenced
+ //reverse dependencies must be unreferenced
E = reverse_dependency_map.find(p_id);
if (E) {
@@ -2278,8 +2278,8 @@ Error RenderingDeviceVulkan::texture_update(RID p_texture, uint32_t p_layer, con
VkBufferImageCopy buffer_image_copy;
buffer_image_copy.bufferOffset = alloc_offset;
- buffer_image_copy.bufferRowLength = 0; //tigthly packed
- buffer_image_copy.bufferImageHeight = 0; //tigthly packed
+ buffer_image_copy.bufferRowLength = 0; //tightly packed
+ buffer_image_copy.bufferImageHeight = 0; //tightly packed
buffer_image_copy.imageSubresource.aspectMask = texture->read_aspect_mask;
buffer_image_copy.imageSubresource.mipLevel = mm_i;
@@ -2726,8 +2726,8 @@ Error RenderingDeviceVulkan::texture_resolve_multisample(RID p_from_texture, RID
ERR_FAIL_COND_V_MSG(dst_tex->type != TEXTURE_TYPE_2D, ERR_INVALID_PARAMETER, "Destination texture must be 2D (or a slice of a 3D/Cube texture).");
ERR_FAIL_COND_V_MSG(dst_tex->samples != TEXTURE_SAMPLES_1, ERR_INVALID_PARAMETER, "Destination texture must not be multisampled.");
- ERR_FAIL_COND_V_MSG(src_tex->format != dst_tex->format, ERR_INVALID_PARAMETER, "Source and Destionation textures must be the same format.");
- ERR_FAIL_COND_V_MSG(src_tex->width != dst_tex->width && src_tex->height != dst_tex->height && src_tex->depth != dst_tex->depth, ERR_INVALID_PARAMETER, "Source and Destionation textures must have the same dimensions.");
+ ERR_FAIL_COND_V_MSG(src_tex->format != dst_tex->format, ERR_INVALID_PARAMETER, "Source and Destination textures must be the same format.");
+ ERR_FAIL_COND_V_MSG(src_tex->width != dst_tex->width && src_tex->height != dst_tex->height && src_tex->depth != dst_tex->depth, ERR_INVALID_PARAMETER, "Source and Destination textures must have the same dimensions.");
ERR_FAIL_COND_V_MSG(src_tex->read_aspect_mask != dst_tex->read_aspect_mask, ERR_INVALID_PARAMETER,
"Source and destination texture must be of the same type (color or depth).");
@@ -2992,16 +2992,15 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
Vector<VkAttachmentReference> resolve_references;
for (int i = 0; i < p_format.size(); i++) {
- VkAttachmentDescription description;
+ ERR_FAIL_INDEX_V(p_format[i].format, DATA_FORMAT_MAX, VK_NULL_HANDLE);
+ ERR_FAIL_INDEX_V(p_format[i].samples, TEXTURE_SAMPLES_MAX, VK_NULL_HANDLE);
+ ERR_FAIL_COND_V_MSG(!(p_format[i].usage_flags & (TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | TEXTURE_USAGE_RESOLVE_ATTACHMENT_BIT)),
+ VK_NULL_HANDLE, "Texture format for index (" + itos(i) + ") requires an attachment (depth, stencil or resolve) bit set.");
+ VkAttachmentDescription description = {};
description.flags = 0;
- ERR_FAIL_INDEX_V(p_format[i].format, DATA_FORMAT_MAX, VK_NULL_HANDLE);
description.format = vulkan_formats[p_format[i].format];
- ERR_FAIL_INDEX_V(p_format[i].samples, TEXTURE_SAMPLES_MAX, VK_NULL_HANDLE);
description.samples = rasterization_sample_count[p_format[i].samples];
- //anything below does not really matter, as vulkan just ignores it when creating a pipeline
- ERR_FAIL_COND_V_MSG(!(p_format[i].usage_flags & (TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | TEXTURE_USAGE_RESOLVE_ATTACHMENT_BIT)), VK_NULL_HANDLE,
- "Texture format for index (" + itos(i) + ") requires an attachment (depth, stencil or resolve) bit set.");
bool is_depth_stencil = p_format[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
bool is_sampled = p_format[i].usage_flags & TEXTURE_USAGE_SAMPLING_BIT;
@@ -3531,7 +3530,7 @@ RID RenderingDeviceVulkan::index_buffer_create(uint32_t p_index_count, IndexBuff
const uint16_t *index16 = (const uint16_t *)r;
for (uint32_t i = 0; i < p_index_count; i++) {
if (p_use_restart_indices && index16[i] == 0xFFFF) {
- continue; //restart index, ingnore
+ continue; //restart index, ignore
}
index_buffer.max_index = MAX(index16[i], index_buffer.max_index);
}
@@ -3539,7 +3538,7 @@ RID RenderingDeviceVulkan::index_buffer_create(uint32_t p_index_count, IndexBuff
const uint32_t *index32 = (const uint32_t *)r;
for (uint32_t i = 0; i < p_index_count; i++) {
if (p_use_restart_indices && index32[i] == 0xFFFFFFFF) {
- continue; //restart index, ingnore
+ continue; //restart index, ignore
}
index_buffer.max_index = MAX(index32[i], index_buffer.max_index);
}
@@ -5068,13 +5067,13 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma
input_assembly_create_info.topology = topology_list[p_render_primitive];
input_assembly_create_info.primitiveRestartEnable = (p_render_primitive == RENDER_PRIMITIVE_TRIANGLE_STRIPS_WITH_RESTART_INDEX);
- //tesselation
- VkPipelineTessellationStateCreateInfo tesselation_create_info;
- tesselation_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
- tesselation_create_info.pNext = nullptr;
- tesselation_create_info.flags = 0;
+ //tessellation
+ VkPipelineTessellationStateCreateInfo tessellation_create_info;
+ tessellation_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
+ tessellation_create_info.pNext = nullptr;
+ tessellation_create_info.flags = 0;
ERR_FAIL_COND_V(p_rasterization_state.patch_control_points < 1 || p_rasterization_state.patch_control_points > limits.maxTessellationPatchSize, RID());
- tesselation_create_info.patchControlPoints = p_rasterization_state.patch_control_points;
+ tessellation_create_info.patchControlPoints = p_rasterization_state.patch_control_points;
VkPipelineViewportStateCreateInfo viewport_state_create_info;
viewport_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
@@ -5286,7 +5285,7 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma
graphics_pipeline_create_info.pStages = shader->pipeline_stages.ptr();
graphics_pipeline_create_info.pVertexInputState = &pipeline_vertex_input_state_create_info;
graphics_pipeline_create_info.pInputAssemblyState = &input_assembly_create_info;
- graphics_pipeline_create_info.pTessellationState = &tesselation_create_info;
+ graphics_pipeline_create_info.pTessellationState = &tessellation_create_info;
graphics_pipeline_create_info.pViewportState = &viewport_state_create_info;
graphics_pipeline_create_info.pRasterizationState = &rasterization_state_create_info;
graphics_pipeline_create_info.pMultisampleState = &multisample_state_create_info;
@@ -5594,7 +5593,7 @@ Error RenderingDeviceVulkan::_draw_list_render_pass_begin(Framebuffer *framebuff
for (int i = 0; i < p_storage_textures.size(); i++) {
Texture *texture = texture_owner.getornull(p_storage_textures[i]);
- ERR_CONTINUE_MSG(!(texture->usage_flags & TEXTURE_USAGE_STORAGE_BIT), "Supplied storage texture " + itos(i) + " for draw list ist not set to be used for storage.");
+ ERR_CONTINUE_MSG(!(texture->usage_flags & TEXTURE_USAGE_STORAGE_BIT), "Supplied storage texture " + itos(i) + " for draw list is not set to be used for storage.");
if (texture->usage_flags & TEXTURE_USAGE_SAMPLING_BIT) {
//must change layout to general
@@ -5644,7 +5643,8 @@ void RenderingDeviceVulkan::_draw_list_insert_clear_region(DrawList *draw_list,
int color_index = 0;
for (int i = 0; i < framebuffer->texture_ids.size(); i++) {
Texture *texture = texture_owner.getornull(framebuffer->texture_ids[i]);
- VkClearAttachment clear_at;
+ VkClearAttachment clear_at = {};
+
if (p_clear_color && texture->usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
ERR_FAIL_INDEX(color_index, p_clear_colors.size()); //a bug
Color clear_color = p_clear_colors[color_index];
@@ -6161,9 +6161,9 @@ void RenderingDeviceVulkan::draw_list_draw(DrawListID p_list, bool p_use_indices
//make sure format is right
ERR_FAIL_COND_MSG(dl->validation.pipeline_vertex_format != dl->validation.vertex_format,
"The vertex format used to create the pipeline does not match the vertex format bound.");
- //make sure amount of instances is valid
+ //make sure number of instances is valid
ERR_FAIL_COND_MSG(p_instances > dl->validation.vertex_max_instances_allowed,
- "Amount of instances requested (" + itos(p_instances) + " is larger than the maximum amount suported by the bound vertex array (" + itos(dl->validation.vertex_max_instances_allowed) + ").");
+ "Number of instances requested (" + itos(p_instances) + " is larger than the maximum number supported by the bound vertex array (" + itos(dl->validation.vertex_max_instances_allowed) + ").");
}
if (dl->validation.pipeline_push_constant_size > 0) {
@@ -7359,7 +7359,7 @@ uint64_t RenderingDeviceVulkan::get_captured_timestamp_gpu_time(uint32_t p_index
// this sucks because timestampPeriod multiplier is a float, while the timestamp is 64 bits nanosecs.
// so, in cases like nvidia which give you enormous numbers and 1 as multiplier, multiplying is next to impossible
- // need to do 128 bits fixed point multiplication to get the rigth value
+ // need to do 128 bits fixed point multiplication to get the right value
uint64_t shift_bits = 16;
diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h
index 936448dbbd..6f8bbc9c64 100644
--- a/drivers/vulkan/rendering_device_vulkan.h
+++ b/drivers/vulkan/rendering_device_vulkan.h
@@ -112,7 +112,7 @@ class RenderingDeviceVulkan : public RenderingDevice {
/*****************/
// In Vulkan, the concept of textures does not exist,
- // intead there is the image (the memory prety much,
+ // instead there is the image (the memory pretty much,
// the view (how the memory is interpreted) and the
// sampler (how it's sampled from the shader).
//
@@ -327,7 +327,7 @@ class RenderingDeviceVulkan : public RenderingDevice {
// Vertex buffers in Vulkan are similar to how
// they work in OpenGL, except that instead of
- // an attribtue index, there is a buffer binding
+ // an attribute index, there is a buffer binding
// index (for binding the buffers in real-time)
// and a location index (what is used in the shader).
//
@@ -869,8 +869,8 @@ class RenderingDeviceVulkan : public RenderingDevice {
List<ComputePipeline> compute_pipelines_to_dispose_of;
VkCommandPool command_pool;
- VkCommandBuffer setup_command_buffer; //used at the begining of every frame for set-up
- VkCommandBuffer draw_command_buffer; //used at the begining of every frame for set-up
+ VkCommandBuffer setup_command_buffer; //used at the beginning of every frame for set-up
+ VkCommandBuffer draw_command_buffer; //used at the beginning of every frame for set-up
struct Timestamp {
String description;
diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp
index e064a4ad6a..997ed3935f 100644
--- a/drivers/vulkan/vulkan_context.cpp
+++ b/drivers/vulkan/vulkan_context.cpp
@@ -154,7 +154,7 @@ VkBool32 VulkanContext::_check_layers(uint32_t check_count, const char **check_n
}
}
if (!found) {
- ERR_PRINT("Cant find layer: " + String(check_names[i]));
+ ERR_PRINT("Can't find layer: " + String(check_names[i]));
return 0;
}
}
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index f3561dc03e..d569a2ca0a 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -3363,9 +3363,9 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) {
}
if (num_tracks == 1) {
- insert_confirm_text->set_text(vformat(TTR("Create NEW track for %s and insert key?"), p_id.query));
+ insert_confirm_text->set_text(vformat(TTR("Create new track for %s and insert key?"), p_id.query));
} else {
- insert_confirm_text->set_text(vformat(TTR("Create %d NEW tracks and insert keys?"), num_tracks));
+ insert_confirm_text->set_text(vformat(TTR("Create %d new tracks and insert keys?"), num_tracks));
}
insert_confirm_bezier->set_visible(all_bezier);
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index d8648310b6..37db3ba780 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -1456,8 +1456,6 @@ void CodeTextEditor::set_edit_state(const Variant &p_state) {
text_editor->set_line_as_bookmark(bookmarks[i], true);
}
}
-
- text_editor->grab_focus();
}
void CodeTextEditor::set_error(const String &p_error) {
@@ -1778,6 +1776,7 @@ CodeTextEditor::CodeTextEditor() {
cs.push_back("(");
cs.push_back("=");
cs.push_back("$");
+ cs.push_back("@");
text_editor->set_completion(true, cs);
idle->connect("timeout", callable_mp(this, &CodeTextEditor::_text_changed_idle_timeout));
diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp
index 99a2a73a75..1e3dc01112 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -674,7 +674,7 @@ CreateDialog::CreateDialog() {
favorites->connect("item_activated", callable_mp(this, &CreateDialog::_favorite_activated));
favorites->add_theme_constant_override("draw_guides", 1);
#ifndef _MSC_VER
-#warning cant forward drag data to a non control, must be fixed
+#warning cannot forward drag data to a non control, must be fixed
#endif
//favorites->set_drag_forwarding(this);
fav_vb->add_margin_child(TTR("Favorites:"), favorites, true);
@@ -707,6 +707,7 @@ CreateDialog::CreateDialog() {
favorite = memnew(Button);
favorite->set_flat(true);
favorite->set_toggle_mode(true);
+ favorite->set_tooltip(TTR("(Un)favorite selected item."));
favorite->connect("pressed", callable_mp(this, &CreateDialog::_favorite_toggled));
search_hb->add_child(favorite);
vbc->add_margin_child(TTR("Search:"), search_hb);
diff --git a/editor/doc_data.cpp b/editor/doc_data.cpp
index 54acbe9559..75b16b4510 100644
--- a/editor/doc_data.cpp
+++ b/editor/doc_data.cpp
@@ -321,12 +321,13 @@ void DocData::generate(bool p_basic_types) {
continue;
}
if (E->get().usage & PROPERTY_USAGE_EDITOR) {
- default_value = ProjectSettings::get_singleton()->property_get_revert(E->get().name);
- default_value_valid = true;
+ if (!ProjectSettings::get_singleton()->get_ignore_value_in_docs(E->get().name)) {
+ default_value = ProjectSettings::get_singleton()->property_get_revert(E->get().name);
+ default_value_valid = true;
+ }
}
} else {
default_value = get_documentation_default_value(name, E->get().name, default_value_valid);
-
if (inherited) {
bool base_default_value_valid = false;
Variant base_default_value = get_documentation_default_value(ClassDB::get_parent_class(name), E->get().name, base_default_value_valid);
@@ -478,6 +479,7 @@ void DocData::generate(bool p_basic_types) {
ConstantDoc constant;
constant.name = E->get();
constant.value = itos(ClassDB::get_integer_constant(name, E->get()));
+ constant.is_value_valid = true;
constant.enumeration = ClassDB::get_integer_constant_enum(name, E->get());
c.constants.push_back(constant);
}
@@ -620,6 +622,7 @@ void DocData::generate(bool p_basic_types) {
constant.name = E->get();
Variant value = Variant::get_constant_value(Variant::Type(i), E->get());
constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string();
+ constant.is_value_valid = true;
c.constants.push_back(constant);
}
}
@@ -635,7 +638,12 @@ void DocData::generate(bool p_basic_types) {
for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
ConstantDoc cd;
cd.name = GlobalConstants::get_global_constant_name(i);
- cd.value = itos(GlobalConstants::get_global_constant_value(i));
+ if (!GlobalConstants::get_ignore_value_in_docs(i)) {
+ cd.value = itos(GlobalConstants::get_global_constant_value(i));
+ cd.is_value_valid = true;
+ } else {
+ cd.is_value_valid = false;
+ }
cd.enumeration = GlobalConstants::get_global_constant_enum(i);
c.constants.push_back(cd);
}
@@ -715,6 +723,7 @@ void DocData::generate(bool p_basic_types) {
ConstantDoc cd;
cd.name = E->get().first;
cd.value = E->get().second;
+ cd.is_value_valid = true;
c.constants.push_back(cd);
}
@@ -989,6 +998,7 @@ Error DocData::_load(Ref<XMLParser> parser) {
constant2.name = parser->get_attribute_value("name");
ERR_FAIL_COND_V(!parser->has_attribute("value"), ERR_FILE_CORRUPT);
constant2.value = parser->get_attribute_value("value");
+ constant2.is_value_valid = true;
if (parser->has_attribute("enum")) {
constant2.enumeration = parser->get_attribute_value("enum");
}
@@ -1178,10 +1188,18 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri
for (int i = 0; i < c.constants.size(); i++) {
const ConstantDoc &k = c.constants[i];
- if (k.enumeration != String()) {
- _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\" enum=\"" + k.enumeration + "\">");
+ if (k.is_value_valid) {
+ if (k.enumeration != String()) {
+ _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\" enum=\"" + k.enumeration + "\">");
+ } else {
+ _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\">");
+ }
} else {
- _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\">");
+ if (k.enumeration != String()) {
+ _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\" enum=\"" + k.enumeration + "\">");
+ } else {
+ _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\">");
+ }
}
_write_string(f, 3, k.description.strip_edges().xml_escape());
_write_string(f, 2, "</constant>");
diff --git a/editor/doc_data.h b/editor/doc_data.h
index 1880be81ed..a35cfb59c7 100644
--- a/editor/doc_data.h
+++ b/editor/doc_data.h
@@ -62,6 +62,7 @@ public:
struct ConstantDoc {
String name;
String value;
+ bool is_value_valid;
String enumeration;
String description;
bool operator<(const ConstantDoc &p_const) const {
diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp
index 94887fb848..5d101ff2c2 100644
--- a/editor/editor_autoload_settings.cpp
+++ b/editor/editor_autoload_settings.cpp
@@ -688,12 +688,12 @@ bool EditorAutoloadSettings::autoload_add(const String &p_name, const String &p_
const String &path = p_path;
if (!FileAccess::exists(path)) {
- EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + TTR(vformat("%s is an invalid path. File does not exist.", path)));
+ EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + vformat(TTR("%s is an invalid path. File does not exist."), path));
return false;
}
if (!path.begins_with("res://")) {
- EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + TTR(vformat("%s is an invalid path. Not in resource path (res://).", path)));
+ EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + vformat(TTR("%s is an invalid path. Not in resource path (res://)."), path));
return false;
}
diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp
index 85151c6d0a..0e851734a7 100644
--- a/editor/editor_file_dialog.cpp
+++ b/editor/editor_file_dialog.cpp
@@ -440,7 +440,7 @@ void EditorFileDialog::_action_pressed() {
}
if (dir_access->file_exists(f) && !disable_overwrite_warning) {
- confirm_save->set_text(TTR("File Exists, Overwrite?"));
+ confirm_save->set_text(TTR("File exists, overwrite?"));
confirm_save->popup_centered(Size2(200, 80));
} else {
_save_to_recent();
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 9c0efd0881..263ed9040a 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -2052,7 +2052,7 @@ void EditorNode::_run(bool p_current, const String &p_custom) {
String args;
bool skip_breakpoints;
- if (p_current || (editor_data.get_edited_scene_root() && p_custom == editor_data.get_edited_scene_root()->get_filename())) {
+ if (p_current || (editor_data.get_edited_scene_root() && p_custom != String() && p_custom == editor_data.get_edited_scene_root()->get_filename())) {
Node *scene = editor_data.get_edited_scene_root();
if (!scene) {
@@ -2082,13 +2082,7 @@ void EditorNode::_run(bool p_current, const String &p_custom) {
if (unsaved_cache) {
Node *scene = editor_data.get_edited_scene_root();
- if (scene) { //only autosave if there is a scene obviously
-
- if (scene->get_filename() == "") {
- show_accept(TTR("Current scene was never saved, please save it prior to running."), TTR("OK"));
- return;
- }
-
+ if (scene && scene->get_filename() != "") { // Only autosave if there is a scene and if it has a path.
_save_scene_with_preview(scene->get_filename());
}
}
@@ -6321,7 +6315,7 @@ EditorNode::EditorNode() {
right_menu_hb->add_child(video_driver);
#ifndef _MSC_VER
-#warning neeeds to be reimplemented
+#warning needs to be reimplemented
#endif
#if 0
String video_drivers = ProjectSettings::get_singleton()->get_custom_property_info()["rendering/quality/driver/driver_name"].hint_string;
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index c3f0d1b712..2d50d25ff5 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -2980,7 +2980,16 @@ Variant EditorPropertyResource::get_drag_data_fw(const Point2 &p_point, Control
}
bool EditorPropertyResource::_is_drop_valid(const Dictionary &p_drag_data) const {
- String allowed_type = base_type;
+ Vector<String> allowed_types = base_type.split(",");
+ int size = allowed_types.size();
+ for (int i = 0; i < size; i++) {
+ String at = allowed_types[i].strip_edges();
+ if (at == "StandardMaterial3D") {
+ allowed_types.append("Texture2D");
+ } else if (at == "ShaderMaterial") {
+ allowed_types.append("Shader");
+ }
+ }
Dictionary drag_data = p_drag_data;
@@ -2993,9 +3002,9 @@ bool EditorPropertyResource::_is_drop_valid(const Dictionary &p_drag_data) const
}
if (res.is_valid()) {
- for (int i = 0; i < allowed_type.get_slice_count(","); i++) {
- String at = allowed_type.get_slice(",", i).strip_edges();
- if (res.is_valid() && ClassDB::is_parent_class(res->get_class(), at)) {
+ for (int i = 0; i < allowed_types.size(); i++) {
+ String at = allowed_types[i].strip_edges();
+ if (ClassDB::is_parent_class(res->get_class(), at)) {
return true;
}
}
@@ -3009,8 +3018,8 @@ bool EditorPropertyResource::_is_drop_valid(const Dictionary &p_drag_data) const
String ftype = EditorFileSystem::get_singleton()->get_file_type(file);
if (ftype != "") {
- for (int i = 0; i < allowed_type.get_slice_count(","); i++) {
- String at = allowed_type.get_slice(",", i).strip_edges();
+ for (int i = 0; i < allowed_types.size(); i++) {
+ String at = allowed_types[i].strip_edges();
if (ClassDB::is_parent_class(ftype, at)) {
return true;
}
@@ -3039,24 +3048,49 @@ void EditorPropertyResource::drop_data_fw(const Point2 &p_point, const Variant &
res = drag_data["resource"];
}
- if (res.is_valid()) {
- emit_changed(get_edited_property(), res);
- update_property();
- return;
- }
-
- if (drag_data.has("type") && String(drag_data["type"]) == "files") {
+ if (!res.is_valid() && drag_data.has("type") && String(drag_data["type"]) == "files") {
Vector<String> files = drag_data["files"];
if (files.size() == 1) {
String file = files[0];
- RES file_res = ResourceLoader::load(file);
- if (file_res.is_valid()) {
- emit_changed(get_edited_property(), file_res);
- update_property();
- return;
+ res = ResourceLoader::load(file);
+ }
+ }
+
+ if (res.is_valid()) {
+ bool need_convert = true;
+
+ Vector<String> allowed_types = base_type.split(",");
+ for (int i = 0; i < allowed_types.size(); i++) {
+ String at = allowed_types[i].strip_edges();
+ if (ClassDB::is_parent_class(res->get_class(), at)) {
+ need_convert = false;
+ break;
+ }
+ }
+
+ if (need_convert) {
+ for (int i = 0; i < allowed_types.size(); i++) {
+ String at = allowed_types[i].strip_edges();
+ if (at == "StandardMaterial3D" && ClassDB::is_parent_class(res->get_class(), "Texture2D")) {
+ Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
+ mat->set_texture(StandardMaterial3D::TextureParam::TEXTURE_ALBEDO, res);
+ res = mat;
+ break;
+ }
+
+ if (at == "ShaderMaterial" && ClassDB::is_parent_class(res->get_class(), "Shader")) {
+ Ref<ShaderMaterial> mat = memnew(ShaderMaterial);
+ mat->set_shader(res);
+ res = mat;
+ break;
+ }
}
}
+
+ emit_changed(get_edited_property(), res);
+ update_property();
+ return;
}
}
diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp
index 683db0e31d..bb144d2ed6 100644
--- a/editor/import/editor_scene_importer_gltf.cpp
+++ b/editor/import/editor_scene_importer_gltf.cpp
@@ -1229,10 +1229,14 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) {
}
}
+ mesh.blend_weights.resize(mesh.mesh->get_blend_shape_count());
+ for (int32_t weight_i = 0; weight_i < mesh.blend_weights.size(); weight_i++) {
+ mesh.blend_weights.write[weight_i] = 0.0f;
+ }
+
if (d.has("weights")) {
const Array &weights = d["weights"];
- ERR_FAIL_COND_V(mesh.mesh->get_blend_shape_count() != weights.size(), ERR_PARSE_ERROR);
- mesh.blend_weights.resize(weights.size());
+ ERR_FAIL_COND_V(mesh.blend_weights.size() != weights.size(), ERR_PARSE_ERROR);
for (int j = 0; j < weights.size(); j++) {
mesh.blend_weights.write[j] = weights[j];
}
diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp
index b57ea3745d..f954931cee 100644
--- a/editor/import/resource_importer_layered_texture.cpp
+++ b/editor/import/resource_importer_layered_texture.cpp
@@ -180,7 +180,7 @@ void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, cons
f->store_32(0); //dataformat
f->store_32(0); //mipmap limit
- //reserverd
+ //reserved
f->store_32(0);
f->store_32(0);
f->store_32(0);
diff --git a/editor/input_map_editor.cpp b/editor/input_map_editor.cpp
index 70c354e55a..52cf9c1869 100644
--- a/editor/input_map_editor.cpp
+++ b/editor/input_map_editor.cpp
@@ -36,44 +36,44 @@
#include "editor/editor_scale.h"
static const char *_button_descriptions[JOY_SDL_BUTTONS] = {
- "Face Bottom, DualShock Cross, Xbox A, Nintendo B",
- "Face Right, DualShock Circle, Xbox B, Nintendo A",
- "Face Left, DualShock Square, Xbox X, Nintendo Y",
- "Face Top, DualShock Triangle, Xbox Y, Nintendo X",
- "DualShock Select, Xbox Back, Nintendo -",
- "Home, DualShock PS, Guide",
- "Start, Nintendo +",
- "Left Stick, DualShock L3, Xbox L/LS",
- "Right Stick, DualShock R3, Xbox R/RS",
- "Left Shoulder, DualShock L1, Xbox LB",
- "Right Shoulder, DualShock R1, Xbox RB",
- "D-Pad Up",
- "D-Pad Down",
- "D-Pad Left",
- "D-Pad Right"
+ TTRC("Face Bottom, DualShock Cross, Xbox A, Nintendo B"),
+ TTRC("Face Right, DualShock Circle, Xbox B, Nintendo A"),
+ TTRC("Face Left, DualShock Square, Xbox X, Nintendo Y"),
+ TTRC("Face Top, DualShock Triangle, Xbox Y, Nintendo X"),
+ TTRC("DualShock Select, Xbox Back, Nintendo -"),
+ TTRC("Home, DualShock PS, Guide"),
+ TTRC("Start, Nintendo +"),
+ TTRC("Left Stick, DualShock L3, Xbox L/LS"),
+ TTRC("Right Stick, DualShock R3, Xbox R/RS"),
+ TTRC("Left Shoulder, DualShock L1, Xbox LB"),
+ TTRC("Right Shoulder, DualShock R1, Xbox RB"),
+ TTRC("D-Pad Up"),
+ TTRC("D-Pad Down"),
+ TTRC("D-Pad Left"),
+ TTRC("D-Pad Right")
};
static const char *_axis_descriptions[JOY_AXIS_MAX * 2] = {
- "Left Stick Left",
- "Left Stick Right",
- "Left Stick Up",
- "Left Stick Down",
- "Right Stick Left",
- "Right Stick Right",
- "Right Stick Up",
- "Right Stick Down",
- "Joystick 2 Left",
- "Joystick 2 Right, Left Trigger, L2, LT",
- "Joystick 2 Up",
- "Joystick 2 Down, Right Trigger, R2, RT",
- "Joystick 3 Left",
- "Joystick 3 Right",
- "Joystick 3 Up",
- "Joystick 3 Down",
- "Joystick 4 Left",
- "Joystick 4 Right",
- "Joystick 4 Up",
- "Joystick 4 Down",
+ TTRC("Left Stick Left"),
+ TTRC("Left Stick Right"),
+ TTRC("Left Stick Up"),
+ TTRC("Left Stick Down"),
+ TTRC("Right Stick Left"),
+ TTRC("Right Stick Right"),
+ TTRC("Right Stick Up"),
+ TTRC("Right Stick Down"),
+ TTRC("Joystick 2 Left"),
+ TTRC("Joystick 2 Right, Left Trigger, L2, LT"),
+ TTRC("Joystick 2 Up"),
+ TTRC("Joystick 2 Down, Right Trigger, R2, RT"),
+ TTRC("Joystick 3 Left"),
+ TTRC("Joystick 3 Right"),
+ TTRC("Joystick 3 Up"),
+ TTRC("Joystick 3 Down"),
+ TTRC("Joystick 4 Left"),
+ TTRC("Joystick 4 Right"),
+ TTRC("Joystick 4 Up"),
+ TTRC("Joystick 4 Down"),
};
void InputMapEditor::_notification(int p_what) {
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index e2f35e29d8..af1d266832 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -411,7 +411,7 @@ void AnimationPlayerEditor::_animation_remove() {
String current = animation->get_item_text(animation->get_selected());
- delete_dialog->set_text(TTR("Delete Animation '" + current + "'?"));
+ delete_dialog->set_text(vformat(TTR("Delete Animation '%s'?"), current));
delete_dialog->popup_centered();
}
diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp
index dc813896ff..269c54ba2b 100644
--- a/editor/plugins/animation_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_tree_editor_plugin.cpp
@@ -79,7 +79,7 @@ void AnimationTreeEditor::_update_path() {
group.instance();
Button *b = memnew(Button);
- b->set_text("root");
+ b->set_text("Root");
b->set_toggle_mode(true);
b->set_button_group(group);
b->set_pressed(true);
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 44913abe8b..f3508cedbd 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -2045,10 +2045,10 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
if ((b->get_alt() && !b->get_control()) || tool == TOOL_MOVE) {
List<CanvasItem *> selection = _get_edited_canvas_items();
- // Remove not movable nodes
+ drag_selection.clear();
for (int i = 0; i < selection.size(); i++) {
- if (!_is_node_movable(selection[i], true)) {
- selection.erase(selection[i]);
+ if (_is_node_movable(selection[i], true)) {
+ drag_selection.push_back(selection[i]);
}
}
@@ -2073,7 +2073,6 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
}
drag_from = transform.affine_inverse().xform(b->get_position());
- drag_selection = selection;
_save_canvas_item_state(drag_selection);
}
return true;
@@ -2395,16 +2394,15 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
// Drag the node(s) if requested
List<CanvasItem *> selection2 = _get_edited_canvas_items();
- // Remove not movable nodes
+ drag_selection.clear();
for (int i = 0; i < selection2.size(); i++) {
- if (!_is_node_movable(selection2[i], true)) {
- selection2.erase(selection2[i]);
+ if (_is_node_movable(selection2[i], true)) {
+ drag_selection.push_back(selection2[i]);
}
}
if (selection2.size() > 0) {
drag_type = DRAG_MOVE;
- drag_selection = selection2;
drag_from = click;
_save_canvas_item_state(drag_selection);
}
@@ -2591,6 +2589,15 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
_gui_input_hover(p_event);
// Change the cursor
+ _update_cursor();
+
+ // Grab focus
+ if (!viewport->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) {
+ viewport->call_deferred("grab_focus");
+ }
+}
+
+void CanvasItemEditor::_update_cursor() {
CursorShape c = CURSOR_ARROW;
switch (drag_type) {
case DRAG_NONE:
@@ -2644,11 +2651,6 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
}
viewport->set_default_cursor_shape(c);
-
- // Grab focus
- if (!viewport->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) {
- viewport->call_deferred("grab_focus");
- }
}
void CanvasItemEditor::_draw_text_at_position(Point2 p_position, String p_string, Margin p_side) {
@@ -4466,7 +4468,13 @@ void CanvasItemEditor::_button_tool_select(int p_index) {
}
tool = (Tool)p_index;
+
viewport->update();
+ _update_cursor();
+
+ // Request immediate refresh of cursor when using hot-keys to switch between tools
+ DisplayServer::CursorShape ds_cursor_shape = (DisplayServer::CursorShape)viewport->get_default_cursor_shape();
+ DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape);
}
void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) {
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index 12abf05cf9..ea58fb1e36 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -492,6 +492,7 @@ private:
bool _gui_input_hover(const Ref<InputEvent> &p_event);
void _gui_input_viewport(const Ref<InputEvent> &p_event);
+ void _update_cursor();
void _selection_changed();
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 6165f39561..6f209c512e 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -1928,22 +1928,22 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
switch (nav_mode) {
case NAVIGATION_PAN: {
- _nav_pan(m, pan_gesture->get_delta());
+ _nav_pan(pan_gesture, pan_gesture->get_delta());
} break;
case NAVIGATION_ZOOM: {
- _nav_zoom(m, pan_gesture->get_delta());
+ _nav_zoom(pan_gesture, pan_gesture->get_delta());
} break;
case NAVIGATION_ORBIT: {
- _nav_orbit(m, pan_gesture->get_delta());
+ _nav_orbit(pan_gesture, pan_gesture->get_delta());
} break;
case NAVIGATION_LOOK: {
- _nav_look(m, pan_gesture->get_delta());
+ _nav_look(pan_gesture, pan_gesture->get_delta());
} break;
@@ -5407,7 +5407,7 @@ void Node3DEditor::_update_gizmos_menu() {
}
String plugin_name = gizmo_plugins_by_name[i]->get_name();
const int plugin_state = gizmo_plugins_by_name[i]->get_state();
- gizmos_menu->add_multistate_item(TTR(plugin_name), 3, plugin_state, i);
+ gizmos_menu->add_multistate_item(plugin_name, 3, plugin_state, i);
const int idx = gizmos_menu->get_item_index(i);
gizmos_menu->set_item_tooltip(
idx,
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 4f783f4e27..6b844ec0d0 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -696,12 +696,12 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) {
Node *tselected = tab_container->get_child(selected);
- ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected));
+ ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected);
if (current) {
Ref<Script> script = current->get_edited_resource();
if (p_save) {
// Do not try to save internal scripts
- if (!(script->get_path() == "" || script->get_path().find("local://") != -1 || script->get_path().find("::") != -1)) {
+ if (!script.is_valid() || !(script->get_path() == "" || script->get_path().find("local://") != -1 || script->get_path().find("::") != -1)) {
_menu_option(FILE_SAVE);
}
}
@@ -779,8 +779,10 @@ void ScriptEditor::_close_docs_tab() {
void ScriptEditor::_copy_script_path() {
ScriptEditorBase *se = _get_current_editor();
- RES script = se->get_edited_resource();
- DisplayServer::get_singleton()->clipboard_set(script->get_path());
+ if (se) {
+ RES script = se->get_edited_resource();
+ DisplayServer::get_singleton()->clipboard_set(script->get_path());
+ }
}
void ScriptEditor::_close_other_tabs() {
@@ -1038,17 +1040,19 @@ void ScriptEditor::_file_dialog_action(String p_file) {
} break;
case FILE_SAVE_AS: {
ScriptEditorBase *current = _get_current_editor();
+ if (current) {
+ RES resource = current->get_edited_resource();
+ String path = ProjectSettings::get_singleton()->localize_path(p_file);
+ Error err = _save_text_file(resource, path);
- String path = ProjectSettings::get_singleton()->localize_path(p_file);
- Error err = _save_text_file(current->get_edited_resource(), path);
+ if (err != OK) {
+ editor->show_accept(TTR("Error saving file!"), TTR("OK"));
+ return;
+ }
- if (err != OK) {
- editor->show_accept(TTR("Error saving file!"), TTR("OK"));
- return;
+ resource->set_path(path);
+ _update_script_names();
}
-
- ((Resource *)current->get_edited_resource().ptr())->set_path(path);
- _update_script_names();
} break;
case THEME_SAVE_AS: {
if (!EditorSettings::get_singleton()->save_text_editor_theme_as(p_file)) {
@@ -1240,13 +1244,14 @@ void ScriptEditor::_menu_option(int p_option) {
}
}
- Ref<TextFile> text_file = current->get_edited_resource();
+ RES resource = current->get_edited_resource();
+ Ref<TextFile> text_file = resource;
if (text_file != nullptr) {
current->apply_code();
_save_text_file(text_file, text_file->get_path());
break;
}
- editor->save_resource(current->get_edited_resource());
+ editor->save_resource(resource);
} break;
case FILE_SAVE_AS: {
@@ -1264,7 +1269,8 @@ void ScriptEditor::_menu_option(int p_option) {
}
}
- Ref<TextFile> text_file = current->get_edited_resource();
+ RES resource = current->get_edited_resource();
+ Ref<TextFile> text_file = resource;
if (text_file != nullptr) {
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
@@ -1280,8 +1286,8 @@ void ScriptEditor::_menu_option(int p_option) {
break;
}
- editor->push_item(Object::cast_to<Object>(current->get_edited_resource().ptr()));
- editor->save_resource_as(current->get_edited_resource());
+ editor->push_item(resource.ptr());
+ editor->save_resource_as(resource);
} break;
@@ -1577,7 +1583,9 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
List<int> bpoints;
se->get_breakpoints(&bpoints);
String base = script->get_path();
- ERR_CONTINUE(base.begins_with("local://") || base == "");
+ if (base.begins_with("local://") || base == "") {
+ continue;
+ }
for (List<int>::Element *E = bpoints.front(); E; E = E->next()) {
p_breakpoints->push_back(base + ":" + itos(E->get() + 1));
@@ -1630,6 +1638,8 @@ void ScriptEditor::ensure_select_current() {
if (tab_container->get_child_count() && tab_container->get_current_tab() >= 0) {
ScriptEditorBase *se = _get_current_editor();
if (se) {
+ se->enable_editor();
+
if (!grab_focus_block && is_visible_in_tree()) {
se->ensure_focus();
}
@@ -1974,6 +1984,11 @@ void ScriptEditor::_update_script_names() {
script_list->select(index);
script_name_label->set_text(sedata_filtered[i].name);
script_icon->set_texture(sedata_filtered[i].icon);
+ ScriptEditorBase *se = _get_current_editor();
+ if (se) {
+ se->enable_editor();
+ _update_selected_editor_menu();
+ }
}
}
@@ -2148,6 +2163,8 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
if ((script != nullptr && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) {
if (should_open) {
+ se->enable_editor();
+
if (tab_container->get_current_tab() != i) {
_go_to_tab(i);
_update_script_names();
@@ -2178,6 +2195,8 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
}
ERR_FAIL_COND_V(!se, false);
+ se->set_edited_resource(p_resource);
+
if (p_resource->get_class_name() != StringName("VisualScript")) {
bool highlighter_set = false;
for (int i = 0; i < syntax_highlighters.size(); i++) {
@@ -2198,7 +2217,11 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
}
tab_container->add_child(se);
- se->set_edited_resource(p_resource);
+
+ if (p_grab_focus) {
+ se->enable_editor();
+ }
+
se->set_tooltip_request_func("_get_debug_tooltip", this);
if (se->get_edit_menu()) {
se->get_edit_menu()->hide();
@@ -2208,6 +2231,7 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
if (p_grab_focus) {
_go_to_tab(tab_container->get_tab_count() - 1);
+ _add_recent_script(p_resource->get_path());
}
_sort_list_on_update = true;
@@ -2232,7 +2256,6 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
}
notify_script_changed(p_resource);
- _add_recent_script(p_resource->get_path());
return true;
}
@@ -2704,7 +2727,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
if (!scr.is_valid()) {
continue;
}
- if (!edit(scr)) {
+ if (!edit(scr, false)) {
continue;
}
} else {
@@ -2713,7 +2736,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
if (error != OK || !text_file.is_valid()) {
continue;
}
- if (!edit(text_file)) {
+ if (!edit(text_file, false)) {
continue;
}
}
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index 3891af4091..74376d692f 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -134,6 +134,7 @@ public:
virtual RES get_edited_resource() const = 0;
virtual Vector<String> get_functions() = 0;
virtual void set_edited_resource(const RES &p_res) = 0;
+ virtual void enable_editor() = 0;
virtual void reload_text() = 0;
virtual String get_name() = 0;
virtual Ref<Texture2D> get_theme_icon() = 0;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 1e03d9dfab..f728974dd7 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -140,10 +140,10 @@ RES ScriptTextEditor::get_edited_resource() const {
}
void ScriptTextEditor::set_edited_resource(const RES &p_res) {
- ERR_FAIL_COND(!script.is_null());
+ ERR_FAIL_COND(script.is_valid());
+ ERR_FAIL_COND(p_res.is_null());
script = p_res;
- _set_theme_for_script();
code_editor->get_text_edit()->set_text(script->get_source_code());
code_editor->get_text_edit()->clear_undo_history();
@@ -151,6 +151,17 @@ void ScriptTextEditor::set_edited_resource(const RES &p_res) {
emit_signal("name_changed");
code_editor->update_line_and_column();
+}
+
+void ScriptTextEditor::enable_editor() {
+ if (editor_enabled) {
+ return;
+ }
+
+ editor_enabled = true;
+
+ _enable_code_editor();
+ _set_theme_for_script();
_validate_script();
}
@@ -301,14 +312,6 @@ void ScriptTextEditor::reload_text() {
code_editor->update_line_and_column();
}
-void ScriptTextEditor::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_READY:
- _load_theme_settings();
- break;
- }
-}
-
void ScriptTextEditor::add_callback(const String &p_function, PackedStringArray p_args) {
String code = code_editor->get_text_edit()->get_text();
int pos = script->get_language()->find_function(p_function, code);
@@ -352,6 +355,10 @@ void ScriptTextEditor::set_edit_state(const Variant &p_state) {
_change_syntax_highlighter(idx);
}
}
+
+ if (editor_enabled) {
+ ensure_focus();
+ }
}
void ScriptTextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) {
@@ -494,7 +501,7 @@ void ScriptTextEditor::_validate_script() {
ScriptLanguage::Warning w = E->get();
Dictionary ignore_meta;
- ignore_meta["line"] = w.line;
+ ignore_meta["line"] = w.start_line;
ignore_meta["code"] = w.string_code.to_lower();
warnings_panel->push_cell();
warnings_panel->push_meta(ignore_meta);
@@ -506,9 +513,9 @@ void ScriptTextEditor::_validate_script() {
warnings_panel->pop(); // Cell.
warnings_panel->push_cell();
- warnings_panel->push_meta(w.line - 1);
+ warnings_panel->push_meta(w.start_line - 1);
warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor"));
- warnings_panel->add_text(TTR("Line") + " " + itos(w.line));
+ warnings_panel->add_text(TTR("Line") + " " + itos(w.start_line));
warnings_panel->add_text(" (" + w.string_code + "):");
warnings_panel->pop(); // Color.
warnings_panel->pop(); // Meta goto.
@@ -1274,23 +1281,28 @@ void ScriptTextEditor::_edit_option_toggle_inline_comment() {
}
void ScriptTextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
+ ERR_FAIL_COND(p_highlighter.is_null());
+
highlighters[p_highlighter->_get_name()] = p_highlighter;
highlighter_menu->add_radio_check_item(p_highlighter->_get_name());
}
void ScriptTextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
+ ERR_FAIL_COND(p_highlighter.is_null());
+
+ Map<String, Ref<EditorSyntaxHighlighter>>::Element *el = highlighters.front();
+ while (el != nullptr) {
+ int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key());
+ highlighter_menu->set_item_checked(highlighter_index, el->value() == p_highlighter);
+ el = el->next();
+ }
+
TextEdit *te = code_editor->get_text_edit();
p_highlighter->_set_edited_resource(script);
te->set_syntax_highlighter(p_highlighter);
- highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(p_highlighter->_get_name()), true);
}
void ScriptTextEditor::_change_syntax_highlighter(int p_idx) {
- Map<String, Ref<EditorSyntaxHighlighter>>::Element *el = highlighters.front();
- while (el != nullptr) {
- highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(el->key()), false);
- el = el->next();
- }
set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]);
}
@@ -1606,64 +1618,40 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p
context_menu->popup();
}
-ScriptTextEditor::ScriptTextEditor() {
- theme_loaded = false;
- script_is_valid = false;
+void ScriptTextEditor::_enable_code_editor() {
+ ERR_FAIL_COND(code_editor->get_parent());
VSplitContainer *editor_box = memnew(VSplitContainer);
add_child(editor_box);
editor_box->set_anchors_and_margins_preset(Control::PRESET_WIDE);
editor_box->set_v_size_flags(SIZE_EXPAND_FILL);
- code_editor = memnew(CodeTextEditor);
editor_box->add_child(code_editor);
- code_editor->add_theme_constant_override("separation", 2);
- code_editor->set_anchors_and_margins_preset(Control::PRESET_WIDE);
+ code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel));
code_editor->connect("validate_script", callable_mp(this, &ScriptTextEditor::_validate_script));
code_editor->connect("load_theme_settings", callable_mp(this, &ScriptTextEditor::_load_theme_settings));
- code_editor->set_code_complete_func(_code_complete_scripts, this);
code_editor->get_text_edit()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled));
code_editor->get_text_edit()->connect("symbol_lookup", callable_mp(this, &ScriptTextEditor::_lookup_symbol));
code_editor->get_text_edit()->connect("symbol_validate", callable_mp(this, &ScriptTextEditor::_validate_symbol));
- code_editor->get_text_edit()->connect("info_clicked", callable_mp(this, &ScriptTextEditor::_lookup_connections));
- code_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ code_editor->get_text_edit()->connect("gui_input", callable_mp(this, &ScriptTextEditor::_text_edit_gui_input));
code_editor->show_toggle_scripts_button();
- warnings_panel = memnew(RichTextLabel);
editor_box->add_child(warnings_panel);
warnings_panel->add_theme_font_override(
"normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts"));
- warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
- warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
- warnings_panel->set_meta_underline(true);
- warnings_panel->set_selection_enabled(true);
- warnings_panel->set_focus_mode(FOCUS_CLICK);
- warnings_panel->hide();
-
- code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel));
warnings_panel->connect("meta_clicked", callable_mp(this, &ScriptTextEditor::_warning_clicked));
- update_settings();
-
- code_editor->get_text_edit()->set_callhint_settings(
- EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"),
- EditorSettings::get_singleton()->get("text_editor/completion/callhint_tooltip_offset"));
-
- code_editor->get_text_edit()->set_select_identifiers_on_hover(true);
- code_editor->get_text_edit()->set_context_menu_enabled(false);
- code_editor->get_text_edit()->connect("gui_input", callable_mp(this, &ScriptTextEditor::_text_edit_gui_input));
-
- context_menu = memnew(PopupMenu);
add_child(context_menu);
context_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
- color_panel = memnew(PopupPanel);
add_child(color_panel);
+
color_picker = memnew(ColorPicker);
color_picker->set_deferred_mode(true);
- color_panel->add_child(color_picker);
color_picker->connect("color_changed", callable_mp(this, &ScriptTextEditor::_color_changed));
+ color_panel->add_child(color_picker);
+
// get default color picker mode from editor settings
int default_color_mode = EDITOR_GET("interface/inspector/default_color_picker_mode");
if (default_color_mode == 1) {
@@ -1672,12 +1660,27 @@ ScriptTextEditor::ScriptTextEditor() {
color_picker->set_raw_mode(true);
}
- edit_hb = memnew(HBoxContainer);
+ quick_open = memnew(ScriptEditorQuickOpen);
+ quick_open->connect("goto_line", callable_mp(this, &ScriptTextEditor::_goto_line));
+ add_child(quick_open);
- edit_menu = memnew(MenuButton);
- edit_menu->set_text(TTR("Edit"));
- edit_menu->set_switch_on_hover(true);
+ goto_line_dialog = memnew(GotoLineDialog);
+ add_child(goto_line_dialog);
+
+ add_child(connection_info_dialog);
+
+ edit_hb->add_child(search_menu);
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND);
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT);
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);
+ search_menu->get_popup()->add_separator();
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES);
+ search_menu->get_popup()->add_separator();
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL);
+ search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
+ edit_hb->add_child(edit_menu);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/undo"), EDIT_UNDO);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/redo"), EDIT_REDO);
edit_menu->get_popup()->add_separator();
@@ -1707,8 +1710,6 @@ ScriptTextEditor::ScriptTextEditor() {
edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
edit_menu->get_popup()->add_separator();
- PopupMenu *convert_case = memnew(PopupMenu);
- convert_case->set_name("convert_case");
edit_menu->get_popup()->add_child(convert_case);
edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "convert_case");
convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KEY_MASK_SHIFT | KEY_F4), EDIT_TO_UPPERCASE);
@@ -1716,12 +1717,73 @@ ScriptTextEditor::ScriptTextEditor() {
convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KEY_MASK_SHIFT | KEY_F6), EDIT_CAPITALIZE);
convert_case->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
- highlighter_menu = memnew(PopupMenu);
- highlighter_menu->set_name("highlighter_menu");
edit_menu->get_popup()->add_child(highlighter_menu);
edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter"), "highlighter_menu");
highlighter_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_change_syntax_highlighter));
+ _load_theme_settings();
+
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES);
+ edit_hb->add_child(goto_menu);
+ goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION);
+ goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);
+ goto_menu->get_popup()->add_separator();
+
+ goto_menu->get_popup()->add_child(bookmarks_menu);
+ goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks");
+ _update_bookmark_list();
+ bookmarks_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_bookmark_list));
+ bookmarks_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_bookmark_item_pressed));
+
+ goto_menu->get_popup()->add_child(breakpoints_menu);
+ goto_menu->get_popup()->add_submenu_item(TTR("Breakpoints"), "Breakpoints");
+ _update_breakpoint_list();
+ breakpoints_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_breakpoint_list));
+ breakpoints_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_breakpoint_item_pressed));
+
+ goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
+}
+
+ScriptTextEditor::ScriptTextEditor() {
+ code_editor = memnew(CodeTextEditor);
+ code_editor->add_theme_constant_override("separation", 2);
+ code_editor->set_anchors_and_margins_preset(Control::PRESET_WIDE);
+ code_editor->set_code_complete_func(_code_complete_scripts, this);
+ code_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ warnings_panel = memnew(RichTextLabel);
+ warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
+ warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
+ warnings_panel->set_meta_underline(true);
+ warnings_panel->set_selection_enabled(true);
+ warnings_panel->set_focus_mode(FOCUS_CLICK);
+ warnings_panel->hide();
+
+ update_settings();
+
+ code_editor->get_text_edit()->set_callhint_settings(
+ EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"),
+ EditorSettings::get_singleton()->get("text_editor/completion/callhint_tooltip_offset"));
+
+ code_editor->get_text_edit()->set_select_identifiers_on_hover(true);
+ code_editor->get_text_edit()->set_context_menu_enabled(false);
+
+ context_menu = memnew(PopupMenu);
+
+ color_panel = memnew(PopupPanel);
+
+ edit_hb = memnew(HBoxContainer);
+
+ edit_menu = memnew(MenuButton);
+ edit_menu->set_text(TTR("Edit"));
+ edit_menu->set_switch_on_hover(true);
+
+ convert_case = memnew(PopupMenu);
+ convert_case->set_name("convert_case");
+
+ highlighter_menu = memnew(PopupMenu);
+ highlighter_menu->set_name("highlighter_menu");
+
Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter;
plain_highlighter.instance();
add_syntax_highlighter(plain_highlighter);
@@ -1732,64 +1794,42 @@ ScriptTextEditor::ScriptTextEditor() {
set_syntax_highlighter(highlighter);
search_menu = memnew(MenuButton);
- edit_hb->add_child(search_menu);
search_menu->set_text(TTR("Search"));
search_menu->set_switch_on_hover(true);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);
- search_menu->get_popup()->add_separator();
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES);
- search_menu->get_popup()->add_separator();
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL);
- search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
-
- edit_hb->add_child(edit_menu);
-
- MenuButton *goto_menu = memnew(MenuButton);
- edit_hb->add_child(goto_menu);
+ goto_menu = memnew(MenuButton);
goto_menu->set_text(TTR("Go To"));
goto_menu->set_switch_on_hover(true);
- goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
-
- goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION);
- goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);
- goto_menu->get_popup()->add_separator();
bookmarks_menu = memnew(PopupMenu);
bookmarks_menu->set_name("Bookmarks");
- goto_menu->get_popup()->add_child(bookmarks_menu);
- goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks");
- _update_bookmark_list();
- bookmarks_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_bookmark_list));
- bookmarks_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_bookmark_item_pressed));
breakpoints_menu = memnew(PopupMenu);
breakpoints_menu->set_name("Breakpoints");
- goto_menu->get_popup()->add_child(breakpoints_menu);
- goto_menu->get_popup()->add_submenu_item(TTR("Breakpoints"), "Breakpoints");
- _update_breakpoint_list();
- breakpoints_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_breakpoint_list));
- breakpoints_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_breakpoint_item_pressed));
-
- quick_open = memnew(ScriptEditorQuickOpen);
- add_child(quick_open);
- quick_open->connect("goto_line", callable_mp(this, &ScriptTextEditor::_goto_line));
-
- goto_line_dialog = memnew(GotoLineDialog);
- add_child(goto_line_dialog);
connection_info_dialog = memnew(ConnectionInfoDialog);
- add_child(connection_info_dialog);
code_editor->get_text_edit()->set_drag_forwarding(this);
}
ScriptTextEditor::~ScriptTextEditor() {
highlighters.clear();
+
+ if (!editor_enabled) {
+ memdelete(code_editor);
+ memdelete(warnings_panel);
+ memdelete(context_menu);
+ memdelete(color_panel);
+ memdelete(edit_hb);
+ memdelete(edit_menu);
+ memdelete(convert_case);
+ memdelete(highlighter_menu);
+ memdelete(search_menu);
+ memdelete(goto_menu);
+ memdelete(bookmarks_menu);
+ memdelete(breakpoints_menu);
+ memdelete(connection_info_dialog);
+ }
}
static ScriptEditorBase *create_editor(const RES &p_resource) {
diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h
index 6d7f84d746..e931c9fdc6 100644
--- a/editor/plugins/script_text_editor.h
+++ b/editor/plugins/script_text_editor.h
@@ -39,8 +39,8 @@
class ConnectionInfoDialog : public AcceptDialog {
GDCLASS(ConnectionInfoDialog, AcceptDialog);
- Label *method;
- Tree *tree;
+ Label *method = nullptr;
+ Tree *tree = nullptr;
virtual void ok_pressed() override;
@@ -53,11 +53,12 @@ public:
class ScriptTextEditor : public ScriptEditorBase {
GDCLASS(ScriptTextEditor, ScriptEditorBase);
- CodeTextEditor *code_editor;
- RichTextLabel *warnings_panel;
+ CodeTextEditor *code_editor = nullptr;
+ RichTextLabel *warnings_panel = nullptr;
Ref<Script> script;
- bool script_is_valid;
+ bool script_is_valid = false;
+ bool editor_enabled = false;
Vector<String> functions;
@@ -65,25 +66,27 @@ class ScriptTextEditor : public ScriptEditorBase {
Vector<String> member_keywords;
- HBoxContainer *edit_hb;
+ HBoxContainer *edit_hb = nullptr;
- MenuButton *edit_menu;
- MenuButton *search_menu;
- PopupMenu *bookmarks_menu;
- PopupMenu *breakpoints_menu;
- PopupMenu *highlighter_menu;
- PopupMenu *context_menu;
+ MenuButton *edit_menu = nullptr;
+ MenuButton *search_menu = nullptr;
+ MenuButton *goto_menu = nullptr;
+ PopupMenu *bookmarks_menu = nullptr;
+ PopupMenu *breakpoints_menu = nullptr;
+ PopupMenu *highlighter_menu = nullptr;
+ PopupMenu *context_menu = nullptr;
+ PopupMenu *convert_case = nullptr;
- GotoLineDialog *goto_line_dialog;
- ScriptEditorQuickOpen *quick_open;
- ConnectionInfoDialog *connection_info_dialog;
+ GotoLineDialog *goto_line_dialog = nullptr;
+ ScriptEditorQuickOpen *quick_open = nullptr;
+ ConnectionInfoDialog *connection_info_dialog = nullptr;
- PopupPanel *color_panel;
- ColorPicker *color_picker;
+ PopupPanel *color_panel = nullptr;
+ ColorPicker *color_picker = nullptr;
Vector2 color_position;
String color_args;
- bool theme_loaded;
+ bool theme_loaded = false;
enum {
EDIT_UNDO,
@@ -132,6 +135,8 @@ class ScriptTextEditor : public ScriptEditorBase {
LOOKUP_SYMBOL,
};
+ void _enable_code_editor();
+
protected:
void _update_breakpoint_list();
void _breakpoint_item_pressed(int p_idx);
@@ -149,7 +154,6 @@ protected:
void _show_warnings_panel(bool p_show);
void _warning_clicked(Variant p_line);
- void _notification(int p_what);
static void _bind_methods();
Map<String, Ref<EditorSyntaxHighlighter>> highlighters;
@@ -185,6 +189,7 @@ public:
virtual void apply_code() override;
virtual RES get_edited_resource() const override;
virtual void set_edited_resource(const RES &p_res) override;
+ virtual void enable_editor() override;
virtual Vector<String> get_functions() override;
virtual void reload_text() override;
virtual String get_name() override;
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index d602d152fe..6d64e3b3cd 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -34,22 +34,27 @@
#include "editor/editor_node.h"
void TextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
+ ERR_FAIL_COND(p_highlighter.is_null());
+
highlighters[p_highlighter->_get_name()] = p_highlighter;
highlighter_menu->add_radio_check_item(p_highlighter->_get_name());
}
void TextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
- TextEdit *te = code_editor->get_text_edit();
- te->set_syntax_highlighter(p_highlighter);
- highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(p_highlighter->_get_name()), true);
-}
+ ERR_FAIL_COND(p_highlighter.is_null());
-void TextEditor::_change_syntax_highlighter(int p_idx) {
Map<String, Ref<EditorSyntaxHighlighter>>::Element *el = highlighters.front();
while (el != nullptr) {
- highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(el->key()), false);
+ int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key());
+ highlighter_menu->set_item_checked(highlighter_index, el->value() == p_highlighter);
el = el->next();
}
+
+ TextEdit *te = code_editor->get_text_edit();
+ te->set_syntax_highlighter(p_highlighter);
+}
+
+void TextEditor::_change_syntax_highlighter(int p_idx) {
set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]);
}
@@ -126,7 +131,7 @@ String TextEditor::get_name() {
}
Ref<Texture2D> TextEditor::get_theme_icon() {
- return EditorNode::get_singleton()->get_object_icon(text_file.operator->(), "");
+ return EditorNode::get_singleton()->get_object_icon(text_file.ptr(), "");
}
RES TextEditor::get_edited_resource() const {
@@ -134,7 +139,8 @@ RES TextEditor::get_edited_resource() const {
}
void TextEditor::set_edited_resource(const RES &p_res) {
- ERR_FAIL_COND(!text_file.is_null());
+ ERR_FAIL_COND(text_file.is_valid());
+ ERR_FAIL_COND(p_res.is_null());
text_file = p_res;
@@ -146,6 +152,16 @@ void TextEditor::set_edited_resource(const RES &p_res) {
code_editor->update_line_and_column();
}
+void TextEditor::enable_editor() {
+ if (editor_enabled) {
+ return;
+ }
+
+ editor_enabled = true;
+
+ _load_theme_settings();
+}
+
void TextEditor::add_callback(const String &p_function, PackedStringArray p_args) {
}
@@ -237,6 +253,8 @@ void TextEditor::set_edit_state(const Variant &p_state) {
_change_syntax_highlighter(idx);
}
}
+
+ ensure_focus();
}
void TextEditor::trim_trailing_whitespace() {
@@ -303,14 +321,6 @@ void TextEditor::clear_edit_menu() {
memdelete(edit_hb);
}
-void TextEditor::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_READY:
- _load_theme_settings();
- break;
- }
-}
-
void TextEditor::_edit_option(int p_op) {
TextEdit *tx = code_editor->get_text_edit();
diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h
index 5299776b56..f3e9e599cf 100644
--- a/editor/plugins/text_editor.h
+++ b/editor/plugins/text_editor.h
@@ -37,18 +37,19 @@ class TextEditor : public ScriptEditorBase {
GDCLASS(TextEditor, ScriptEditorBase);
private:
- CodeTextEditor *code_editor;
+ CodeTextEditor *code_editor = nullptr;
Ref<TextFile> text_file;
+ bool editor_enabled = false;
- HBoxContainer *edit_hb;
- MenuButton *edit_menu;
- PopupMenu *highlighter_menu;
- MenuButton *search_menu;
- PopupMenu *bookmarks_menu;
- PopupMenu *context_menu;
+ HBoxContainer *edit_hb = nullptr;
+ MenuButton *edit_menu = nullptr;
+ PopupMenu *highlighter_menu = nullptr;
+ MenuButton *search_menu = nullptr;
+ PopupMenu *bookmarks_menu = nullptr;
+ PopupMenu *context_menu = nullptr;
- GotoLineDialog *goto_line_dialog;
+ GotoLineDialog *goto_line_dialog = nullptr;
enum {
EDIT_UNDO,
@@ -88,8 +89,6 @@ private:
protected:
static void _bind_methods();
- void _notification(int p_what);
-
void _edit_option(int p_op);
void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position);
void _text_edit_gui_input(const Ref<InputEvent> &ev);
@@ -113,7 +112,7 @@ public:
virtual Ref<Texture2D> get_theme_icon() override;
virtual RES get_edited_resource() const override;
virtual void set_edited_resource(const RES &p_res) override;
- void set_edited_file(const Ref<TextFile> &p_file);
+ virtual void enable_editor() override;
virtual void reload_text() override;
virtual void apply_code() override;
virtual bool is_unsaved() override;
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 43ace737c0..18a107ff75 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -828,7 +828,7 @@ ThemeEditor::ThemeEditor() {
type_hbc->add_child(type_edit);
type_menu = memnew(MenuButton);
type_menu->set_flat(false);
- type_menu->set_text("..");
+ type_menu->set_text("...");
type_hbc->add_child(type_menu);
type_menu->get_popup()->connect("id_pressed", callable_mp(this, &ThemeEditor::_type_menu_cbk));
@@ -846,7 +846,7 @@ ThemeEditor::ThemeEditor() {
name_hbc->add_child(name_edit);
name_menu = memnew(MenuButton);
type_menu->set_flat(false);
- name_menu->set_text("..");
+ name_menu->set_text("...");
name_hbc->add_child(name_menu);
name_menu->get_popup()->connect("about_to_popup", callable_mp(this, &ThemeEditor::_name_menu_about_to_show));
diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp
index b121a7ae3e..a613174ed9 100644
--- a/editor/plugins/tile_set_editor_plugin.cpp
+++ b/editor/plugins/tile_set_editor_plugin.cpp
@@ -698,7 +698,7 @@ void TileSetEditor::_on_tileset_toolbar_confirm() {
List<int> ids;
tileset->get_tile_list(&ids);
- undo_redo->create_action(TTR(option == TOOL_TILESET_MERGE_SCENE ? "Merge Tileset from Scene" : "Create Tileset from Scene"));
+ undo_redo->create_action(option == TOOL_TILESET_MERGE_SCENE ? TTR("Merge Tileset from Scene") : TTR("Create Tileset from Scene"));
undo_redo->add_do_method(this, "_undo_redo_import_scene", scene, option == TOOL_TILESET_MERGE_SCENE);
undo_redo->add_undo_method(tileset.ptr(), "clear");
for (List<int>::Element *E = ids.front(); E; E = E->next()) {
@@ -1977,7 +1977,7 @@ void TileSetEditor::_set_edited_shape_points(const Vector<Vector2> &points) {
if (convex.is_valid()) {
undo_redo->add_do_method(convex.ptr(), "set_points", points);
undo_redo->add_undo_method(convex.ptr(), "set_points", _get_edited_shape_points());
- } else if (concave.is_valid()) {
+ } else if (concave.is_valid() && points.size() > 1) {
PackedVector2Array segments;
for (int i = 0; i < points.size() - 1; i++) {
segments.push_back(points[i]);
@@ -2669,7 +2669,7 @@ void TileSetEditor::draw_polygon_shapes() {
workspace->draw_polygon(polygon, colors);
if (coord == edited_shape_coord || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- if (!creating_shape) {
+ if (!creating_shape && polygon.size() > 1) {
for (int j = 0; j < polygon.size() - 1; j++) {
workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
}
@@ -2706,13 +2706,11 @@ void TileSetEditor::draw_polygon_shapes() {
}
workspace->draw_polygon(polygon, colors);
- if (!creating_shape) {
- if (polygon.size() > 1) {
- for (int j = 0; j < polygon.size() - 1; j++) {
- workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
- }
- workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
+ if (!creating_shape && polygon.size() > 1) {
+ for (int j = 0; j < polygon.size() - 1; j++) {
+ workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
}
+ workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
}
if (shape == edited_occlusion_shape) {
draw_handles = true;
@@ -2756,7 +2754,7 @@ void TileSetEditor::draw_polygon_shapes() {
workspace->draw_polygon(polygon, colors);
if (coord == edited_shape_coord) {
- if (!creating_shape) {
+ if (!creating_shape && polygon.size() > 1) {
for (int j = 0; j < polygon.size() - 1; j++) {
workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
}
@@ -2796,7 +2794,7 @@ void TileSetEditor::draw_polygon_shapes() {
}
workspace->draw_polygon(polygon, colors);
- if (!creating_shape) {
+ if (!creating_shape && polygon.size() > 1) {
for (int j = 0; j < polygon.size() - 1; j++) {
workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
}
@@ -2845,7 +2843,7 @@ void TileSetEditor::draw_polygon_shapes() {
workspace->draw_polygon(polygon, colors);
if (coord == edited_shape_coord) {
- if (!creating_shape) {
+ if (!creating_shape && polygon.size() > 1) {
for (int j = 0; j < polygon.size() - 1; j++) {
workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
}
@@ -2863,7 +2861,7 @@ void TileSetEditor::draw_polygon_shapes() {
}
}
- if (creating_shape) {
+ if (creating_shape && current_shape.size() > 1) {
for (int j = 0; j < current_shape.size() - 1; j++) {
workspace->draw_line(current_shape[j], current_shape[j + 1], Color(0, 1, 1), 1);
}
diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp
index 6a54894f40..211e365454 100644
--- a/editor/rename_dialog.cpp
+++ b/editor/rename_dialog.cpp
@@ -141,7 +141,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und
but_insert_name = memnew(Button);
but_insert_name->set_text("NAME");
- but_insert_name->set_tooltip(String("${NAME}\n") + TTR("Node name"));
+ but_insert_name->set_tooltip(String("${NAME}\n") + TTR("Node name."));
but_insert_name->set_focus_mode(Control::FOCUS_NONE);
but_insert_name->connect("pressed", callable_mp(this, &RenameDialog::_insert_text), make_binds("${NAME}"));
but_insert_name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -151,7 +151,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und
but_insert_parent = memnew(Button);
but_insert_parent->set_text("PARENT");
- but_insert_parent->set_tooltip(String("${PARENT}\n") + TTR("Node's parent name, if available"));
+ but_insert_parent->set_tooltip(String("${PARENT}\n") + TTR("Node's parent name, if available."));
but_insert_parent->set_focus_mode(Control::FOCUS_NONE);
but_insert_parent->connect("pressed", callable_mp(this, &RenameDialog::_insert_text), make_binds("${PARENT}"));
but_insert_parent->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -161,7 +161,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und
but_insert_type = memnew(Button);
but_insert_type->set_text("TYPE");
- but_insert_type->set_tooltip(String("${TYPE}\n") + TTR("Node type"));
+ but_insert_type->set_tooltip(String("${TYPE}\n") + TTR("Node type."));
but_insert_type->set_focus_mode(Control::FOCUS_NONE);
but_insert_type->connect("pressed", callable_mp(this, &RenameDialog::_insert_text), make_binds("${TYPE}"));
but_insert_type->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -171,7 +171,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und
but_insert_scene = memnew(Button);
but_insert_scene->set_text("SCENE");
- but_insert_scene->set_tooltip(String("${SCENE}\n") + TTR("Current scene name"));
+ but_insert_scene->set_tooltip(String("${SCENE}\n") + TTR("Current scene name."));
but_insert_scene->set_focus_mode(Control::FOCUS_NONE);
but_insert_scene->connect("pressed", callable_mp(this, &RenameDialog::_insert_text), make_binds("${SCENE}"));
but_insert_scene->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -181,7 +181,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und
but_insert_root = memnew(Button);
but_insert_root->set_text("ROOT");
- but_insert_root->set_tooltip(String("${ROOT}\n") + TTR("Root node name"));
+ but_insert_root->set_tooltip(String("${ROOT}\n") + TTR("Root node name."));
but_insert_root->set_focus_mode(Control::FOCUS_NONE);
but_insert_root->connect("pressed", callable_mp(this, &RenameDialog::_insert_text), make_binds("${ROOT}"));
but_insert_root->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -199,7 +199,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und
chk_per_level_counter = memnew(CheckBox);
chk_per_level_counter->set_text(TTR("Per-level Counter"));
- chk_per_level_counter->set_tooltip(TTR("If set the counter restarts for each group of child nodes"));
+ chk_per_level_counter->set_tooltip(TTR("If set the counter restarts for each group of child nodes."));
vbc_substitute->add_child(chk_per_level_counter);
HBoxContainer *hbc_count_options = memnew(HBoxContainer);
@@ -207,22 +207,22 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und
Label *lbl_count_start = memnew(Label);
lbl_count_start->set_text(TTR("Start"));
- lbl_count_start->set_tooltip(TTR("Initial value for the counter"));
+ lbl_count_start->set_tooltip(TTR("Initial value for the counter."));
hbc_count_options->add_child(lbl_count_start);
spn_count_start = memnew(SpinBox);
- spn_count_start->set_tooltip(TTR("Initial value for the counter"));
+ spn_count_start->set_tooltip(TTR("Initial value for the counter."));
spn_count_start->set_step(1);
spn_count_start->set_min(0);
hbc_count_options->add_child(spn_count_start);
Label *lbl_count_step = memnew(Label);
lbl_count_step->set_text(TTR("Step"));
- lbl_count_step->set_tooltip(TTR("Amount by which counter is incremented for each node"));
+ lbl_count_step->set_tooltip(TTR("Amount by which counter is incremented for each node."));
hbc_count_options->add_child(lbl_count_step);
spn_count_step = memnew(SpinBox);
- spn_count_step->set_tooltip(TTR("Amount by which counter is incremented for each node"));
+ spn_count_step->set_tooltip(TTR("Amount by which counter is incremented for each node."));
spn_count_step->set_step(1);
hbc_count_options->add_child(spn_count_step);
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 90747e45e4..ce869feddd 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -1100,6 +1100,7 @@ void SceneTreeDock::_notification(int p_what) {
node_shortcuts_toggle->set_name("NodeShortcutsToggle");
node_shortcuts_toggle->set_icon(get_theme_icon("Favorites", "EditorIcons"));
node_shortcuts_toggle->set_toggle_mode(true);
+ node_shortcuts_toggle->set_tooltip(TTR("Switch to Favorite Nodes"));
node_shortcuts_toggle->set_pressed(EDITOR_GET("_use_favorites_root_selection"));
node_shortcuts_toggle->set_anchors_and_margins_preset(Control::PRESET_CENTER_RIGHT);
node_shortcuts_toggle->connect("pressed", callable_mp(this, &SceneTreeDock::_update_create_root_dialog));
@@ -1217,7 +1218,6 @@ void SceneTreeDock::_node_selected() {
Node *node = scene_tree->get_selected();
if (!node) {
- editor->push_item(nullptr);
return;
}
@@ -1902,11 +1902,10 @@ void SceneTreeDock::_selection_changed() {
if (selection_size > 1) {
//automatically turn on multi-edit
_tool_selected(TOOL_MULTI_EDIT);
- } else if (selection_size == 1) {
- editor->push_item(EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list()[0]);
- } else {
+ } else if (selection_size == 0) {
editor->push_item(nullptr);
}
+
_update_script_button();
}
@@ -2719,7 +2718,7 @@ void SceneTreeDock::_update_create_root_dialog() {
if (l != String()) {
Button *button = memnew(Button);
favorite_nodes->add_child(button);
- button->set_text(TTR(l));
+ button->set_text(l);
String name = l.get_slicec(' ', 0);
if (ScriptServer::is_global_class(name)) {
name = ScriptServer::get_global_class_native_base(name);
diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp
index ffdf8208b8..628475bbc0 100644
--- a/editor/script_create_dialog.cpp
+++ b/editor/script_create_dialog.cpp
@@ -603,7 +603,7 @@ void ScriptCreateDialog::_path_entered(const String &p_path) {
}
void ScriptCreateDialog::_msg_script_valid(bool valid, const String &p_msg) {
- error_label->set_text("- " + TTR(p_msg));
+ error_label->set_text("- " + p_msg);
if (valid) {
error_label->add_theme_color_override("font_color", gc->get_theme_color("success_color", "Editor"));
} else {
@@ -612,7 +612,7 @@ void ScriptCreateDialog::_msg_script_valid(bool valid, const String &p_msg) {
}
void ScriptCreateDialog::_msg_path_valid(bool valid, const String &p_msg) {
- path_error_label->set_text("- " + TTR(p_msg));
+ path_error_label->set_text("- " + p_msg);
if (valid) {
path_error_label->add_theme_color_override("font_color", gc->get_theme_color("success_color", "Editor"));
} else {
diff --git a/main/SCsub b/main/SCsub
index 7a301b82bc..152d0c4d03 100644
--- a/main/SCsub
+++ b/main/SCsub
@@ -9,7 +9,6 @@ env.main_sources = []
env.add_source_files(env.main_sources, "*.cpp")
-
env.Depends("#main/splash.gen.h", "#main/splash.png")
env.CommandNoCache("#main/splash.gen.h", "#main/splash.png", run_in_subprocess(main_builders.make_splash))
@@ -21,8 +20,5 @@ env.CommandNoCache(
env.Depends("#main/app_icon.gen.h", "#main/app_icon.png")
env.CommandNoCache("#main/app_icon.gen.h", "#main/app_icon.png", run_in_subprocess(main_builders.make_app_icon))
-if env["tools"]:
- SConscript("tests/SCsub")
-
lib = env.add_library("main", env.main_sources)
env.Prepend(LIBS=[lib])
diff --git a/main/main.cpp b/main/main.cpp
index a500e173a2..6965e5415a 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -30,6 +30,7 @@
#include "main.h"
+#include "core/core_string_names.h"
#include "core/crypto/crypto.h"
#include "core/debugger/engine_debugger.h"
#include "core/input/input.h"
@@ -54,7 +55,6 @@
#include "main/performance.h"
#include "main/splash.gen.h"
#include "main/splash_editor.gen.h"
-#include "main/tests/test_main.h"
#include "modules/modules_enabled.gen.h"
#include "modules/register_module_types.h"
#include "platform/register_platform_apis.h"
@@ -74,13 +74,19 @@
#include "servers/rendering/rendering_server_wrap_mt.h"
#include "servers/xr_server.h"
+#ifdef TESTS_ENABLED
+#include "tests/test_main.h"
+#endif
+
#ifdef TOOLS_ENABLED
+
#include "editor/doc_data.h"
#include "editor/doc_data_class_path.gen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/progress_dialog.h"
#include "editor/project_manager.h"
+
#endif
/* Static members */
@@ -186,7 +192,8 @@ static String get_full_version_string() {
// to have less code in main.cpp.
void initialize_physics() {
/// 3D Physics Server
- physics_server = PhysicsServer3DManager::new_server(ProjectSettings::get_singleton()->get(PhysicsServer3DManager::setting_property_name));
+ physics_server = PhysicsServer3DManager::new_server(
+ ProjectSettings::get_singleton()->get(PhysicsServer3DManager::setting_property_name));
if (!physics_server) {
// Physics server not found, Use the default physics
physics_server = PhysicsServer3DManager::new_default_server();
@@ -195,7 +202,8 @@ void initialize_physics() {
physics_server->init();
/// 2D Physics server
- physics_2d_server = PhysicsServer2DManager::new_server(ProjectSettings::get_singleton()->get(PhysicsServer2DManager::setting_property_name));
+ physics_2d_server = PhysicsServer2DManager::new_server(
+ ProjectSettings::get_singleton()->get(PhysicsServer2DManager::setting_property_name));
if (!physics_2d_server) {
// Physics server not found, Use the default physics
physics_2d_server = PhysicsServer2DManager::new_default_server();
@@ -254,20 +262,25 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print(" -h, --help Display this help message.\n");
OS::get_singleton()->print(" --version Display the version string.\n");
OS::get_singleton()->print(" -v, --verbose Use verbose stdout mode.\n");
- OS::get_singleton()->print(" --quiet Quiet mode, silences stdout messages. Errors are still displayed.\n");
+ OS::get_singleton()->print(
+ " --quiet Quiet mode, silences stdout messages. Errors are still displayed.\n");
OS::get_singleton()->print("\n");
OS::get_singleton()->print("Run options:\n");
#ifdef TOOLS_ENABLED
OS::get_singleton()->print(" -e, --editor Start the editor instead of running the scene.\n");
- OS::get_singleton()->print(" -p, --project-manager Start the project manager, even if a project is auto-detected.\n");
+ OS::get_singleton()->print(
+ " -p, --project-manager Start the project manager, even if a project is auto-detected.\n");
#endif
OS::get_singleton()->print(" -q, --quit Quit after the first iteration.\n");
- OS::get_singleton()->print(" -l, --language <locale> Use a specific locale (<locale> being a two-letter code).\n");
- OS::get_singleton()->print(" --path <directory> Path to a project (<directory> must contain a 'project.godot' file).\n");
+ OS::get_singleton()->print(
+ " -l, --language <locale> Use a specific locale (<locale> being a two-letter code).\n");
+ OS::get_singleton()->print(
+ " --path <directory> Path to a project (<directory> must contain a 'project.godot' file).\n");
OS::get_singleton()->print(" -u, --upwards Scan folders upwards for project.godot file.\n");
OS::get_singleton()->print(" --main-pack <file> Path to a pack (.pck) file to load.\n");
- OS::get_singleton()->print(" --render-thread <mode> Render thread mode ('unsafe', 'safe', 'separate').\n");
+ OS::get_singleton()->print(
+ " --render-thread <mode> Render thread mode ('unsafe', 'safe', 'separate').\n");
OS::get_singleton()->print(" --remote-fs <address> Remote filesystem (<host/IP>[:<port>] address).\n");
OS::get_singleton()->print(" --remote-fs-password <password> Password for remote filesystem.\n");
@@ -308,9 +321,12 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print(" --resolution <W>x<H> Request window resolution.\n");
OS::get_singleton()->print(" --position <X>,<Y> Request window position.\n");
OS::get_singleton()->print(" --low-dpi Force low-DPI mode (macOS and Windows only).\n");
- OS::get_singleton()->print(" --no-window Disable window creation (Windows only). Useful together with --script.\n");
- OS::get_singleton()->print(" --enable-vsync-via-compositor When vsync is enabled, vsync via the OS' window compositor (Windows only).\n");
- OS::get_singleton()->print(" --disable-vsync-via-compositor Disable vsync via the OS' window compositor (Windows only).\n");
+ OS::get_singleton()->print(
+ " --no-window Disable window creation (Windows only). Useful together with --script.\n");
+ OS::get_singleton()->print(
+ " --enable-vsync-via-compositor When vsync is enabled, vsync via the OS' window compositor (Windows only).\n");
+ OS::get_singleton()->print(
+ " --disable-vsync-via-compositor Disable vsync via the OS' window compositor (Windows only).\n");
OS::get_singleton()->print(" --single-window Use a single window (no separate subwindows).\n");
OS::get_singleton()->print(" --tablet-driver Tablet input driver (");
for (int i = 0; i < OS::get_singleton()->get_tablet_driver_count(); i++) {
@@ -324,36 +340,53 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print("Debug options:\n");
OS::get_singleton()->print(" -d, --debug Debug (local stdout debugger).\n");
- OS::get_singleton()->print(" -b, --breakpoints Breakpoint list as source::line comma-separated pairs, no spaces (use %%20 instead).\n");
+ OS::get_singleton()->print(
+ " -b, --breakpoints Breakpoint list as source::line comma-separated pairs, no spaces (use %%20 instead).\n");
OS::get_singleton()->print(" --profiling Enable profiling in the script debugger.\n");
- OS::get_singleton()->print(" --gpu-abort Abort on GPU errors (usually validation layer errors), may help see the problem if your system freezes.\n");
- OS::get_singleton()->print(" --remote-debug <uri> Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n");
+ OS::get_singleton()->print(
+ " --gpu-abort Abort on GPU errors (usually validation layer errors), may help see the problem if your system freezes.\n");
+ OS::get_singleton()->print(
+ " --remote-debug <uri> Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n");
#if defined(DEBUG_ENABLED) && !defined(SERVER_ENABLED)
OS::get_singleton()->print(" --debug-collisions Show collision shapes when running the scene.\n");
OS::get_singleton()->print(" --debug-navigation Show navigation polygons when running the scene.\n");
#endif
- OS::get_singleton()->print(" --frame-delay <ms> Simulate high CPU load (delay each frame by <ms> milliseconds).\n");
- OS::get_singleton()->print(" --time-scale <scale> Force time scale (higher values are faster, 1.0 is normal speed).\n");
- OS::get_singleton()->print(" --disable-render-loop Disable render loop so rendering only occurs when called explicitly from script.\n");
- OS::get_singleton()->print(" --disable-crash-handler Disable crash handler when supported by the platform code.\n");
- OS::get_singleton()->print(" --fixed-fps <fps> Force a fixed number of frames per second. This setting disables real-time synchronization.\n");
+ OS::get_singleton()->print(
+ " --frame-delay <ms> Simulate high CPU load (delay each frame by <ms> milliseconds).\n");
+ OS::get_singleton()->print(
+ " --time-scale <scale> Force time scale (higher values are faster, 1.0 is normal speed).\n");
+ OS::get_singleton()->print(
+ " --disable-render-loop Disable render loop so rendering only occurs when called explicitly from script.\n");
+ OS::get_singleton()->print(
+ " --disable-crash-handler Disable crash handler when supported by the platform code.\n");
+ OS::get_singleton()->print(
+ " --fixed-fps <fps> Force a fixed number of frames per second. This setting disables real-time synchronization.\n");
OS::get_singleton()->print(" --print-fps Print the frames per second to the stdout.\n");
OS::get_singleton()->print("\n");
OS::get_singleton()->print("Standalone tools:\n");
OS::get_singleton()->print(" -s, --script <script> Run a script.\n");
- OS::get_singleton()->print(" --check-only Only parse for errors and quit (use with --script).\n");
+ OS::get_singleton()->print(
+ " --check-only Only parse for errors and quit (use with --script).\n");
#ifdef TOOLS_ENABLED
- OS::get_singleton()->print(" --export <preset> <path> Export the project using the given preset and matching release template. The preset name should match one defined in export_presets.cfg.\n");
- OS::get_singleton()->print(" <path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. 'builds/game.exe'). The target directory should exist.\n");
+ OS::get_singleton()->print(
+ " --export <preset> <path> Export the project using the given preset and matching release template. The preset name should match one defined in export_presets.cfg.\n");
+ OS::get_singleton()->print(
+ " <path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. 'builds/game.exe'). The target directory should exist.\n");
OS::get_singleton()->print(" --export-debug <preset> <path> Same as --export, but using the debug template.\n");
- OS::get_singleton()->print(" --export-pack <preset> <path> Same as --export, but only export the game pack for the given preset. The <path> extension determines whether it will be in PCK or ZIP format.\n");
- OS::get_singleton()->print(" --doctool <path> Dump the engine API reference to the given <path> in XML format, merging if existing files are found.\n");
- OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n");
- OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n");
+ OS::get_singleton()->print(
+ " --export-pack <preset> <path> Same as --export, but only export the game pack for the given preset. The <path> extension determines whether it will be in PCK or ZIP format.\n");
+ OS::get_singleton()->print(
+ " --doctool <path> Dump the engine API reference to the given <path> in XML format, merging if existing files are found.\n");
+ OS::get_singleton()->print(
+ " --no-docbase Disallow dumping the base types (used with --doctool).\n");
+ OS::get_singleton()->print(
+ " --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n");
#ifdef DEBUG_METHODS_ENABLED
- OS::get_singleton()->print(" --gdnative-generate-json-api Generate JSON dump of the Godot API for GDNative bindings.\n");
+ OS::get_singleton()->print(
+ " --gdnative-generate-json-api Generate JSON dump of the Godot API for GDNative bindings.\n");
#endif
+#ifdef TESTS_ENABLED
OS::get_singleton()->print(" --test <test> Run a unit test [");
const char **test_names = tests_get_names();
const char *comma = "";
@@ -364,6 +397,26 @@ void Main::print_help(const char *p_binary) {
}
OS::get_singleton()->print("].\n");
#endif
+ OS::get_singleton()->print("\n");
+#endif
+}
+
+int Main::test_entrypoint(int argc, char *argv[], bool &tests_need_run) {
+#ifdef TESTS_ENABLED
+ for (int x = 0; x < argc; x++) {
+ if (strncmp(argv[x], "--test", 6) == 0) {
+ tests_need_run = true;
+ OS::get_singleton()->initialize();
+ StringName::setup();
+ int status = test_main(argc, argv);
+ StringName::cleanup();
+ // TODO: fix OS::singleton cleanup
+ return status;
+ }
+ }
+#endif
+ tests_need_run = false;
+ return 0;
}
/* Engine initialization
@@ -418,7 +471,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
ClassDB::register_class<Performance>();
engine->add_singleton(Engine::Singleton("Performance", performance));
- GLOBAL_DEF("debug/settings/crash_handler/message", String("Please include this when reporting the bug on https://github.com/godotengine/godot/issues"));
+ GLOBAL_DEF("debug/settings/crash_handler/message",
+ String("Please include this when reporting the bug on https://github.com/godotengine/godot/issues"));
MAIN_PRINT("Main: Parse CMDLine");
@@ -523,7 +577,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
if (!found) {
- OS::get_singleton()->print("Unknown audio driver '%s', aborting.\nValid options are ", audio_driver.utf8().get_data());
+ OS::get_singleton()->print("Unknown audio driver '%s', aborting.\nValid options are ",
+ audio_driver.utf8().get_data());
for (int i = 0; i < AudioDriverManager::get_driver_count(); i++) {
if (i == AudioDriverManager::get_driver_count() - 1) {
@@ -559,7 +614,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
if (!found) {
- OS::get_singleton()->print("Unknown display driver '%s', aborting.\nValid options are ", display_driver.utf8().get_data());
+ OS::get_singleton()->print("Unknown display driver '%s', aborting.\nValid options are ",
+ display_driver.utf8().get_data());
for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {
if (i == DisplayServer::get_create_function_count() - 1) {
@@ -607,7 +663,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
if (!found) {
- OS::get_singleton()->print("Unknown tablet driver '%s', aborting.\n", tablet_driver.utf8().get_data());
+ OS::get_singleton()->print("Unknown tablet driver '%s', aborting.\n",
+ tablet_driver.utf8().get_data());
goto error;
}
@@ -629,7 +686,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (vm.find("x") == -1) { // invalid parameter format
- OS::get_singleton()->print("Invalid resolution '%s', it should be e.g. '1280x720'.\n", vm.utf8().get_data());
+ OS::get_singleton()->print("Invalid resolution '%s', it should be e.g. '1280x720'.\n",
+ vm.utf8().get_data());
goto error;
}
@@ -637,7 +695,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
int h = vm.get_slice("x", 1).to_int();
if (w <= 0 || h <= 0) {
- OS::get_singleton()->print("Invalid resolution '%s', width and height must be above 0.\n", vm.utf8().get_data());
+ OS::get_singleton()->print("Invalid resolution '%s', width and height must be above 0.\n",
+ vm.utf8().get_data());
goto error;
}
@@ -658,7 +717,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (vm.find(",") == -1) { // invalid parameter format
- OS::get_singleton()->print("Invalid position '%s', it should be e.g. '80,128'.\n", vm.utf8().get_data());
+ OS::get_singleton()->print("Invalid position '%s', it should be e.g. '80,128'.\n",
+ vm.utf8().get_data());
goto error;
}
@@ -754,7 +814,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
// We still pass it to the main arguments since the argument handling itself is not done in this function
main_args.push_back(I->get());
#endif
- } else if (I->get() == "--export" || I->get() == "--export-debug" || I->get() == "--export-pack") { // Export project
+ } else if (I->get() == "--export" || I->get() == "--export-debug" ||
+ I->get() == "--export-pack") { // Export project
editor = true;
main_args.push_back(I->get());
@@ -847,7 +908,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (I->next()) {
debug_uri = I->next()->get();
if (debug_uri.find("://") == -1) { // wrong address
- OS::get_singleton()->print("Invalid debug host address, it should be of the form <protocol>://<host/IP>:<port>.\n");
+ OS::get_singleton()->print(
+ "Invalid debug host address, it should be of the form <protocol>://<host/IP>:<port>.\n");
goto error;
}
N = I->next()->next();
@@ -888,7 +950,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
#ifdef TOOLS_ENABLED
if (editor && project_manager) {
- OS::get_singleton()->print("Error: Command line arguments implied opening both editor and project manager, which is not possible. Aborting.\n");
+ OS::get_singleton()->print(
+ "Error: Command line arguments implied opening both editor and project manager, which is not possible. Aborting.\n");
goto error;
}
#endif
@@ -935,15 +998,35 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->ensure_user_data_dir();
GLOBAL_DEF("memory/limits/multithreaded_server/rid_pool_prealloc", 60);
- ProjectSettings::get_singleton()->set_custom_property_info("memory/limits/multithreaded_server/rid_pool_prealloc", PropertyInfo(Variant::INT, "memory/limits/multithreaded_server/rid_pool_prealloc", PROPERTY_HINT_RANGE, "0,500,1")); // No negative and limit to 500 due to crashes
+ ProjectSettings::get_singleton()->set_custom_property_info("memory/limits/multithreaded_server/rid_pool_prealloc",
+ PropertyInfo(Variant::INT,
+ "memory/limits/multithreaded_server/rid_pool_prealloc",
+ PROPERTY_HINT_RANGE,
+ "0,500,1")); // No negative and limit to 500 due to crashes
GLOBAL_DEF("network/limits/debugger/max_chars_per_second", 32768);
- ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_chars_per_second", PropertyInfo(Variant::INT, "network/limits/debugger/max_chars_per_second", PROPERTY_HINT_RANGE, "0, 4096, 1, or_greater"));
+ ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_chars_per_second",
+ PropertyInfo(Variant::INT,
+ "network/limits/debugger/max_chars_per_second",
+ PROPERTY_HINT_RANGE,
+ "0, 4096, 1, or_greater"));
GLOBAL_DEF("network/limits/debugger/max_queued_messages", 2048);
- ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_queued_messages", PropertyInfo(Variant::INT, "network/limits/debugger/max_queued_messages", PROPERTY_HINT_RANGE, "0, 8192, 1, or_greater"));
+ ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_queued_messages",
+ PropertyInfo(Variant::INT,
+ "network/limits/debugger/max_queued_messages",
+ PROPERTY_HINT_RANGE,
+ "0, 8192, 1, or_greater"));
GLOBAL_DEF("network/limits/debugger/max_errors_per_second", 400);
- ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_errors_per_second", PropertyInfo(Variant::INT, "network/limits/debugger/max_errors_per_second", PROPERTY_HINT_RANGE, "0, 200, 1, or_greater"));
+ ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_errors_per_second",
+ PropertyInfo(Variant::INT,
+ "network/limits/debugger/max_errors_per_second",
+ PROPERTY_HINT_RANGE,
+ "0, 200, 1, or_greater"));
GLOBAL_DEF("network/limits/debugger/max_warnings_per_second", 400);
- ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_warnings_per_second", PropertyInfo(Variant::INT, "network/limits/debugger/max_warnings_per_second", PROPERTY_HINT_RANGE, "0, 200, 1, or_greater"));
+ ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_warnings_per_second",
+ PropertyInfo(Variant::INT,
+ "network/limits/debugger/max_warnings_per_second",
+ PROPERTY_HINT_RANGE,
+ "0, 200, 1, or_greater"));
EngineDebugger::initialize(debug_uri, skip_breakpoints, breakpoints);
@@ -978,8 +1061,13 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF("logging/file_logging/enable_file_logging.pc", true);
GLOBAL_DEF("logging/file_logging/log_path", "user://logs/godot.log");
GLOBAL_DEF("logging/file_logging/max_log_files", 5);
- ProjectSettings::get_singleton()->set_custom_property_info("logging/file_logging/max_log_files", PropertyInfo(Variant::INT, "logging/file_logging/max_log_files", PROPERTY_HINT_RANGE, "0,20,1,or_greater")); //no negative numbers
- if (!project_manager && !editor && FileAccess::get_create_func(FileAccess::ACCESS_USERDATA) && GLOBAL_GET("logging/file_logging/enable_file_logging")) {
+ ProjectSettings::get_singleton()->set_custom_property_info("logging/file_logging/max_log_files",
+ PropertyInfo(Variant::INT,
+ "logging/file_logging/max_log_files",
+ PROPERTY_HINT_RANGE,
+ "0,20,1,or_greater")); //no negative numbers
+ if (!project_manager && !editor && FileAccess::get_create_func(FileAccess::ACCESS_USERDATA) &&
+ GLOBAL_GET("logging/file_logging/enable_file_logging")) {
// Don't create logs for the project manager as they would be written to
// the current working directory, which is inconvenient.
String base_path = GLOBAL_GET("logging/file_logging/log_path");
@@ -1020,7 +1108,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->set_cmdline(execpath, main_args);
GLOBAL_DEF("rendering/quality/driver/driver_name", "Vulkan");
- ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/driver/driver_name", PropertyInfo(Variant::STRING, "rendering/quality/driver/driver_name", PROPERTY_HINT_ENUM, "Vulkan,GLES2"));
+ ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/driver/driver_name",
+ PropertyInfo(Variant::STRING,
+ "rendering/quality/driver/driver_name",
+ PROPERTY_HINT_ENUM, "Vulkan,GLES2"));
if (display_driver == "") {
display_driver = GLOBAL_GET("rendering/quality/driver/driver_name");
}
@@ -1029,24 +1120,39 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF("rendering/quality/2d/gles2_use_nvidia_rect_flicker_workaround", false);
GLOBAL_DEF("display/window/size/width", 1024);
- ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/width", PropertyInfo(Variant::INT, "display/window/size/width", PROPERTY_HINT_RANGE, "0,7680,or_greater")); // 8K resolution
+ ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/width",
+ PropertyInfo(Variant::INT, "display/window/size/width",
+ PROPERTY_HINT_RANGE,
+ "0,7680,or_greater")); // 8K resolution
GLOBAL_DEF("display/window/size/height", 600);
- ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/height", PropertyInfo(Variant::INT, "display/window/size/height", PROPERTY_HINT_RANGE, "0,4320,or_greater")); // 8K resolution
+ ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/height",
+ PropertyInfo(Variant::INT, "display/window/size/height",
+ PROPERTY_HINT_RANGE,
+ "0,4320,or_greater")); // 8K resolution
GLOBAL_DEF("display/window/size/resizable", true);
GLOBAL_DEF("display/window/size/borderless", false);
GLOBAL_DEF("display/window/size/fullscreen", false);
GLOBAL_DEF("display/window/size/always_on_top", false);
GLOBAL_DEF("display/window/size/test_width", 0);
- ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/test_width", PropertyInfo(Variant::INT, "display/window/size/test_width", PROPERTY_HINT_RANGE, "0,7680,or_greater")); // 8K resolution
+ ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/test_width",
+ PropertyInfo(Variant::INT,
+ "display/window/size/test_width",
+ PROPERTY_HINT_RANGE,
+ "0,7680,or_greater")); // 8K resolution
GLOBAL_DEF("display/window/size/test_height", 0);
- ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/test_height", PropertyInfo(Variant::INT, "display/window/size/test_height", PROPERTY_HINT_RANGE, "0,4320,or_greater")); // 8K resolution
+ ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/test_height",
+ PropertyInfo(Variant::INT,
+ "display/window/size/test_height",
+ PROPERTY_HINT_RANGE,
+ "0,4320,or_greater")); // 8K resolution
if (use_custom_res) {
if (!force_res) {
window_size.width = GLOBAL_GET("display/window/size/width");
window_size.height = GLOBAL_GET("display/window/size/height");
- if (globals->has_setting("display/window/size/test_width") && globals->has_setting("display/window/size/test_height")) {
+ if (globals->has_setting("display/window/size/test_width") &&
+ globals->has_setting("display/window/size/test_height")) {
int tw = globals->get("display/window/size/test_width");
if (tw > 0) {
window_size.width = tw;
@@ -1091,7 +1197,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->_vsync_via_compositor = window_vsync_via_compositor;
if (tablet_driver == "") { // specified in project.godot
- tablet_driver = GLOBAL_DEF_RST("display/window/tablet_driver", OS::get_singleton()->get_tablet_driver_name(0));
+ tablet_driver = GLOBAL_DEF_RST_NOVAL("display/window/tablet_driver", OS::get_singleton()->get_tablet_driver_name(0));
}
for (int i = 0; i < OS::get_singleton()->get_tablet_driver_count(); i++) {
@@ -1106,8 +1212,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
/* todo restore
- OS::get_singleton()->_allow_layered = GLOBAL_DEF("display/window/per_pixel_transparency/allowed", false);
- video_mode.layered = GLOBAL_DEF("display/window/per_pixel_transparency/enabled", false);
+ OS::get_singleton()->_allow_layered = GLOBAL_DEF("display/window/per_pixel_transparency/allowed", false);
+ video_mode.layered = GLOBAL_DEF("display/window/per_pixel_transparency/enabled", false);
*/
GLOBAL_DEF("rendering/quality/intended_usage/framebuffer_allocation", 2);
GLOBAL_DEF("rendering/quality/intended_usage/framebuffer_allocation.mobile", 3);
@@ -1145,7 +1251,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
if (audio_driver == "") { // specified in project.godot
- audio_driver = GLOBAL_DEF_RST("audio/driver", AudioDriverManager::get_driver(0)->get_name());
+ audio_driver = GLOBAL_DEF_RST_NOVAL("audio/driver", AudioDriverManager::get_driver(0)->get_name());
}
for (int i = 0; i < AudioDriverManager::get_driver_count(); i++) {
@@ -1180,10 +1286,15 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
Engine::get_singleton()->set_iterations_per_second(GLOBAL_DEF("physics/common/physics_fps", 60));
- ProjectSettings::get_singleton()->set_custom_property_info("physics/common/physics_fps", PropertyInfo(Variant::INT, "physics/common/physics_fps", PROPERTY_HINT_RANGE, "1,120,1,or_greater"));
+ ProjectSettings::get_singleton()->set_custom_property_info("physics/common/physics_fps",
+ PropertyInfo(Variant::INT, "physics/common/physics_fps",
+ PROPERTY_HINT_RANGE, "1,120,1,or_greater"));
Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5));
Engine::get_singleton()->set_target_fps(GLOBAL_DEF("debug/settings/fps/force_fps", 0));
- ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/fps/force_fps", PropertyInfo(Variant::INT, "debug/settings/fps/force_fps", PROPERTY_HINT_RANGE, "0,120,1,or_greater"));
+ ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/fps/force_fps",
+ PropertyInfo(Variant::INT,
+ "debug/settings/fps/force_fps",
+ PROPERTY_HINT_RANGE, "0,120,1,or_greater"));
GLOBAL_DEF("debug/settings/stdout/print_fps", false);
GLOBAL_DEF("debug/settings/stdout/verbose_stdout", false);
@@ -1194,12 +1305,21 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (frame_delay == 0) {
frame_delay = GLOBAL_DEF("application/run/frame_delay_msec", 0);
- ProjectSettings::get_singleton()->set_custom_property_info("application/run/frame_delay_msec", PropertyInfo(Variant::INT, "application/run/frame_delay_msec", PROPERTY_HINT_RANGE, "0,100,1,or_greater")); // No negative numbers
+ ProjectSettings::get_singleton()->set_custom_property_info("application/run/frame_delay_msec",
+ PropertyInfo(Variant::INT,
+ "application/run/frame_delay_msec",
+ PROPERTY_HINT_RANGE,
+ "0,100,1,or_greater")); // No negative numbers
}
OS::get_singleton()->set_low_processor_usage_mode(GLOBAL_DEF("application/run/low_processor_mode", false));
- OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(GLOBAL_DEF("application/run/low_processor_mode_sleep_usec", 6900)); // Roughly 144 FPS
- ProjectSettings::get_singleton()->set_custom_property_info("application/run/low_processor_mode_sleep_usec", PropertyInfo(Variant::INT, "application/run/low_processor_mode_sleep_usec", PROPERTY_HINT_RANGE, "0,33200,1,or_greater")); // No negative numbers
+ OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(
+ GLOBAL_DEF("application/run/low_processor_mode_sleep_usec", 6900)); // Roughly 144 FPS
+ ProjectSettings::get_singleton()->set_custom_property_info("application/run/low_processor_mode_sleep_usec",
+ PropertyInfo(Variant::INT,
+ "application/run/low_processor_mode_sleep_usec",
+ PROPERTY_HINT_RANGE,
+ "0,33200,1,or_greater")); // No negative numbers
GLOBAL_DEF("display/window/ios/hide_home_indicator", true);
@@ -1286,14 +1406,16 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
String rendering_driver; // temp broken
Error err;
- display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_flags, window_size, err);
+ display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_flags,
+ window_size, err);
if (err != OK) {
//ok i guess we can't use this display server, try other ones
for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {
if (i == display_driver_idx) {
continue; //don't try the same twice
}
- display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_flags, window_size, err);
+ display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_flags,
+ window_size, err);
if (err == OK) {
break;
}
@@ -1314,7 +1436,9 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
rendering_server = memnew(RenderingServerRaster);
if (OS::get_singleton()->get_render_thread_mode() != OS::RENDER_THREAD_UNSAFE) {
- rendering_server = memnew(RenderingServerWrapMT(rendering_server, OS::get_singleton()->get_render_thread_mode() == OS::RENDER_SEPARATE_THREAD));
+ rendering_server = memnew(RenderingServerWrapMT(rendering_server,
+ OS::get_singleton()->get_render_thread_mode() ==
+ OS::RENDER_SEPARATE_THREAD));
}
rendering_server->init();
@@ -1379,7 +1503,10 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
String boot_logo_path = GLOBAL_DEF("application/boot_splash/image", String());
bool boot_logo_scale = GLOBAL_DEF("application/boot_splash/fullsize", true);
bool boot_logo_filter = GLOBAL_DEF("application/boot_splash/use_filter", true);
- ProjectSettings::get_singleton()->set_custom_property_info("application/boot_splash/image", PropertyInfo(Variant::STRING, "application/boot_splash/image", PROPERTY_HINT_FILE, "*.png"));
+ ProjectSettings::get_singleton()->set_custom_property_info("application/boot_splash/image",
+ PropertyInfo(Variant::STRING,
+ "application/boot_splash/image",
+ PROPERTY_HINT_FILE, "*.png"));
Ref<Image> boot_logo;
@@ -1396,7 +1523,8 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
Color boot_bg_color = GLOBAL_DEF("application/boot_splash/bg_color", boot_splash_bg_color);
if (boot_logo.is_valid()) {
OS::get_singleton()->_msec_splash = OS::get_singleton()->get_ticks_msec();
- RenderingServer::get_singleton()->set_boot_image(boot_logo, boot_bg_color, boot_logo_scale, boot_logo_filter);
+ RenderingServer::get_singleton()->set_boot_image(boot_logo, boot_bg_color, boot_logo_scale,
+ boot_logo_filter);
} else {
#ifndef NO_DEFAULT_BOOT_LOGO
@@ -1421,21 +1549,31 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
}
MAIN_PRINT("Main: DCC");
- RenderingServer::get_singleton()->set_default_clear_color(GLOBAL_DEF("rendering/environment/default_clear_color", Color(0.3, 0.3, 0.3)));
+ RenderingServer::get_singleton()->set_default_clear_color(
+ GLOBAL_DEF("rendering/environment/default_clear_color", Color(0.3, 0.3, 0.3)));
MAIN_PRINT("Main: END");
GLOBAL_DEF("application/config/icon", String());
- ProjectSettings::get_singleton()->set_custom_property_info("application/config/icon", PropertyInfo(Variant::STRING, "application/config/icon", PROPERTY_HINT_FILE, "*.png,*.webp"));
+ ProjectSettings::get_singleton()->set_custom_property_info("application/config/icon",
+ PropertyInfo(Variant::STRING, "application/config/icon",
+ PROPERTY_HINT_FILE, "*.png,*.webp"));
GLOBAL_DEF("application/config/macos_native_icon", String());
- ProjectSettings::get_singleton()->set_custom_property_info("application/config/macos_native_icon", PropertyInfo(Variant::STRING, "application/config/macos_native_icon", PROPERTY_HINT_FILE, "*.icns"));
+ ProjectSettings::get_singleton()->set_custom_property_info("application/config/macos_native_icon",
+ PropertyInfo(Variant::STRING,
+ "application/config/macos_native_icon",
+ PROPERTY_HINT_FILE, "*.icns"));
GLOBAL_DEF("application/config/windows_native_icon", String());
- ProjectSettings::get_singleton()->set_custom_property_info("application/config/windows_native_icon", PropertyInfo(Variant::STRING, "application/config/windows_native_icon", PROPERTY_HINT_FILE, "*.ico"));
+ ProjectSettings::get_singleton()->set_custom_property_info("application/config/windows_native_icon",
+ PropertyInfo(Variant::STRING,
+ "application/config/windows_native_icon",
+ PROPERTY_HINT_FILE, "*.ico"));
Input *id = Input::get_singleton();
if (id) {
- if (bool(GLOBAL_DEF("input_devices/pointing/emulate_touch_from_mouse", false)) && !(editor || project_manager)) {
+ if (bool(GLOBAL_DEF("input_devices/pointing/emulate_touch_from_mouse", false)) &&
+ !(editor || project_manager)) {
bool found_touchscreen = false;
for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) {
if (DisplayServer::get_singleton()->screen_is_touchscreen(i)) {
@@ -1460,10 +1598,14 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
GLOBAL_DEF("display/mouse_cursor/custom_image", String());
GLOBAL_DEF("display/mouse_cursor/custom_image_hotspot", Vector2());
GLOBAL_DEF("display/mouse_cursor/tooltip_position_offset", Point2(10, 10));
- ProjectSettings::get_singleton()->set_custom_property_info("display/mouse_cursor/custom_image", PropertyInfo(Variant::STRING, "display/mouse_cursor/custom_image", PROPERTY_HINT_FILE, "*.png,*.webp"));
+ ProjectSettings::get_singleton()->set_custom_property_info("display/mouse_cursor/custom_image",
+ PropertyInfo(Variant::STRING,
+ "display/mouse_cursor/custom_image",
+ PROPERTY_HINT_FILE, "*.png,*.webp"));
if (String(ProjectSettings::get_singleton()->get("display/mouse_cursor/custom_image")) != String()) {
- Ref<Texture2D> cursor = ResourceLoader::load(ProjectSettings::get_singleton()->get("display/mouse_cursor/custom_image"));
+ Ref<Texture2D> cursor = ResourceLoader::load(
+ ProjectSettings::get_singleton()->get("display/mouse_cursor/custom_image"));
if (cursor.is_valid()) {
Vector2 hotspot = ProjectSettings::get_singleton()->get("display/mouse_cursor/custom_image_hotspot");
Input::get_singleton()->set_custom_mouse_cursor(cursor, Input::CURSOR_ARROW, hotspot);
@@ -1544,7 +1686,6 @@ bool Main::start() {
String positional_arg;
String game_path;
String script;
- String test;
bool check_only = false;
#ifdef TOOLS_ENABLED
@@ -1555,10 +1696,12 @@ bool Main::start() {
#endif
main_timer_sync.init(OS::get_singleton()->get_ticks_usec());
-
List<String> args = OS::get_singleton()->get_cmdline_args();
+
+ // parameters that do not have an argument to the right
for (int i = 0; i < args.size(); i++) {
- //parameters that do not have an argument to the right
+ // Doctest Unit Testing Handler
+ // Designed to override and pass arguments to the unit test handler.
if (args[i] == "--check-only") {
check_only = true;
#ifdef TOOLS_ENABLED
@@ -1591,8 +1734,6 @@ bool Main::start() {
bool parsed_pair = true;
if (args[i] == "-s" || args[i] == "--script") {
script = args[i + 1];
- } else if (args[i] == "--test") {
- test = args[i + 1];
#ifdef TOOLS_ENABLED
} else if (args[i] == "--doctool") {
doc_tool = args[i + 1];
@@ -1624,7 +1765,8 @@ bool Main::start() {
String main_loop_type;
#ifdef TOOLS_ENABLED
if (doc_tool != "") {
- Engine::get_singleton()->set_editor_hint(true); // Needed to instance editor-only classes for their default values
+ Engine::get_singleton()->set_editor_hint(
+ true); // Needed to instance editor-only classes for their default values
{
DirAccessRef da = DirAccess::open(doc_tool);
@@ -1716,16 +1858,7 @@ bool Main::start() {
main_loop = memnew(SceneTree);
};
- if (test != "") {
-#ifdef TOOLS_ENABLED
- main_loop = test_main(test, args);
-
- if (!main_loop) {
- return false;
- }
-#endif
-
- } else if (script != "") {
+ if (script != "") {
Ref<Script> script_res = ResourceLoader::load(script);
ERR_FAIL_COND_V_MSG(script_res.is_null(), false, "Can't load script: " + script);
@@ -1744,7 +1877,9 @@ bool Main::start() {
if (obj) {
memdelete(obj);
}
- ERR_FAIL_V_MSG(false, vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", script));
+ ERR_FAIL_V_MSG(false,
+ vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.",
+ script));
}
script_loop->set_init_script(script_res);
@@ -1832,7 +1967,9 @@ bool Main::start() {
Object *obj = ClassDB::instance(ibt);
- ERR_CONTINUE_MSG(obj == nullptr, "Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt));
+ ERR_CONTINUE_MSG(obj == nullptr,
+ "Cannot instance script for autoload, expected 'Node' inheritance, got: " +
+ String(ibt));
n = Object::cast_to<Node>(obj);
n->set_script(script_res);
@@ -1880,7 +2017,8 @@ bool Main::start() {
String stretch_mode = GLOBAL_DEF("display/window/stretch/mode", "disabled");
String stretch_aspect = GLOBAL_DEF("display/window/stretch/aspect", "ignore");
- Size2i stretch_size = Size2(GLOBAL_DEF("display/window/size/width", 0), GLOBAL_DEF("display/window/size/height", 0));
+ Size2i stretch_size = Size2(GLOBAL_DEF("display/window/size/width", 0),
+ GLOBAL_DEF("display/window/size/height", 0));
Window::ContentScaleMode cs_sm = Window::CONTENT_SCALE_MODE_DISABLED;
if (stretch_mode == "canvas_items") {
@@ -1924,10 +2062,14 @@ bool Main::start() {
int shadow_atlas_q3_subdiv = GLOBAL_GET("rendering/quality/shadow_atlas/quadrant_3_subdiv");
sml->get_root()->set_shadow_atlas_size(shadow_atlas_size);
- sml->get_root()->set_shadow_atlas_quadrant_subdiv(0, Viewport::ShadowAtlasQuadrantSubdiv(shadow_atlas_q0_subdiv));
- sml->get_root()->set_shadow_atlas_quadrant_subdiv(1, Viewport::ShadowAtlasQuadrantSubdiv(shadow_atlas_q1_subdiv));
- sml->get_root()->set_shadow_atlas_quadrant_subdiv(2, Viewport::ShadowAtlasQuadrantSubdiv(shadow_atlas_q2_subdiv));
- sml->get_root()->set_shadow_atlas_quadrant_subdiv(3, Viewport::ShadowAtlasQuadrantSubdiv(shadow_atlas_q3_subdiv));
+ sml->get_root()->set_shadow_atlas_quadrant_subdiv(0, Viewport::ShadowAtlasQuadrantSubdiv(
+ shadow_atlas_q0_subdiv));
+ sml->get_root()->set_shadow_atlas_quadrant_subdiv(1, Viewport::ShadowAtlasQuadrantSubdiv(
+ shadow_atlas_q1_subdiv));
+ sml->get_root()->set_shadow_atlas_quadrant_subdiv(2, Viewport::ShadowAtlasQuadrantSubdiv(
+ shadow_atlas_q2_subdiv));
+ sml->get_root()->set_shadow_atlas_quadrant_subdiv(3, Viewport::ShadowAtlasQuadrantSubdiv(
+ shadow_atlas_q3_subdiv));
bool snap_controls = GLOBAL_DEF("gui/common/snap_controls_to_pixels", true);
sml->get_root()->set_snap_controls_to_pixels(snap_controls);
@@ -1937,30 +2079,51 @@ bool Main::start() {
int texture_filter = GLOBAL_DEF("rendering/canvas_textures/default_texture_filter", 1);
int texture_repeat = GLOBAL_DEF("rendering/canvas_textures/default_texture_repeat", 0);
- sml->get_root()->set_default_canvas_item_texture_filter(Viewport::DefaultCanvasItemTextureFilter(texture_filter));
- sml->get_root()->set_default_canvas_item_texture_repeat(Viewport::DefaultCanvasItemTextureRepeat(texture_repeat));
+ sml->get_root()->set_default_canvas_item_texture_filter(
+ Viewport::DefaultCanvasItemTextureFilter(texture_filter));
+ sml->get_root()->set_default_canvas_item_texture_repeat(
+ Viewport::DefaultCanvasItemTextureRepeat(texture_repeat));
} else {
GLOBAL_DEF("display/window/stretch/mode", "disabled");
- ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/mode", PropertyInfo(Variant::STRING, "display/window/stretch/mode", PROPERTY_HINT_ENUM, "disabled,canvas_items,viewport"));
+ ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/mode",
+ PropertyInfo(Variant::STRING,
+ "display/window/stretch/mode",
+ PROPERTY_HINT_ENUM,
+ "disabled,canvas_items,viewport"));
GLOBAL_DEF("display/window/stretch/aspect", "ignore");
- ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/aspect", PropertyInfo(Variant::STRING, "display/window/stretch/aspect", PROPERTY_HINT_ENUM, "ignore,keep,keep_width,keep_height,expand"));
+ ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/aspect",
+ PropertyInfo(Variant::STRING,
+ "display/window/stretch/aspect",
+ PROPERTY_HINT_ENUM,
+ "ignore,keep,keep_width,keep_height,expand"));
GLOBAL_DEF("display/window/stretch/shrink", 1.0);
- ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/shrink", PropertyInfo(Variant::FLOAT, "display/window/stretch/shrink", PROPERTY_HINT_RANGE, "1.0,8.0,0.1"));
+ ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/shrink",
+ PropertyInfo(Variant::FLOAT,
+ "display/window/stretch/shrink",
+ PROPERTY_HINT_RANGE,
+ "1.0,8.0,0.1"));
sml->set_auto_accept_quit(GLOBAL_DEF("application/config/auto_accept_quit", true));
sml->set_quit_on_go_back(GLOBAL_DEF("application/config/quit_on_go_back", true));
GLOBAL_DEF("gui/common/snap_controls_to_pixels", true);
GLOBAL_DEF("rendering/quality/dynamic_fonts/use_oversampling", true);
GLOBAL_DEF("rendering/canvas_textures/default_texture_filter", 1);
- ProjectSettings::get_singleton()->set_custom_property_info("rendering/canvas_textures/default_texture_filter", PropertyInfo(Variant::INT, "rendering/canvas_textures/default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,MipmapLinear,MipmapNearest"));
+ ProjectSettings::get_singleton()->set_custom_property_info(
+ "rendering/canvas_textures/default_texture_filter",
+ PropertyInfo(Variant::INT, "rendering/canvas_textures/default_texture_filter", PROPERTY_HINT_ENUM,
+ "Nearest,Linear,MipmapLinear,MipmapNearest"));
GLOBAL_DEF("rendering/canvas_textures/default_texture_repeat", 0);
- ProjectSettings::get_singleton()->set_custom_property_info("rendering/canvas_textures/default_texture_repeat", PropertyInfo(Variant::INT, "rendering/canvas_textures/default_texture_repeat", PROPERTY_HINT_ENUM, "Disable,Enable,Mirror"));
+ ProjectSettings::get_singleton()->set_custom_property_info(
+ "rendering/canvas_textures/default_texture_repeat",
+ PropertyInfo(Variant::INT, "rendering/canvas_textures/default_texture_repeat", PROPERTY_HINT_ENUM,
+ "Disable,Enable,Mirror"));
}
#ifdef TOOLS_ENABLED
if (editor) {
- bool editor_embed_subwindows = EditorSettings::get_singleton()->get_setting("interface/editor/single_window_mode");
+ bool editor_embed_subwindows = EditorSettings::get_singleton()->get_setting(
+ "interface/editor/single_window_mode");
if (editor_embed_subwindows) {
sml->get_root()->set_embed_subwindows_hint(true);
@@ -1973,7 +2136,8 @@ bool Main::start() {
local_game_path = game_path.replace("\\", "/");
if (!local_game_path.begins_with("res://")) {
- bool absolute = (local_game_path.size() > 1) && (local_game_path[0] == '/' || local_game_path[1] == ':');
+ bool absolute =
+ (local_game_path.size() > 1) && (local_game_path[0] == '/' || local_game_path[1] == ':');
if (!absolute) {
if (ProjectSettings::get_singleton()->is_using_datapack()) {
@@ -1989,7 +2153,8 @@ bool Main::start() {
} else {
DirAccess *da = DirAccess::open(local_game_path.substr(0, sep));
if (da) {
- local_game_path = da->get_current_dir().plus_file(local_game_path.substr(sep + 1, local_game_path.length()));
+ local_game_path = da->get_current_dir().plus_file(
+ local_game_path.substr(sep + 1, local_game_path.length()));
memdelete(da);
}
}
@@ -2059,7 +2224,7 @@ bool Main::start() {
}
#ifdef TOOLS_ENABLED
- if (project_manager || (script == "" && test == "" && game_path == "" && !editor)) {
+ if (project_manager || (script == "" && game_path == "" && !editor)) {
Engine::get_singleton()->set_editor_hint(true);
ProjectManager *pmanager = memnew(ProjectManager);
ProgressDialog *progress_dialog = memnew(ProgressDialog);
@@ -2072,12 +2237,16 @@ bool Main::start() {
if (project_manager || editor) {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CONSOLE_WINDOW)) {
// Hide console window if requested (Windows-only).
- bool hide_console = EditorSettings::get_singleton()->get_setting("interface/editor/hide_console_window");
+ bool hide_console = EditorSettings::get_singleton()->get_setting(
+ "interface/editor/hide_console_window");
DisplayServer::get_singleton()->console_set_visible(!hide_console);
}
// Load SSL Certificates from Editor Settings (or builtin)
- Crypto::load_default_certificates(EditorSettings::get_singleton()->get_setting("network/ssl/editor_ssl_certificates").operator String());
+ Crypto::load_default_certificates(EditorSettings::get_singleton()->get_setting(
+ "network/ssl/editor_ssl_certificates")
+ .
+ operator String());
}
#endif
}
@@ -2107,6 +2276,7 @@ uint32_t Main::frames = 0;
uint32_t Main::frame = 0;
bool Main::force_redraw_requested = false;
int Main::iterating = 0;
+
bool Main::is_iterating() {
return iterating > 0;
}
@@ -2182,7 +2352,8 @@ bool Main::iteration() {
message_queue->flush();
- physics_process_ticks = MAX(physics_process_ticks, OS::get_singleton()->get_ticks_usec() - physics_begin); // keep the largest one for reference
+ physics_process_ticks = MAX(physics_process_ticks, OS::get_singleton()->get_ticks_usec() -
+ physics_begin); // keep the largest one for reference
physics_process_max = MAX(OS::get_singleton()->get_ticks_usec() - physics_begin, physics_process_max);
Engine::get_singleton()->_physics_frames++;
}
@@ -2198,7 +2369,8 @@ bool Main::iteration() {
RenderingServer::get_singleton()->sync(); //sync if still drawing from previous frames.
- if (DisplayServer::get_singleton()->can_any_window_draw() && RenderingServer::get_singleton()->is_render_loop_enabled()) {
+ if (DisplayServer::get_singleton()->can_any_window_draw() &&
+ RenderingServer::get_singleton()->is_render_loop_enabled()) {
if ((!force_redraw_requested) && OS::get_singleton()->is_in_low_processor_usage_mode()) {
if (RenderingServer::get_singleton()->has_changed()) {
RenderingServer::get_singleton()->draw(true, scaled_step); // flush visual commands
@@ -2260,10 +2432,12 @@ bool Main::iteration() {
auto_build_solutions = false;
// Only relevant when running the editor.
if (!editor) {
- ERR_FAIL_V_MSG(true, "Command line option --build-solutions was passed, but no project is being edited. Aborting.");
+ ERR_FAIL_V_MSG(true,
+ "Command line option --build-solutions was passed, but no project is being edited. Aborting.");
}
if (!EditorNode::get_singleton()->call_build()) {
- ERR_FAIL_V_MSG(true, "Command line option --build-solutions was passed, but the build callback failed. Aborting.");
+ ERR_FAIL_V_MSG(true,
+ "Command line option --build-solutions was passed, but the build callback failed. Aborting.");
}
}
#endif
diff --git a/main/main.h b/main/main.h
index 308128735c..20c0bebefa 100644
--- a/main/main.h
+++ b/main/main.h
@@ -45,7 +45,7 @@ class Main {
public:
static bool is_project_manager();
-
+ static int test_entrypoint(int argc, char *argv[], bool &tests_need_run);
static Error setup(const char *execpath, int argc, char *argv[], bool p_second_phase = true);
static Error setup2(Thread::ID p_main_tid_override = 0);
static bool start();
@@ -58,4 +58,19 @@ public:
static void cleanup();
};
+// Test main override is for the testing behaviour
+#define TEST_MAIN_OVERRIDE \
+ bool run_test = false; \
+ int return_code = Main::test_entrypoint(argc, argv, run_test); \
+ if (run_test) { \
+ return return_code; \
+ }
+
+#define TEST_MAIN_PARAM_OVERRIDE(argc, argv) \
+ bool run_test = false; \
+ int return_code = Main::test_entrypoint(argc, argv, run_test); \
+ if (run_test) { \
+ return return_code; \
+ }
+
#endif // MAIN_H
diff --git a/main/tests/test_gdscript.cpp b/main/tests/test_gdscript.cpp
deleted file mode 100644
index 10586c6495..0000000000
--- a/main/tests/test_gdscript.cpp
+++ /dev/null
@@ -1,1035 +0,0 @@
-/*************************************************************************/
-/* test_gdscript.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "test_gdscript.h"
-
-#include "core/os/file_access.h"
-#include "core/os/main_loop.h"
-#include "core/os/os.h"
-
-#include "modules/modules_enabled.gen.h"
-#ifdef MODULE_GDSCRIPT_ENABLED
-
-#include "modules/gdscript/gdscript.h"
-#include "modules/gdscript/gdscript_compiler.h"
-#include "modules/gdscript/gdscript_parser.h"
-#include "modules/gdscript/gdscript_tokenizer.h"
-
-namespace TestGDScript {
-
-static void _print_indent(int p_ident, const String &p_text) {
- String txt;
- for (int i = 0; i < p_ident; i++) {
- txt += '\t';
- }
-
- print_line(txt + p_text);
-}
-
-static String _parser_extends(const GDScriptParser::ClassNode *p_class) {
- String txt = "extends ";
- if (String(p_class->extends_file) != "") {
- txt += "\"" + p_class->extends_file + "\"";
- if (p_class->extends_class.size()) {
- txt += ".";
- }
- }
-
- for (int i = 0; i < p_class->extends_class.size(); i++) {
- if (i != 0) {
- txt += ".";
- }
-
- txt += p_class->extends_class[i];
- }
-
- return txt;
-}
-
-static String _parser_expr(const GDScriptParser::Node *p_expr) {
- String txt;
- switch (p_expr->type) {
- case GDScriptParser::Node::TYPE_IDENTIFIER: {
- const GDScriptParser::IdentifierNode *id_node = static_cast<const GDScriptParser::IdentifierNode *>(p_expr);
- txt = id_node->name;
- } break;
- case GDScriptParser::Node::TYPE_CONSTANT: {
- const GDScriptParser::ConstantNode *c_node = static_cast<const GDScriptParser::ConstantNode *>(p_expr);
- if (c_node->value.get_type() == Variant::STRING) {
- txt = "\"" + String(c_node->value) + "\"";
- } else {
- txt = c_node->value;
- }
-
- } break;
- case GDScriptParser::Node::TYPE_SELF: {
- txt = "self";
- } break;
- case GDScriptParser::Node::TYPE_ARRAY: {
- const GDScriptParser::ArrayNode *arr_node = static_cast<const GDScriptParser::ArrayNode *>(p_expr);
- txt += "[";
- for (int i = 0; i < arr_node->elements.size(); i++) {
- if (i > 0) {
- txt += ", ";
- }
- txt += _parser_expr(arr_node->elements[i]);
- }
- txt += "]";
- } break;
- case GDScriptParser::Node::TYPE_DICTIONARY: {
- const GDScriptParser::DictionaryNode *dict_node = static_cast<const GDScriptParser::DictionaryNode *>(p_expr);
- txt += "{";
- for (int i = 0; i < dict_node->elements.size(); i++) {
- if (i > 0) {
- txt += ", ";
- }
-
- const GDScriptParser::DictionaryNode::Pair &p = dict_node->elements[i];
- txt += _parser_expr(p.key);
- txt += ":";
- txt += _parser_expr(p.value);
- }
- txt += "}";
- } break;
- case GDScriptParser::Node::TYPE_OPERATOR: {
- const GDScriptParser::OperatorNode *c_node = static_cast<const GDScriptParser::OperatorNode *>(p_expr);
- switch (c_node->op) {
- case GDScriptParser::OperatorNode::OP_PARENT_CALL:
- txt += ".";
- [[fallthrough]];
- case GDScriptParser::OperatorNode::OP_CALL: {
- ERR_FAIL_COND_V(c_node->arguments.size() < 1, "");
- String func_name;
- const GDScriptParser::Node *nfunc = c_node->arguments[0];
- int arg_ofs = 0;
- if (nfunc->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
- const GDScriptParser::BuiltInFunctionNode *bif_node = static_cast<const GDScriptParser::BuiltInFunctionNode *>(nfunc);
- func_name = GDScriptFunctions::get_func_name(bif_node->function);
- arg_ofs = 1;
- } else if (nfunc->type == GDScriptParser::Node::TYPE_TYPE) {
- const GDScriptParser::TypeNode *t_node = static_cast<const GDScriptParser::TypeNode *>(nfunc);
- func_name = Variant::get_type_name(t_node->vtype);
- arg_ofs = 1;
- } else {
- ERR_FAIL_COND_V(c_node->arguments.size() < 2, "");
- nfunc = c_node->arguments[1];
- ERR_FAIL_COND_V(nfunc->type != GDScriptParser::Node::TYPE_IDENTIFIER, "");
-
- if (c_node->arguments[0]->type != GDScriptParser::Node::TYPE_SELF) {
- func_name = _parser_expr(c_node->arguments[0]) + ".";
- }
-
- func_name += _parser_expr(nfunc);
- arg_ofs = 2;
- }
-
- txt += func_name + "(";
-
- for (int i = arg_ofs; i < c_node->arguments.size(); i++) {
- const GDScriptParser::Node *arg = c_node->arguments[i];
- if (i > arg_ofs) {
- txt += ", ";
- }
- txt += _parser_expr(arg);
- }
-
- txt += ")";
-
- } break;
- case GDScriptParser::OperatorNode::OP_INDEX: {
- ERR_FAIL_COND_V(c_node->arguments.size() != 2, "");
-
- //index with []
- txt = _parser_expr(c_node->arguments[0]) + "[" + _parser_expr(c_node->arguments[1]) + "]";
-
- } break;
- case GDScriptParser::OperatorNode::OP_INDEX_NAMED: {
- ERR_FAIL_COND_V(c_node->arguments.size() != 2, "");
-
- txt = _parser_expr(c_node->arguments[0]) + "." + _parser_expr(c_node->arguments[1]);
-
- } break;
- case GDScriptParser::OperatorNode::OP_NEG: {
- txt = "-" + _parser_expr(c_node->arguments[0]);
- } break;
- case GDScriptParser::OperatorNode::OP_NOT: {
- txt = "not " + _parser_expr(c_node->arguments[0]);
- } break;
- case GDScriptParser::OperatorNode::OP_BIT_INVERT: {
- txt = "~" + _parser_expr(c_node->arguments[0]);
- } break;
- case GDScriptParser::OperatorNode::OP_IN: {
- txt = _parser_expr(c_node->arguments[0]) + " in " + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_EQUAL: {
- txt = _parser_expr(c_node->arguments[0]) + "==" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_NOT_EQUAL: {
- txt = _parser_expr(c_node->arguments[0]) + "!=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_LESS: {
- txt = _parser_expr(c_node->arguments[0]) + "<" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_LESS_EQUAL: {
- txt = _parser_expr(c_node->arguments[0]) + "<=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_GREATER: {
- txt = _parser_expr(c_node->arguments[0]) + ">" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_GREATER_EQUAL: {
- txt = _parser_expr(c_node->arguments[0]) + ">=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_AND: {
- txt = _parser_expr(c_node->arguments[0]) + " and " + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_OR: {
- txt = _parser_expr(c_node->arguments[0]) + " or " + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ADD: {
- txt = _parser_expr(c_node->arguments[0]) + "+" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_SUB: {
- txt = _parser_expr(c_node->arguments[0]) + "-" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_MUL: {
- txt = _parser_expr(c_node->arguments[0]) + "*" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_DIV: {
- txt = _parser_expr(c_node->arguments[0]) + "/" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_MOD: {
- txt = _parser_expr(c_node->arguments[0]) + "%" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: {
- txt = _parser_expr(c_node->arguments[0]) + "<<" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: {
- txt = _parser_expr(c_node->arguments[0]) + ">>" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ASSIGN: {
- txt = _parser_expr(c_node->arguments[0]) + "=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_ADD: {
- txt = _parser_expr(c_node->arguments[0]) + "+=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_SUB: {
- txt = _parser_expr(c_node->arguments[0]) + "-=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_MUL: {
- txt = _parser_expr(c_node->arguments[0]) + "*=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_DIV: {
- txt = _parser_expr(c_node->arguments[0]) + "/=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_MOD: {
- txt = _parser_expr(c_node->arguments[0]) + "%=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT: {
- txt = _parser_expr(c_node->arguments[0]) + "<<=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT: {
- txt = _parser_expr(c_node->arguments[0]) + ">>=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_AND: {
- txt = _parser_expr(c_node->arguments[0]) + "&=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_OR: {
- txt = _parser_expr(c_node->arguments[0]) + "|=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_XOR: {
- txt = _parser_expr(c_node->arguments[0]) + "^=" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_BIT_AND: {
- txt = _parser_expr(c_node->arguments[0]) + "&" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_BIT_OR: {
- txt = _parser_expr(c_node->arguments[0]) + "|" + _parser_expr(c_node->arguments[1]);
- } break;
- case GDScriptParser::OperatorNode::OP_BIT_XOR: {
- txt = _parser_expr(c_node->arguments[0]) + "^" + _parser_expr(c_node->arguments[1]);
- } break;
- default: {
- }
- }
-
- } break;
- case GDScriptParser::Node::TYPE_CAST: {
- const GDScriptParser::CastNode *cast_node = static_cast<const GDScriptParser::CastNode *>(p_expr);
- txt = _parser_expr(cast_node->source_node) + " as " + cast_node->cast_type.to_string();
-
- } break;
- case GDScriptParser::Node::TYPE_NEWLINE: {
- //skippie
- } break;
- default: {
- ERR_FAIL_V_MSG("", "Parser bug at " + itos(p_expr->line) + ", invalid expression type: " + itos(p_expr->type));
- }
- }
-
- return txt;
-}
-
-static void _parser_show_block(const GDScriptParser::BlockNode *p_block, int p_indent) {
- for (int i = 0; i < p_block->statements.size(); i++) {
- const GDScriptParser::Node *statement = p_block->statements[i];
-
- switch (statement->type) {
- case GDScriptParser::Node::TYPE_CONTROL_FLOW: {
- const GDScriptParser::ControlFlowNode *cf_node = static_cast<const GDScriptParser::ControlFlowNode *>(statement);
- switch (cf_node->cf_type) {
- case GDScriptParser::ControlFlowNode::CF_IF: {
- ERR_FAIL_COND(cf_node->arguments.size() != 1);
- String txt;
- txt += "if ";
- txt += _parser_expr(cf_node->arguments[0]);
- txt += ":";
- _print_indent(p_indent, txt);
- ERR_FAIL_COND(!cf_node->body);
- _parser_show_block(cf_node->body, p_indent + 1);
- if (cf_node->body_else) {
- _print_indent(p_indent, "else:");
- _parser_show_block(cf_node->body_else, p_indent + 1);
- }
-
- } break;
- case GDScriptParser::ControlFlowNode::CF_FOR: {
- ERR_FAIL_COND(cf_node->arguments.size() != 2);
- String txt;
- txt += "for ";
- txt += _parser_expr(cf_node->arguments[0]);
- txt += " in ";
- txt += _parser_expr(cf_node->arguments[1]);
- txt += ":";
- _print_indent(p_indent, txt);
- ERR_FAIL_COND(!cf_node->body);
- _parser_show_block(cf_node->body, p_indent + 1);
-
- } break;
- case GDScriptParser::ControlFlowNode::CF_WHILE: {
- ERR_FAIL_COND(cf_node->arguments.size() != 1);
- String txt;
- txt += "while ";
- txt += _parser_expr(cf_node->arguments[0]);
- txt += ":";
- _print_indent(p_indent, txt);
- ERR_FAIL_COND(!cf_node->body);
- _parser_show_block(cf_node->body, p_indent + 1);
-
- } break;
- case GDScriptParser::ControlFlowNode::CF_MATCH: {
- // FIXME: Implement
- } break;
- case GDScriptParser::ControlFlowNode::CF_CONTINUE: {
- _print_indent(p_indent, "continue");
- } break;
- case GDScriptParser::ControlFlowNode::CF_BREAK: {
- _print_indent(p_indent, "break");
- } break;
- case GDScriptParser::ControlFlowNode::CF_RETURN: {
- if (cf_node->arguments.size()) {
- _print_indent(p_indent, "return " + _parser_expr(cf_node->arguments[0]));
- } else {
- _print_indent(p_indent, "return ");
- }
- } break;
- }
-
- } break;
- case GDScriptParser::Node::TYPE_LOCAL_VAR: {
- const GDScriptParser::LocalVarNode *lv_node = static_cast<const GDScriptParser::LocalVarNode *>(statement);
- _print_indent(p_indent, "var " + String(lv_node->name));
- } break;
- default: {
- //expression i guess
- _print_indent(p_indent, _parser_expr(statement));
- }
- }
- }
-}
-
-static void _parser_show_function(const GDScriptParser::FunctionNode *p_func, int p_indent, GDScriptParser::BlockNode *p_initializer = nullptr) {
- String txt;
- if (p_func->_static) {
- txt = "static ";
- }
- txt += "func ";
- if (p_func->name == "") { // initializer
- txt += "[built-in-initializer]";
- } else {
- txt += String(p_func->name);
- }
- txt += "(";
-
- for (int i = 0; i < p_func->arguments.size(); i++) {
- if (i != 0) {
- txt += ", ";
- }
- txt += "var " + String(p_func->arguments[i]);
- if (i >= (p_func->arguments.size() - p_func->default_values.size())) {
- int defarg = i - (p_func->arguments.size() - p_func->default_values.size());
- txt += "=";
- txt += _parser_expr(p_func->default_values[defarg]);
- }
- }
-
- txt += ")";
-
- //todo constructor check!
-
- txt += ":";
-
- _print_indent(p_indent, txt);
- if (p_initializer) {
- _parser_show_block(p_initializer, p_indent + 1);
- }
- _parser_show_block(p_func->body, p_indent + 1);
-}
-
-static void _parser_show_class(const GDScriptParser::ClassNode *p_class, int p_indent, const Vector<String> &p_code) {
- if (p_indent == 0 && (String(p_class->extends_file) != "" || p_class->extends_class.size())) {
- _print_indent(p_indent, _parser_extends(p_class));
- print_line("\n");
- }
-
- for (int i = 0; i < p_class->subclasses.size(); i++) {
- const GDScriptParser::ClassNode *subclass = p_class->subclasses[i];
- String line = "class " + subclass->name;
- if (String(subclass->extends_file) != "" || subclass->extends_class.size()) {
- line += " " + _parser_extends(subclass);
- }
- line += ":";
- _print_indent(p_indent, line);
- _parser_show_class(subclass, p_indent + 1, p_code);
- print_line("\n");
- }
-
- for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
- const GDScriptParser::ClassNode::Constant &constant = E->get();
- _print_indent(p_indent, "const " + String(E->key()) + "=" + _parser_expr(constant.expression));
- }
-
- for (int i = 0; i < p_class->variables.size(); i++) {
- const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
-
- _print_indent(p_indent, "var " + String(m.identifier));
- }
-
- print_line("\n");
-
- for (int i = 0; i < p_class->static_functions.size(); i++) {
- _parser_show_function(p_class->static_functions[i], p_indent);
- print_line("\n");
- }
-
- for (int i = 0; i < p_class->functions.size(); i++) {
- if (String(p_class->functions[i]->name) == "_init") {
- _parser_show_function(p_class->functions[i], p_indent, p_class->initializer);
- } else {
- _parser_show_function(p_class->functions[i], p_indent);
- }
- print_line("\n");
- }
- //_parser_show_function(p_class->initializer,p_indent);
- print_line("\n");
-}
-
-static String _disassemble_addr(const Ref<GDScript> &p_script, const GDScriptFunction &func, int p_addr) {
- int addr = p_addr & GDScriptFunction::ADDR_MASK;
-
- switch (p_addr >> GDScriptFunction::ADDR_BITS) {
- case GDScriptFunction::ADDR_TYPE_SELF: {
- return "self";
- } break;
- case GDScriptFunction::ADDR_TYPE_CLASS: {
- return "class";
- } break;
- case GDScriptFunction::ADDR_TYPE_MEMBER: {
- return "member(" + p_script->debug_get_member_by_index(addr) + ")";
- } break;
- case GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT: {
- return "class_const(" + func.get_global_name(addr) + ")";
- } break;
- case GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT: {
- Variant v = func.get_constant(addr);
- String txt;
- if (v.get_type() == Variant::STRING || v.get_type() == Variant::NODE_PATH) {
- txt = "\"" + String(v) + "\"";
- } else {
- txt = v;
- }
- return "const(" + txt + ")";
- } break;
- case GDScriptFunction::ADDR_TYPE_STACK: {
- return "stack(" + itos(addr) + ")";
- } break;
- case GDScriptFunction::ADDR_TYPE_STACK_VARIABLE: {
- return "var_stack(" + itos(addr) + ")";
- } break;
- case GDScriptFunction::ADDR_TYPE_GLOBAL: {
- return "global(" + func.get_global_name(addr) + ")";
- } break;
- case GDScriptFunction::ADDR_TYPE_NIL: {
- return "nil";
- } break;
- }
-
- return "<err>";
-}
-
-static void _disassemble_class(const Ref<GDScript> &p_class, const Vector<String> &p_code) {
- const Map<StringName, GDScriptFunction *> &mf = p_class->debug_get_member_functions();
-
- for (const Map<StringName, GDScriptFunction *>::Element *E = mf.front(); E; E = E->next()) {
- const GDScriptFunction &func = *E->get();
- const int *code = func.get_code();
- int codelen = func.get_code_size();
- String defargs;
- if (func.get_default_argument_count()) {
- defargs = "defarg at: ";
- for (int i = 0; i < func.get_default_argument_count(); i++) {
- if (i > 0) {
- defargs += ",";
- }
- defargs += itos(func.get_default_argument_addr(i));
- }
- defargs += " ";
- }
- print_line("== function " + String(func.get_name()) + "() :: stack size: " + itos(func.get_max_stack_size()) + " " + defargs + "==");
-
-#define DADDR(m_ip) (_disassemble_addr(p_class, func, code[ip + m_ip]))
-
- for (int ip = 0; ip < codelen;) {
- int incr = 0;
- String txt = itos(ip) + " ";
-
- switch (code[ip]) {
- case GDScriptFunction::OPCODE_OPERATOR: {
- int op = code[ip + 1];
- txt += " op ";
-
- String opname = Variant::get_operator_name(Variant::Operator(op));
-
- txt += DADDR(4);
- txt += " = ";
- txt += DADDR(2);
- txt += " " + opname + " ";
- txt += DADDR(3);
- incr += 5;
-
- } break;
- case GDScriptFunction::OPCODE_SET: {
- txt += "set ";
- txt += DADDR(1);
- txt += "[";
- txt += DADDR(2);
- txt += "]=";
- txt += DADDR(3);
- incr += 4;
-
- } break;
- case GDScriptFunction::OPCODE_GET: {
- txt += " get ";
- txt += DADDR(3);
- txt += "=";
- txt += DADDR(1);
- txt += "[";
- txt += DADDR(2);
- txt += "]";
- incr += 4;
-
- } break;
- case GDScriptFunction::OPCODE_SET_NAMED: {
- txt += " set_named ";
- txt += DADDR(1);
- txt += "[\"";
- txt += func.get_global_name(code[ip + 2]);
- txt += "\"]=";
- txt += DADDR(3);
- incr += 4;
-
- } break;
- case GDScriptFunction::OPCODE_GET_NAMED: {
- txt += " get_named ";
- txt += DADDR(3);
- txt += "=";
- txt += DADDR(1);
- txt += "[\"";
- txt += func.get_global_name(code[ip + 2]);
- txt += "\"]";
- incr += 4;
-
- } break;
- case GDScriptFunction::OPCODE_SET_MEMBER: {
- txt += " set_member ";
- txt += "[\"";
- txt += func.get_global_name(code[ip + 1]);
- txt += "\"]=";
- txt += DADDR(2);
- incr += 3;
-
- } break;
- case GDScriptFunction::OPCODE_GET_MEMBER: {
- txt += " get_member ";
- txt += DADDR(2);
- txt += "=";
- txt += "[\"";
- txt += func.get_global_name(code[ip + 1]);
- txt += "\"]";
- incr += 3;
-
- } break;
- case GDScriptFunction::OPCODE_ASSIGN: {
- txt += " assign ";
- txt += DADDR(1);
- txt += "=";
- txt += DADDR(2);
- incr += 3;
-
- } break;
- case GDScriptFunction::OPCODE_ASSIGN_TRUE: {
- txt += " assign ";
- txt += DADDR(1);
- txt += "= true";
- incr += 2;
-
- } break;
- case GDScriptFunction::OPCODE_ASSIGN_FALSE: {
- txt += " assign ";
- txt += DADDR(1);
- txt += "= false";
- incr += 2;
-
- } break;
- case GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN: {
- txt += " assign typed builtin (";
- txt += Variant::get_type_name((Variant::Type)code[ip + 1]);
- txt += ") ";
- txt += DADDR(2);
- txt += " = ";
- txt += DADDR(3);
- incr += 4;
-
- } break;
- case GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE: {
- Variant className = func.get_constant(code[ip + 1]);
- GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(className.operator Object *());
-
- txt += " assign typed native (";
- txt += nc->get_name().operator String();
- txt += ") ";
- txt += DADDR(2);
- txt += " = ";
- txt += DADDR(3);
- incr += 4;
-
- } break;
- case GDScriptFunction::OPCODE_CAST_TO_SCRIPT: {
- txt += " cast ";
- txt += DADDR(3);
- txt += "=";
- txt += DADDR(1);
- txt += " as ";
- txt += DADDR(2);
- incr += 4;
-
- } break;
- case GDScriptFunction::OPCODE_CONSTRUCT: {
- Variant::Type t = Variant::Type(code[ip + 1]);
- int argc = code[ip + 2];
-
- txt += " construct ";
- txt += DADDR(3 + argc);
- txt += " = ";
-
- txt += Variant::get_type_name(t) + "(";
- for (int i = 0; i < argc; i++) {
- if (i > 0) {
- txt += ", ";
- }
- txt += DADDR(i + 3);
- }
- txt += ")";
-
- incr = 4 + argc;
-
- } break;
- case GDScriptFunction::OPCODE_CONSTRUCT_ARRAY: {
- int argc = code[ip + 1];
- txt += " make_array ";
- txt += DADDR(2 + argc);
- txt += " = [ ";
-
- for (int i = 0; i < argc; i++) {
- if (i > 0) {
- txt += ", ";
- }
- txt += DADDR(2 + i);
- }
-
- txt += "]";
-
- incr += 3 + argc;
-
- } break;
- case GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY: {
- int argc = code[ip + 1];
- txt += " make_dict ";
- txt += DADDR(2 + argc * 2);
- txt += " = { ";
-
- for (int i = 0; i < argc; i++) {
- if (i > 0) {
- txt += ", ";
- }
- txt += DADDR(2 + i * 2 + 0);
- txt += ":";
- txt += DADDR(2 + i * 2 + 1);
- }
-
- txt += "}";
-
- incr += 3 + argc * 2;
-
- } break;
-
- case GDScriptFunction::OPCODE_CALL:
- case GDScriptFunction::OPCODE_CALL_RETURN: {
- bool ret = code[ip] == GDScriptFunction::OPCODE_CALL_RETURN;
-
- if (ret) {
- txt += " call-ret ";
- } else {
- txt += " call ";
- }
-
- int argc = code[ip + 1];
- if (ret) {
- txt += DADDR(4 + argc) + "=";
- }
-
- txt += DADDR(2) + ".";
- txt += String(func.get_global_name(code[ip + 3]));
- txt += "(";
-
- for (int i = 0; i < argc; i++) {
- if (i > 0) {
- txt += ", ";
- }
- txt += DADDR(4 + i);
- }
- txt += ")";
-
- incr = 5 + argc;
-
- } break;
- case GDScriptFunction::OPCODE_CALL_BUILT_IN: {
- txt += " call-built-in ";
-
- int argc = code[ip + 2];
- txt += DADDR(3 + argc) + "=";
-
- txt += GDScriptFunctions::get_func_name(GDScriptFunctions::Function(code[ip + 1]));
- txt += "(";
-
- for (int i = 0; i < argc; i++) {
- if (i > 0) {
- txt += ", ";
- }
- txt += DADDR(3 + i);
- }
- txt += ")";
-
- incr = 4 + argc;
-
- } break;
- case GDScriptFunction::OPCODE_CALL_SELF_BASE: {
- txt += " call-self-base ";
-
- int argc = code[ip + 2];
- txt += DADDR(3 + argc) + "=";
-
- txt += func.get_global_name(code[ip + 1]);
- txt += "(";
-
- for (int i = 0; i < argc; i++) {
- if (i > 0) {
- txt += ", ";
- }
- txt += DADDR(3 + i);
- }
- txt += ")";
-
- incr = 4 + argc;
-
- } break;
- case GDScriptFunction::OPCODE_YIELD: {
- txt += " yield ";
- incr = 1;
-
- } break;
- case GDScriptFunction::OPCODE_YIELD_SIGNAL: {
- txt += " yield_signal ";
- txt += DADDR(1);
- txt += ",";
- txt += DADDR(2);
- incr = 3;
- } break;
- case GDScriptFunction::OPCODE_YIELD_RESUME: {
- txt += " yield resume: ";
- txt += DADDR(1);
- incr = 2;
- } break;
- case GDScriptFunction::OPCODE_JUMP: {
- txt += " jump ";
- txt += itos(code[ip + 1]);
-
- incr = 2;
-
- } break;
- case GDScriptFunction::OPCODE_JUMP_IF: {
- txt += " jump-if ";
- txt += DADDR(1);
- txt += " to ";
- txt += itos(code[ip + 2]);
-
- incr = 3;
- } break;
- case GDScriptFunction::OPCODE_JUMP_IF_NOT: {
- txt += " jump-if-not ";
- txt += DADDR(1);
- txt += " to ";
- txt += itos(code[ip + 2]);
-
- incr = 3;
- } break;
- case GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT: {
- txt += " jump-to-default-argument ";
- incr = 1;
- } break;
- case GDScriptFunction::OPCODE_RETURN: {
- txt += " return ";
- txt += DADDR(1);
-
- incr = 2;
-
- } break;
- case GDScriptFunction::OPCODE_ITERATE_BEGIN: {
- txt += " for-init " + DADDR(4) + " in " + DADDR(2) + " counter " + DADDR(1) + " end " + itos(code[ip + 3]);
- incr += 5;
-
- } break;
- case GDScriptFunction::OPCODE_ITERATE: {
- txt += " for-loop " + DADDR(4) + " in " + DADDR(2) + " counter " + DADDR(1) + " end " + itos(code[ip + 3]);
- incr += 5;
-
- } break;
- case GDScriptFunction::OPCODE_LINE: {
- int line = code[ip + 1] - 1;
- if (line >= 0 && line < p_code.size()) {
- txt = "\n" + itos(line + 1) + ": " + p_code[line] + "\n";
- } else {
- txt = "";
- }
- incr += 2;
- } break;
- case GDScriptFunction::OPCODE_END: {
- txt += " end";
- incr += 1;
- } break;
- case GDScriptFunction::OPCODE_ASSERT: {
- txt += " assert ";
- txt += DADDR(1);
- incr += 2;
-
- } break;
- }
-
- if (incr == 0) {
- ERR_BREAK_MSG(true, "Unhandled opcode: " + itos(code[ip]));
- }
-
- ip += incr;
- if (txt != "") {
- print_line(txt);
- }
- }
- }
-}
-
-MainLoop *test(TestType p_type) {
- List<String> cmdlargs = OS::get_singleton()->get_cmdline_args();
-
- if (cmdlargs.empty()) {
- return nullptr;
- }
-
- String test = cmdlargs.back()->get();
- if (!test.ends_with(".gd") && !test.ends_with(".gdc")) {
- print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test);
- return nullptr;
- }
-
- FileAccess *fa = FileAccess::open(test, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(!fa, nullptr, "Could not open file: " + test);
-
- Vector<uint8_t> buf;
- int flen = fa->get_len();
- buf.resize(fa->get_len() + 1);
- fa->get_buffer(buf.ptrw(), flen);
- buf.write[flen] = 0;
-
- String code;
- code.parse_utf8((const char *)&buf[0]);
-
- Vector<String> lines;
- int last = 0;
-
- for (int i = 0; i <= code.length(); i++) {
- if (code[i] == '\n' || code[i] == 0) {
- lines.push_back(code.substr(last, i - last));
- last = i + 1;
- }
- }
-
- if (p_type == TEST_TOKENIZER) {
- GDScriptTokenizerText tk;
- tk.set_code(code);
- int line = -1;
- while (tk.get_token() != GDScriptTokenizer::TK_EOF) {
- String text;
- if (tk.get_token() == GDScriptTokenizer::TK_IDENTIFIER) {
- text = "'" + tk.get_token_identifier() + "' (identifier)";
- } else if (tk.get_token() == GDScriptTokenizer::TK_CONSTANT) {
- const Variant &c = tk.get_token_constant();
- if (c.get_type() == Variant::STRING) {
- text = "\"" + String(c) + "\"";
- } else {
- text = c;
- }
-
- text = text + " (" + Variant::get_type_name(c.get_type()) + " constant)";
- } else if (tk.get_token() == GDScriptTokenizer::TK_ERROR) {
- text = "ERROR: " + tk.get_token_error();
- } else if (tk.get_token() == GDScriptTokenizer::TK_NEWLINE) {
- text = "newline (" + itos(tk.get_token_line()) + ") + indent: " + itos(tk.get_token_line_indent());
- } else if (tk.get_token() == GDScriptTokenizer::TK_BUILT_IN_FUNC) {
- text = "'" + String(GDScriptFunctions::get_func_name(tk.get_token_built_in_func())) + "' (built-in function)";
- } else {
- text = tk.get_token_name(tk.get_token());
- }
-
- if (tk.get_token_line() != line) {
- int from = line + 1;
- line = tk.get_token_line();
-
- for (int i = from; i <= line; i++) {
- int l = i - 1;
- if (l >= 0 && l < lines.size()) {
- print_line("\n" + itos(i) + ": " + lines[l] + "\n");
- }
- }
- }
- print_line("\t(" + itos(tk.get_token_column()) + "): " + text);
- tk.advance();
- }
- }
-
- if (p_type == TEST_PARSER) {
- GDScriptParser parser;
- Error err = parser.parse(code);
- if (err) {
- print_line("Parse Error:\n" + itos(parser.get_error_line()) + ":" + itos(parser.get_error_column()) + ":" + parser.get_error());
- memdelete(fa);
- return nullptr;
- }
-
- const GDScriptParser::Node *root = parser.get_parse_tree();
- ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, nullptr);
- const GDScriptParser::ClassNode *cnode = static_cast<const GDScriptParser::ClassNode *>(root);
-
- _parser_show_class(cnode, 0, lines);
- }
-
- if (p_type == TEST_COMPILER) {
- GDScriptParser parser;
-
- Error err = parser.parse(code);
- if (err) {
- print_line("Parse Error:\n" + itos(parser.get_error_line()) + ":" + itos(parser.get_error_column()) + ":" + parser.get_error());
- memdelete(fa);
- return nullptr;
- }
-
- Ref<GDScript> gds;
- gds.instance();
-
- GDScriptCompiler gdc;
- err = gdc.compile(&parser, gds.ptr());
- if (err) {
- print_line("Compile Error:\n" + itos(gdc.get_error_line()) + ":" + itos(gdc.get_error_column()) + ":" + gdc.get_error());
- return nullptr;
- }
-
- Ref<GDScript> current = gds;
-
- while (current.is_valid()) {
- print_line("** CLASS **");
- _disassemble_class(current, lines);
-
- current = current->get_base();
- }
-
- } else if (p_type == TEST_BYTECODE) {
- Vector<uint8_t> buf2 = GDScriptTokenizerBuffer::parse_code_string(code);
- String dst = test.get_basename() + ".gdc";
- FileAccess *fw = FileAccess::open(dst, FileAccess::WRITE);
- fw->store_buffer(buf2.ptr(), buf2.size());
- memdelete(fw);
- }
-
- memdelete(fa);
-
- return nullptr;
-}
-
-} // namespace TestGDScript
-
-#else
-
-namespace TestGDScript {
-
-MainLoop *test(TestType p_type) {
- ERR_PRINT("The GDScript module is disabled, therefore GDScript tests cannot be used.");
- return nullptr;
-}
-
-} // namespace TestGDScript
-
-#endif
diff --git a/main/tests/test_string.cpp b/main/tests/test_string.cpp
deleted file mode 100644
index 73d59b0088..0000000000
--- a/main/tests/test_string.cpp
+++ /dev/null
@@ -1,1207 +0,0 @@
-/*************************************************************************/
-/* test_string.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "test_string.h"
-
-#include "core/io/ip_address.h"
-#include "core/os/os.h"
-#include "core/ustring.h"
-
-#include "modules/modules_enabled.gen.h"
-#ifdef MODULE_REGEX_ENABLED
-#include "modules/regex/regex.h"
-#endif
-
-#include <stdio.h>
-#include <wchar.h>
-
-namespace TestString {
-
-bool test_1() {
- OS::get_singleton()->print("\n\nTest 1: Assign from cstr\n");
-
- String s = "Hello";
-
- OS::get_singleton()->print("\tExpected: Hello\n");
- OS::get_singleton()->print("\tResulted: %ls\n", s.c_str());
-
- return (wcscmp(s.c_str(), L"Hello") == 0);
-}
-
-bool test_2() {
- OS::get_singleton()->print("\n\nTest 2: Assign from string (operator=)\n");
-
- String s = "Dolly";
- const String &t = s;
-
- OS::get_singleton()->print("\tExpected: Dolly\n");
- OS::get_singleton()->print("\tResulted: %ls\n", t.c_str());
-
- return (wcscmp(t.c_str(), L"Dolly") == 0);
-}
-
-bool test_3() {
- OS::get_singleton()->print("\n\nTest 3: Assign from c-string (copycon)\n");
-
- String s("Sheep");
- const String &t(s);
-
- OS::get_singleton()->print("\tExpected: Sheep\n");
- OS::get_singleton()->print("\tResulted: %ls\n", t.c_str());
-
- return (wcscmp(t.c_str(), L"Sheep") == 0);
-}
-
-bool test_4() {
- OS::get_singleton()->print("\n\nTest 4: Assign from c-widechar (operator=)\n");
-
- String s(L"Give me");
-
- OS::get_singleton()->print("\tExpected: Give me\n");
- OS::get_singleton()->print("\tResulted: %ls\n", s.c_str());
-
- return (wcscmp(s.c_str(), L"Give me") == 0);
-}
-
-bool test_5() {
- OS::get_singleton()->print("\n\nTest 5: Assign from c-widechar (copycon)\n");
-
- String s(L"Wool");
-
- OS::get_singleton()->print("\tExpected: Wool\n");
- OS::get_singleton()->print("\tResulted: %ls\n", s.c_str());
-
- return (wcscmp(s.c_str(), L"Wool") == 0);
-}
-
-bool test_6() {
- OS::get_singleton()->print("\n\nTest 6: comparisons (equal)\n");
-
- String s = "Test Compare";
-
- OS::get_singleton()->print("\tComparing to \"Test Compare\"\n");
-
- if (!(s == "Test Compare")) {
- return false;
- }
-
- if (!(s == L"Test Compare")) {
- return false;
- }
-
- if (!(s == String("Test Compare"))) {
- return false;
- }
-
- return true;
-}
-
-bool test_7() {
- OS::get_singleton()->print("\n\nTest 7: comparisons (unequal)\n");
-
- String s = "Test Compare";
-
- OS::get_singleton()->print("\tComparing to \"Test Compare\"\n");
-
- if (!(s != "Peanut")) {
- return false;
- }
-
- if (!(s != L"Coconut")) {
- return false;
- }
-
- if (!(s != String("Butter"))) {
- return false;
- }
-
- return true;
-}
-
-bool test_8() {
- OS::get_singleton()->print("\n\nTest 8: comparisons (operator<)\n");
-
- String s = "Bees";
-
- OS::get_singleton()->print("\tComparing to \"Bees\"\n");
-
- if (!(s < "Elephant")) {
- return false;
- }
-
- if (s < L"Amber") {
- return false;
- }
-
- if (s < String("Beatrix")) {
- return false;
- }
-
- return true;
-}
-
-bool test_9() {
- OS::get_singleton()->print("\n\nTest 9: Concatenation\n");
-
- String s;
-
- s += "Have";
- s += ' ';
- s += 'a';
- s += String(" ");
- s = s + L"Nice";
- s = s + " ";
- s = s + String("Day");
-
- OS::get_singleton()->print("\tComparing to \"Have a Nice Day\"\n");
-
- return (s == "Have a Nice Day");
-}
-
-bool test_10() {
- OS::get_singleton()->print("\n\nTest 10: Misc funcs (size/length/empty/etc)\n");
-
- if (!String("").empty()) {
- return false;
- }
-
- if (String("Mellon").size() != 7) {
- return false;
- }
-
- if (String("Oranges").length() != 7) {
- return false;
- }
-
- return true;
-}
-
-bool test_11() {
- OS::get_singleton()->print("\n\nTest 11: Operator[]\n");
-
- String a = "Kugar Sane";
-
- a[0] = 'S';
- a[6] = 'C';
-
- if (a != "Sugar Cane") {
- return false;
- }
-
- if (a[1] != 'u') {
- return false;
- }
-
- return true;
-}
-
-bool test_12() {
- OS::get_singleton()->print("\n\nTest 12: case functions\n");
-
- String a = "MoMoNgA";
-
- if (a.to_upper() != "MOMONGA") {
- return false;
- }
-
- if (a.nocasecmp_to("momonga") != 0) {
- return false;
- }
-
- return true;
-}
-
-bool test_13() {
- OS::get_singleton()->print("\n\nTest 13: UTF8\n");
-
- /* how can i embed UTF in here? */
-
- static const CharType ustr[] = { 0x304A, 0x360F, 0x3088, 0x3046, 0 };
- //static const wchar_t ustr[] = { 'P', 0xCE, 'p',0xD3, 0 };
- String s = ustr;
-
- OS::get_singleton()->print("\tUnicode: %ls\n", ustr);
- s.parse_utf8(s.utf8().get_data());
- OS::get_singleton()->print("\tConvert/Parse UTF8: %ls\n", s.c_str());
-
- return (s == ustr);
-}
-
-bool test_14() {
- OS::get_singleton()->print("\n\nTest 14: ASCII\n");
-
- String s = L"Primero Leche";
- OS::get_singleton()->print("\tAscii: %s\n", s.ascii().get_data());
-
- String t = s.ascii().get_data();
- return (s == t);
-}
-
-bool test_15() {
- OS::get_singleton()->print("\n\nTest 15: substr\n");
-
- String s = "Killer Baby";
- OS::get_singleton()->print("\tsubstr(3,4) of \"%ls\" is \"%ls\"\n", s.c_str(), s.substr(3, 4).c_str());
-
- return (s.substr(3, 4) == "ler ");
-}
-
-bool test_16() {
- OS::get_singleton()->print("\n\nTest 16: find\n");
-
- String s = "Pretty Woman";
- OS::get_singleton()->print("\tString: %ls\n", s.c_str());
- OS::get_singleton()->print("\t\"tty\" is at %i pos.\n", s.find("tty"));
- OS::get_singleton()->print("\t\"Revenge of the Monster Truck\" is at %i pos.\n", s.find("Revenge of the Monster Truck"));
-
- if (s.find("tty") != 3) {
- return false;
- }
-
- if (s.find("Revenge of the Monster Truck") != -1) {
- return false;
- }
-
- return true;
-}
-
-bool test_17() {
- OS::get_singleton()->print("\n\nTest 17: find no case\n");
-
- String s = "Pretty Whale";
- OS::get_singleton()->print("\tString: %ls\n", s.c_str());
- OS::get_singleton()->print("\t\"WHA\" is at %i pos.\n", s.findn("WHA"));
- OS::get_singleton()->print("\t\"Revenge of the Monster SawFish\" is at %i pos.\n", s.findn("Revenge of the Monster Truck"));
-
- if (s.findn("WHA") != 7) {
- return false;
- }
-
- if (s.findn("Revenge of the Monster SawFish") != -1) {
- return false;
- }
-
- return true;
-}
-
-bool test_18() {
- OS::get_singleton()->print("\n\nTest 18: find no case\n");
-
- String s = "Pretty Whale";
- OS::get_singleton()->print("\tString: %ls\n", s.c_str());
- OS::get_singleton()->print("\t\"WHA\" is at %i pos.\n", s.findn("WHA"));
- OS::get_singleton()->print("\t\"Revenge of the Monster SawFish\" is at %i pos.\n", s.findn("Revenge of the Monster Truck"));
-
- if (s.findn("WHA") != 7) {
- return false;
- }
-
- if (s.findn("Revenge of the Monster SawFish") != -1) {
- return false;
- }
-
- return true;
-}
-
-bool test_19() {
- OS::get_singleton()->print("\n\nTest 19: Search & replace\n");
-
- String s = "Happy Birthday, Anna!";
- OS::get_singleton()->print("\tString: %ls\n", s.c_str());
-
- s = s.replace("Birthday", "Halloween");
- OS::get_singleton()->print("\tReplaced Birthday/Halloween: %ls.\n", s.c_str());
-
- return (s == "Happy Halloween, Anna!");
-}
-
-bool test_20() {
- OS::get_singleton()->print("\n\nTest 20: Insertion\n");
-
- String s = "Who is Frederic?";
-
- OS::get_singleton()->print("\tString: %ls\n", s.c_str());
- s = s.insert(s.find("?"), " Chopin");
- OS::get_singleton()->print("\tInserted Chopin: %ls.\n", s.c_str());
-
- return (s == "Who is Frederic Chopin?");
-}
-
-bool test_21() {
- OS::get_singleton()->print("\n\nTest 21: Number -> String\n");
-
- OS::get_singleton()->print("\tPi is %f\n", 33.141593);
- OS::get_singleton()->print("\tPi String is %ls\n", String::num(3.141593).c_str());
-
- return String::num(3.141593) == "3.141593";
-}
-
-bool test_22() {
- OS::get_singleton()->print("\n\nTest 22: String -> Int\n");
-
- static const char *nums[4] = { "1237461283", "- 22", "0", " - 1123412" };
- static const int num[4] = { 1237461283, -22, 0, -1123412 };
-
- for (int i = 0; i < 4; i++) {
-#ifdef __MINGW32__ // MinGW can't handle normal format specifiers for some reason. So we need special code just for MinGW.
- OS::get_singleton()->print("\tString: \"%s\" as Int is %I64i\n", nums[i], (long long)(String(nums[i]).to_int()));
-#else
- OS::get_singleton()->print("\tString: \"%s\" as Int is %lli\n", nums[i], (long long)(String(nums[i]).to_int()));
-#endif
- if (String(nums[i]).to_int() != num[i]) {
- return false;
- }
- }
-
- return true;
-}
-
-bool test_23() {
- OS::get_singleton()->print("\n\nTest 23: String -> Float\n");
-
- static const char *nums[4] = { "-12348298412.2", "0.05", "2.0002", " -0.0001" };
- static const double num[4] = { -12348298412.2, 0.05, 2.0002, -0.0001 };
-
- for (int i = 0; i < 4; i++) {
- OS::get_singleton()->print("\tString: \"%s\" as Float is %f\n", nums[i], String(nums[i]).to_double());
-
- if (ABS(String(nums[i]).to_double() - num[i]) > 0.00001) {
- return false;
- }
- }
-
- return true;
-}
-
-bool test_24() {
- OS::get_singleton()->print("\n\nTest 24: Slicing\n");
-
- String s = "Mars,Jupiter,Saturn,Uranus";
-
- const char *slices[4] = { "Mars", "Jupiter", "Saturn", "Uranus" };
-
- OS::get_singleton()->print("\tSlicing \"%ls\" by \"%s\"..\n", s.c_str(), ",");
-
- for (int i = 0; i < s.get_slice_count(","); i++) {
- OS::get_singleton()->print("\t\t%i- %ls\n", i + 1, s.get_slice(",", i).c_str());
-
- if (s.get_slice(",", i) != slices[i]) {
- return false;
- }
- }
-
- return true;
-}
-
-bool test_25() {
- OS::get_singleton()->print("\n\nTest 25: Erasing\n");
-
- String s = "Josephine is such a cute girl!";
-
- OS::get_singleton()->print("\tString: %ls\n", s.c_str());
- OS::get_singleton()->print("\tRemoving \"cute\"\n");
-
- s.erase(s.find("cute "), String("cute ").length());
- OS::get_singleton()->print("\tResult: %ls\n", s.c_str());
-
- return (s == "Josephine is such a girl!");
-}
-
-bool test_26() {
- OS::get_singleton()->print("\n\nTest 26: RegEx substitution\n");
-
-#ifndef MODULE_REGEX_ENABLED
- OS::get_singleton()->print("\tRegEx module disabled, can't run test.");
- return false;
-#else
- String s = "Double all the vowels.";
-
- OS::get_singleton()->print("\tString: %ls\n", s.c_str());
- OS::get_singleton()->print("\tRepeating instances of 'aeiou' once\n");
-
- RegEx re("(?<vowel>[aeiou])");
- s = re.sub(s, "$0$vowel", true);
-
- OS::get_singleton()->print("\tResult: %ls\n", s.c_str());
-
- return (s == "Doouublee aall thee vooweels.");
-#endif
-}
-
-struct test_27_data {
- char const *data;
- char const *begin;
- bool expected;
-};
-
-bool test_27() {
- OS::get_singleton()->print("\n\nTest 27: begins_with\n");
- test_27_data tc[] = {
- { "res://foobar", "res://", true },
- { "res", "res://", false },
- { "abc", "abc", true }
- };
- size_t count = sizeof(tc) / sizeof(tc[0]);
- bool state = true;
- for (size_t i = 0; state && i < count; ++i) {
- String s = tc[i].data;
- state = s.begins_with(tc[i].begin) == tc[i].expected;
- if (state) {
- String sb = tc[i].begin;
- state = s.begins_with(sb) == tc[i].expected;
- }
- if (!state) {
- OS::get_singleton()->print("\n\t Failure on:\n\t\tstring: %s\n\t\tbegin: %s\n\t\texpected: %s\n", tc[i].data, tc[i].begin, tc[i].expected ? "true" : "false");
- break;
- }
- };
- return state;
-};
-
-bool test_28() {
- OS::get_singleton()->print("\n\nTest 28: sprintf\n");
-
- bool success, state = true;
- char output_format[] = "\tTest:\t%ls => %ls (%s)\n";
- String format, output;
- Array args;
- bool error;
-
- // %%
- format = "fish %% frog";
- args.clear();
- output = format.sprintf(args, &error);
- success = (output == String("fish % frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- //////// INTS
-
- // Int
- format = "fish %d frog";
- args.clear();
- args.push_back(5);
- output = format.sprintf(args, &error);
- success = (output == String("fish 5 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Int left padded with zeroes.
- format = "fish %05d frog";
- args.clear();
- args.push_back(5);
- output = format.sprintf(args, &error);
- success = (output == String("fish 00005 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Int left padded with spaces.
- format = "fish %5d frog";
- args.clear();
- args.push_back(5);
- output = format.sprintf(args, &error);
- success = (output == String("fish 5 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Int right padded with spaces.
- format = "fish %-5d frog";
- args.clear();
- args.push_back(5);
- output = format.sprintf(args, &error);
- success = (output == String("fish 5 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Int with sign (positive).
- format = "fish %+d frog";
- args.clear();
- args.push_back(5);
- output = format.sprintf(args, &error);
- success = (output == String("fish +5 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Negative int.
- format = "fish %d frog";
- args.clear();
- args.push_back(-5);
- output = format.sprintf(args, &error);
- success = (output == String("fish -5 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Hex (lower)
- format = "fish %x frog";
- args.clear();
- args.push_back(45);
- output = format.sprintf(args, &error);
- success = (output == String("fish 2d frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Hex (upper)
- format = "fish %X frog";
- args.clear();
- args.push_back(45);
- output = format.sprintf(args, &error);
- success = (output == String("fish 2D frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Octal
- format = "fish %o frog";
- args.clear();
- args.push_back(99);
- output = format.sprintf(args, &error);
- success = (output == String("fish 143 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- ////// REALS
-
- // Real
- format = "fish %f frog";
- args.clear();
- args.push_back(99.99);
- output = format.sprintf(args, &error);
- success = (output == String("fish 99.990000 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Real left-padded
- format = "fish %11f frog";
- args.clear();
- args.push_back(99.99);
- output = format.sprintf(args, &error);
- success = (output == String("fish 99.990000 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Real right-padded
- format = "fish %-11f frog";
- args.clear();
- args.push_back(99.99);
- output = format.sprintf(args, &error);
- success = (output == String("fish 99.990000 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Real given int.
- format = "fish %f frog";
- args.clear();
- args.push_back(99);
- output = format.sprintf(args, &error);
- success = (output == String("fish 99.000000 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Real with sign (positive).
- format = "fish %+f frog";
- args.clear();
- args.push_back(99.99);
- output = format.sprintf(args, &error);
- success = (output == String("fish +99.990000 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Real with 1 decimals.
- format = "fish %.1f frog";
- args.clear();
- args.push_back(99.99);
- output = format.sprintf(args, &error);
- success = (output == String("fish 100.0 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Real with 12 decimals.
- format = "fish %.12f frog";
- args.clear();
- args.push_back(99.99);
- output = format.sprintf(args, &error);
- success = (output == String("fish 99.990000000000 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Real with no decimals.
- format = "fish %.f frog";
- args.clear();
- args.push_back(99.99);
- output = format.sprintf(args, &error);
- success = (output == String("fish 100 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- /////// Strings.
-
- // String
- format = "fish %s frog";
- args.clear();
- args.push_back("cheese");
- output = format.sprintf(args, &error);
- success = (output == String("fish cheese frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // String left-padded
- format = "fish %10s frog";
- args.clear();
- args.push_back("cheese");
- output = format.sprintf(args, &error);
- success = (output == String("fish cheese frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // String right-padded
- format = "fish %-10s frog";
- args.clear();
- args.push_back("cheese");
- output = format.sprintf(args, &error);
- success = (output == String("fish cheese frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- ///// Characters
-
- // Character as string.
- format = "fish %c frog";
- args.clear();
- args.push_back("A");
- output = format.sprintf(args, &error);
- success = (output == String("fish A frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Character as int.
- format = "fish %c frog";
- args.clear();
- args.push_back(65);
- output = format.sprintf(args, &error);
- success = (output == String("fish A frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- ///// Dynamic width
-
- // String dynamic width
- format = "fish %*s frog";
- args.clear();
- args.push_back(10);
- args.push_back("cheese");
- output = format.sprintf(args, &error);
- success = (output == String("fish cheese frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Int dynamic width
- format = "fish %*d frog";
- args.clear();
- args.push_back(10);
- args.push_back(99);
- output = format.sprintf(args, &error);
- success = (output == String("fish 99 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Float dynamic width
- format = "fish %*.*f frog";
- args.clear();
- args.push_back(10);
- args.push_back(3);
- args.push_back(99.99);
- output = format.sprintf(args, &error);
- success = (output == String("fish 99.990 frog") && !error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- ///// Errors
-
- // More formats than arguments.
- format = "fish %s %s frog";
- args.clear();
- args.push_back("cheese");
- output = format.sprintf(args, &error);
- success = (output == "not enough arguments for format string" && error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // More arguments than formats.
- format = "fish %s frog";
- args.clear();
- args.push_back("hello");
- args.push_back("cheese");
- output = format.sprintf(args, &error);
- success = (output == "not all arguments converted during string formatting" && error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Incomplete format.
- format = "fish %10";
- args.clear();
- args.push_back("cheese");
- output = format.sprintf(args, &error);
- success = (output == "incomplete format" && error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Bad character in format string
- format = "fish %&f frog";
- args.clear();
- args.push_back("cheese");
- output = format.sprintf(args, &error);
- success = (output == "unsupported format character" && error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Too many decimals.
- format = "fish %2.2.2f frog";
- args.clear();
- args.push_back(99.99);
- output = format.sprintf(args, &error);
- success = (output == "too many decimal points in format" && error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // * not a number
- format = "fish %*f frog";
- args.clear();
- args.push_back("cheese");
- args.push_back(99.99);
- output = format.sprintf(args, &error);
- success = (output == "* wants number" && error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Character too long.
- format = "fish %c frog";
- args.clear();
- args.push_back("sc");
- output = format.sprintf(args, &error);
- success = (output == "%c requires number or single-character string" && error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- // Character bad type.
- format = "fish %c frog";
- args.clear();
- args.push_back(Array());
- output = format.sprintf(args, &error);
- success = (output == "%c requires number or single-character string" && error);
- OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- return state;
-}
-
-bool test_29() {
- bool state = true;
-
- IP_Address ip0("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
- OS::get_singleton()->print("ip0 is %ls\n", String(ip0).c_str());
-
- IP_Address ip(0x0123, 0x4567, 0x89ab, 0xcdef, true);
- OS::get_singleton()->print("ip6 is %ls\n", String(ip).c_str());
-
- IP_Address ip2("fe80::52e5:49ff:fe93:1baf");
- OS::get_singleton()->print("ip6 is %ls\n", String(ip2).c_str());
-
- IP_Address ip3("::ffff:192.168.0.1");
- OS::get_singleton()->print("ip6 is %ls\n", String(ip3).c_str());
-
- String ip4 = "192.168.0.1";
- bool success = ip4.is_valid_ip_address();
- OS::get_singleton()->print("Is valid ipv4: %ls, %s\n", ip4.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- ip4 = "192.368.0.1";
- success = (!ip4.is_valid_ip_address());
- OS::get_singleton()->print("Is invalid ipv4: %ls, %s\n", ip4.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- String ip6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
- success = ip6.is_valid_ip_address();
- OS::get_singleton()->print("Is valid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- ip6 = "2001:0db8:85j3:0000:0000:8a2e:0370:7334";
- success = (!ip6.is_valid_ip_address());
- OS::get_singleton()->print("Is invalid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- ip6 = "2001:0db8:85f345:0000:0000:8a2e:0370:7334";
- success = (!ip6.is_valid_ip_address());
- OS::get_singleton()->print("Is invalid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- ip6 = "2001:0db8::0:8a2e:370:7334";
- success = (ip6.is_valid_ip_address());
- OS::get_singleton()->print("Is valid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- ip6 = "::ffff:192.168.0.1";
- success = (ip6.is_valid_ip_address());
- OS::get_singleton()->print("Is valid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL");
- state = state && success;
-
- return state;
-};
-
-bool test_30() {
- bool state = true;
- bool success = true;
- String input = "bytes2var";
- String output = "Bytes 2 Var";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "linear2db";
- output = "Linear 2 Db";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "vector3";
- output = "Vector 3";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "sha256";
- output = "Sha 256";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "2db";
- output = "2 Db";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "PascalCase";
- output = "Pascal Case";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "PascalPascalCase";
- output = "Pascal Pascal Case";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "snake_case";
- output = "Snake Case";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "snake_snake_case";
- output = "Snake Snake Case";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "sha256sum";
- output = "Sha 256 Sum";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "cat2dog";
- output = "Cat 2 Dog";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "function(name)";
- output = "Function(name)";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls (existing incorrect behavior): %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "snake_case_function(snake_case_arg)";
- output = "Snake Case Function(snake Case Arg)";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls (existing incorrect behavior): %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- input = "snake_case_function( snake_case_arg )";
- output = "Snake Case Function( Snake Case Arg )";
- success = (input.capitalize() == output);
- state = state && success;
- OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL");
-
- return state;
-}
-
-bool test_31() {
- bool state = true;
- bool success;
-
- String a = "";
- success = a[0] == 0;
- OS::get_singleton()->print("Is 0 String[0]:, %s\n", success ? "OK" : "FAIL");
- if (!success) {
- state = false;
- }
-
- String b = "Godot";
- success = b[b.size()] == 0;
- OS::get_singleton()->print("Is 0 String[size()]:, %s\n", success ? "OK" : "FAIL");
- if (!success) {
- state = false;
- }
-
- const String c = "";
- success = c[0] == 0;
- OS::get_singleton()->print("Is 0 const String[0]:, %s\n", success ? "OK" : "FAIL");
- if (!success) {
- state = false;
- }
-
- const String d = "Godot";
- success = d[d.size()] == 0;
- OS::get_singleton()->print("Is 0 const String[size()]:, %s\n", success ? "OK" : "FAIL");
- if (!success) {
- state = false;
- }
-
- return state;
-};
-
-bool test_32() {
-#define STRIP_TEST(x) \
- { \
- bool success = x; \
- state = state && success; \
- if (!success) { \
- OS::get_singleton()->print("\tfailed at: %s\n", #x); \
- } \
- }
-
- OS::get_singleton()->print("\n\nTest 32: lstrip and rstrip\n");
- bool state = true;
-
- // strip none
- STRIP_TEST(String("abc").lstrip("") == "abc");
- STRIP_TEST(String("abc").rstrip("") == "abc");
- // strip one
- STRIP_TEST(String("abc").lstrip("a") == "bc");
- STRIP_TEST(String("abc").rstrip("c") == "ab");
- // strip lots
- STRIP_TEST(String("bababbababccc").lstrip("ab") == "ccc");
- STRIP_TEST(String("aaabcbcbcbbcbbc").rstrip("cb") == "aaa");
- // strip empty string
- STRIP_TEST(String("").lstrip("") == "");
- STRIP_TEST(String("").rstrip("") == "");
- // strip to empty string
- STRIP_TEST(String("abcabcabc").lstrip("bca") == "");
- STRIP_TEST(String("abcabcabc").rstrip("bca") == "");
- // don't strip wrong end
- STRIP_TEST(String("abc").lstrip("c") == "abc");
- STRIP_TEST(String("abca").lstrip("a") == "bca");
- STRIP_TEST(String("abc").rstrip("a") == "abc");
- STRIP_TEST(String("abca").rstrip("a") == "abc");
- // in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5)
- // and the same second as "ÿ" (\u00ff)
- STRIP_TEST(String::utf8("¿").lstrip(String::utf8("µÿ")) == String::utf8("¿"));
- STRIP_TEST(String::utf8("¿").rstrip(String::utf8("µÿ")) == String::utf8("¿"));
- STRIP_TEST(String::utf8("µ¿ÿ").lstrip(String::utf8("µÿ")) == String::utf8("¿ÿ"));
- STRIP_TEST(String::utf8("µ¿ÿ").rstrip(String::utf8("µÿ")) == String::utf8("µ¿"));
-
- // the above tests repeated with additional superfluous strip chars
-
- // strip none
- STRIP_TEST(String("abc").lstrip("qwjkl") == "abc");
- STRIP_TEST(String("abc").rstrip("qwjkl") == "abc");
- // strip one
- STRIP_TEST(String("abc").lstrip("qwajkl") == "bc");
- STRIP_TEST(String("abc").rstrip("qwcjkl") == "ab");
- // strip lots
- STRIP_TEST(String("bababbababccc").lstrip("qwabjkl") == "ccc");
- STRIP_TEST(String("aaabcbcbcbbcbbc").rstrip("qwcbjkl") == "aaa");
- // strip empty string
- STRIP_TEST(String("").lstrip("qwjkl") == "");
- STRIP_TEST(String("").rstrip("qwjkl") == "");
- // strip to empty string
- STRIP_TEST(String("abcabcabc").lstrip("qwbcajkl") == "");
- STRIP_TEST(String("abcabcabc").rstrip("qwbcajkl") == "");
- // don't strip wrong end
- STRIP_TEST(String("abc").lstrip("qwcjkl") == "abc");
- STRIP_TEST(String("abca").lstrip("qwajkl") == "bca");
- STRIP_TEST(String("abc").rstrip("qwajkl") == "abc");
- STRIP_TEST(String("abca").rstrip("qwajkl") == "abc");
- // in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5)
- // and the same second as "ÿ" (\u00ff)
- STRIP_TEST(String::utf8("¿").lstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿"));
- STRIP_TEST(String::utf8("¿").rstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿"));
- STRIP_TEST(String::utf8("µ¿ÿ").lstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿ÿ"));
- STRIP_TEST(String::utf8("µ¿ÿ").rstrip(String::utf8("qwaµÿjkl")) == String::utf8("µ¿"));
-
- return state;
-
-#undef STRIP_TEST
-}
-
-bool test_33() {
- OS::get_singleton()->print("\n\nTest 33: parse_utf8(null, -1)\n");
-
- String empty;
- return empty.parse_utf8(nullptr, -1);
-}
-
-bool test_34() {
- OS::get_singleton()->print("\n\nTest 34: Cyrillic to_lower()\n");
-
- String upper = String::utf8("АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ");
- String lower = String::utf8("абвгдеёжзийклмнопрстуфхцчшщъыьэюя");
-
- String test = upper.to_lower();
-
- bool state = test == lower;
-
- return state;
-}
-
-bool test_35() {
-#define COUNT_TEST(x) \
- { \
- bool success = x; \
- state = state && success; \
- if (!success) { \
- OS::get_singleton()->print("\tfailed at: %s\n", #x); \
- } \
- }
-
- OS::get_singleton()->print("\n\nTest 35: count and countn function\n");
- bool state = true;
-
- COUNT_TEST(String("").count("Test") == 0);
- COUNT_TEST(String("Test").count("") == 0);
- COUNT_TEST(String("Test").count("test") == 0);
- COUNT_TEST(String("Test").count("TEST") == 0);
- COUNT_TEST(String("TEST").count("TEST") == 1);
- COUNT_TEST(String("Test").count("Test") == 1);
- COUNT_TEST(String("aTest").count("Test") == 1);
- COUNT_TEST(String("Testa").count("Test") == 1);
- COUNT_TEST(String("TestTestTest").count("Test") == 3);
- COUNT_TEST(String("TestTestTest").count("TestTest") == 1);
- COUNT_TEST(String("TestGodotTestGodotTestGodot").count("Test") == 3);
-
- COUNT_TEST(String("TestTestTestTest").count("Test", 4, 8) == 1);
- COUNT_TEST(String("TestTestTestTest").count("Test", 4, 12) == 2);
- COUNT_TEST(String("TestTestTestTest").count("Test", 4, 16) == 3);
- COUNT_TEST(String("TestTestTestTest").count("Test", 4) == 3);
-
- COUNT_TEST(String("Test").countn("test") == 1);
- COUNT_TEST(String("Test").countn("TEST") == 1);
- COUNT_TEST(String("testTest-Testatest").countn("tEst") == 4);
- COUNT_TEST(String("testTest-TeStatest").countn("tEsT", 4, 16) == 2);
-
- return state;
-}
-
-typedef bool (*TestFunc)();
-
-TestFunc test_funcs[] = {
-
- test_1,
- test_2,
- test_3,
- test_4,
- test_5,
- test_6,
- test_7,
- test_8,
- test_9,
- test_10,
- test_11,
- test_12,
- test_13,
- test_14,
- test_15,
- test_16,
- test_17,
- test_18,
- test_19,
- test_20,
- test_21,
- test_22,
- test_23,
- test_24,
- test_25,
- test_26,
- test_27,
- test_28,
- test_29,
- test_30,
- test_31,
- test_32,
- test_33,
- test_34,
- test_35,
- nullptr
-
-};
-
-MainLoop *test() {
- /** A character length != wchar_t may be forced, so the tests won't work */
-
- static_assert(sizeof(CharType) == sizeof(wchar_t));
-
- int count = 0;
- int passed = 0;
-
- while (true) {
- if (!test_funcs[count]) {
- break;
- }
- bool pass = test_funcs[count]();
- if (pass) {
- passed++;
- }
- OS::get_singleton()->print("\t%s\n", pass ? "PASS" : "FAILED");
-
- count++;
- }
-
- OS::get_singleton()->print("\n\n\n");
- OS::get_singleton()->print("*************\n");
- OS::get_singleton()->print("***TOTALS!***\n");
- OS::get_singleton()->print("*************\n");
-
- OS::get_singleton()->print("Passed %i of %i tests\n", passed, count);
-
- return nullptr;
-}
-
-} // namespace TestString
diff --git a/main/tests/test_string.h b/main/tests/test_string.h
deleted file mode 100644
index 96fa811126..0000000000
--- a/main/tests/test_string.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*************************************************************************/
-/* test_string.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef TEST_STRING_H
-#define TEST_STRING_H
-
-#include "core/os/main_loop.h"
-#include "core/ustring.h"
-
-namespace TestString {
-
-MainLoop *test();
-}
-
-#endif
diff --git a/methods.py b/methods.py
index 7b853b7821..65a460f63d 100644
--- a/methods.py
+++ b/methods.py
@@ -92,7 +92,7 @@ def update_version(module_version_string=""):
gitfolder = module_folder[8:]
if os.path.isfile(os.path.join(gitfolder, "HEAD")):
- head = open(os.path.join(gitfolder, "HEAD"), "r").readline().strip()
+ head = open(os.path.join(gitfolder, "HEAD"), "r", encoding="utf8").readline().strip()
if head.startswith("ref: "):
head = os.path.join(gitfolder, head[5:])
if os.path.isfile(head):
diff --git a/misc/travis/android-tools-linux.sh b/misc/ci/android-tools-linux.sh
index 6114551861..6114551861 100755
--- a/misc/travis/android-tools-linux.sh
+++ b/misc/ci/android-tools-linux.sh
diff --git a/misc/ci/sources.list b/misc/ci/sources.list
new file mode 100644
index 0000000000..4d8f94f35c
--- /dev/null
+++ b/misc/ci/sources.list
@@ -0,0 +1,4 @@
+deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse
+deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe multiverse
+deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse
+deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse
diff --git a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
index 54dda2563d..9a1143a21a 100644
--- a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
+++ b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
@@ -12,6 +12,8 @@
$modules_buildfile
1FF8DBB11FBA9DE1009DE660 /* dummy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */; };
D07CD44E1C5D589C00B7FB28 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D07CD44D1C5D589C00B7FB28 /* Images.xcassets */; };
+ 9039D3BE24C093AC0020482C /* MoltenVK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9039D3BD24C093AC0020482C /* MoltenVK.a */; };
+ 9073252C24BF980B0063BCD4 /* vulkan in Resources */ = {isa = PBXBuildFile; fileRef = 905036DC24BF932E00301046 /* vulkan */; };
D0BCFE4618AEBDA2004A7AAE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE4418AEBDA2004A7AAE /* InfoPlist.strings */; };
D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE7718AEBFEB004A7AAE /* $binary.pck */; };
$pbx_launch_screen_build_reference
@@ -37,6 +39,8 @@
$modules_fileref
1FF4C1881F584E6300A41E41 /* $binary.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "$binary.entitlements"; sourceTree = "<group>"; };
1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dummy.cpp; sourceTree = "<group>"; };
+ 9039D3BD24C093AC0020482C /* MoltenVK.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = MoltenVK; path = MoltenVK.a; sourceTree = "<group>"; };
+ 905036DC24BF932E00301046 /* vulkan */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vulkan; path = "$binary/vulkan"; sourceTree = "<group>"; };
D07CD44D1C5D589C00B7FB28 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
D0BCFE3418AEBDA2004A7AAE /* $binary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "$binary.app"; sourceTree = BUILT_PRODUCTS_DIR; };
D0BCFE4318AEBDA2004A7AAE /* $binary-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "$binary-Info.plist"; sourceTree = "<group>"; };
@@ -52,6 +56,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 9039D3BE24C093AC0020482C /* MoltenVK.a in Frameworks */,
DEADBEEF2F582BE20003B888 /* $binary.a */,
$modules_buildphase
$additional_pbx_frameworks_build
@@ -64,6 +69,7 @@
D0BCFE2B18AEBDA2004A7AAE = {
isa = PBXGroup;
children = (
+ 905036DC24BF932E00301046 /* vulkan */,
1F1575711F582BE20003B888 /* dylibs */,
D0BCFE7718AEBFEB004A7AAE /* $binary.pck */,
D0BCFE4118AEBDA2004A7AAE /* $binary */,
@@ -84,6 +90,7 @@
D0BCFE3618AEBDA2004A7AAE /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 9039D3BD24C093AC0020482C /* MoltenVK.a */,
DEADBEEF1F582BE20003B888 /* $binary.a */,
$modules_buildgrp
$additional_pbx_frameworks_refs
@@ -248,6 +255,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 9073252C24BF980B0063BCD4 /* vulkan in Resources */,
D07CD44E1C5D589C00B7FB28 /* Images.xcassets in Resources */,
D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */,
$pbx_launch_screen_build_phase
@@ -319,7 +327,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
OTHER_LDFLAGS = "$linker_flags";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -358,7 +366,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
OTHER_LDFLAGS = "$linker_flags";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
diff --git a/misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json b/misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json
new file mode 100644
index 0000000000..7501cb548c
--- /dev/null
+++ b/misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json
@@ -0,0 +1,7 @@
+{
+ "file_format_version" : "1.0.0",
+ "ICD": {
+ "library_path": "./../../Frameworks/MoltenVK.framework/MoltenVK",
+ "api_version" : "1.0.0"
+ }
+}
diff --git a/misc/travis/ccache-osx.sh b/misc/travis/ccache-osx.sh
deleted file mode 100755
index 5ce7a80cbc..0000000000
--- a/misc/travis/ccache-osx.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/bin/bash
-
-echo
-echo "Download and install ccache ..."
-echo
-
-echo "Downloading sources ..."
-curl -L -O https://www.samba.org/ftp/ccache/ccache-3.3.4.tar.gz # latest version available here: https://ccache.samba.org/download.html
-
-echo "Extracting to build directory ..."
-tar xzf ccache-3.3.4.tar.gz
-cd ccache-3.3.4
-
-echo "Compiling sources ..."
-./configure --prefix=/usr/local --with-bundled-zlib > /dev/null
-make
-
-echo "Installing ..."
-
-mkdir /usr/local/opt/ccache
-
-mkdir /usr/local/opt/ccache/bin
-cp ccache /usr/local/opt/ccache/bin
-ln -s /usr/local/opt/ccache/bin/ccache /usr/local/bin/ccache
-
-mkdir /usr/local/opt/ccache/libexec
-links=(
- clang
- clang++
- cc
- gcc gcc2 gcc3 gcc-3.3 gcc-4.0 gcc-4.2 gcc-4.3 gcc-4.4 gcc-4.5 gcc-4.6 gcc-4.7 gcc-4.8 gcc-4.9 gcc-5 gcc-6 gcc-7
- c++ c++3 c++-3.3 c++-4.0 c++-4.2 c++-4.3 c++-4.4 c++-4.5 c++-4.6 c++-4.7 c++-4.8 c++-4.9 c++-5 c++-6 c++-7
- g++ g++2 g++3 g++-3.3 g++-4.0 g++-4.2 g++-4.3 g++-4.4 g++-4.5 g++-4.6 g++-4.7 g++-4.8 g++-4.9 g++-5 g++-6 g++-7
-)
-for link in "${links[@]}"; do
- ln -s ../bin/ccache /usr/local/opt/ccache/libexec/$link
-done
-#/usr/local/bin/ccache -M 2G
-cd $TRAVIS_BUILD_DIR
-
-echo
-echo "Done!"
-echo
diff --git a/modules/arkit/arkit_interface.h b/modules/arkit/arkit_interface.h
index 1044f3cf6f..5a2c50e213 100644
--- a/modules/arkit/arkit_interface.h
+++ b/modules/arkit/arkit_interface.h
@@ -60,8 +60,8 @@ private:
float eye_height, z_near, z_far;
Ref<CameraFeed> feed;
- int image_width[2];
- int image_height[2];
+ size_t image_width[2];
+ size_t image_height[2];
Vector<uint8_t> img_data[2];
struct anchor_map {
@@ -84,9 +84,9 @@ public:
void start_session();
void stop_session();
- bool get_anchor_detection_is_enabled() const;
- void set_anchor_detection_is_enabled(bool p_enable);
- virtual int get_camera_feed_id();
+ bool get_anchor_detection_is_enabled() const override;
+ void set_anchor_detection_is_enabled(bool p_enable) override;
+ virtual int get_camera_feed_id() override;
bool get_light_estimation_is_enabled() const;
void set_light_estimation_is_enabled(bool p_enable);
@@ -97,22 +97,22 @@ public:
/* while Godot has its own raycast logic this takes ARKits camera into account and hits on any ARAnchor */
Array raycast(Vector2 p_screen_coord);
- void notification(int p_what);
+ virtual void notification(int p_what) override;
- virtual StringName get_name() const;
- virtual int get_capabilities() const;
+ virtual StringName get_name() const override;
+ virtual int get_capabilities() const override;
- virtual bool is_initialized() const;
- virtual bool initialize();
- virtual void uninitialize();
+ virtual bool is_initialized() const override;
+ virtual bool initialize() override;
+ virtual void uninitialize() override;
- virtual Size2 get_render_targetsize();
- virtual bool is_stereo();
- virtual Transform get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform);
- virtual CameraMatrix get_projection_for_eye(XRInterface::Eyes p_eye, real_t p_aspect, real_t p_z_near, real_t p_z_far);
- virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect);
+ virtual Size2 get_render_targetsize() override;
+ virtual bool is_stereo() override;
+ virtual Transform get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform) override;
+ virtual CameraMatrix get_projection_for_eye(XRInterface::Eyes p_eye, real_t p_aspect, real_t p_z_near, real_t p_z_far) override;
+ virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) override;
- virtual void process();
+ virtual void process() override;
// called by delegate (void * because C++ and Obj-C don't always mix, should really change all platform/iphone/*.cpp files to .mm)
void _add_or_update_anchor(void *p_anchor);
diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm
index 7de824815a..3fb2cc933d 100644
--- a/modules/arkit/arkit_interface.mm
+++ b/modules/arkit/arkit_interface.mm
@@ -42,7 +42,9 @@
#include "arkit_session_delegate.h"
// just a dirty workaround for now, declare these as globals. I'll probably encapsulate ARSession and associated logic into an mm object and change ARKitInterface to a normal cpp object that consumes it.
+API_AVAILABLE(ios(11.0))
ARSession *ar_session;
+
ARKitSessionDelegate *ar_delegate;
NSTimeInterval last_timestamp;
@@ -55,22 +57,28 @@ void ARKitInterface::start_session() {
if (initialized) {
print_line("Starting ARKit session");
- Class ARWorldTrackingConfigurationClass = NSClassFromString(@"ARWorldTrackingConfiguration");
- ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfigurationClass new];
+ if (@available(iOS 11, *)) {
+ Class ARWorldTrackingConfigurationClass = NSClassFromString(@"ARWorldTrackingConfiguration");
+ ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfigurationClass new];
- configuration.lightEstimationEnabled = light_estimation_is_enabled;
- if (plane_detection_is_enabled) {
- configuration.planeDetection = ARPlaneDetectionVertical | ARPlaneDetectionHorizontal;
- } else {
- configuration.planeDetection = 0;
- }
+ configuration.lightEstimationEnabled = light_estimation_is_enabled;
+ if (plane_detection_is_enabled) {
+ if (@available(iOS 11.3, *)) {
+ configuration.planeDetection = ARPlaneDetectionVertical | ARPlaneDetectionHorizontal;
+ } else {
+ configuration.planeDetection = ARPlaneDetectionHorizontal;
+ }
+ } else {
+ configuration.planeDetection = 0;
+ }
- // make sure our camera is on
- if (feed.is_valid()) {
- feed->set_active(true);
- }
+ // make sure our camera is on
+ if (feed.is_valid()) {
+ feed->set_active(true);
+ }
- [ar_session runWithConfiguration:configuration];
+ [ar_session runWithConfiguration:configuration];
+ }
}
}
@@ -84,7 +92,9 @@ void ARKitInterface::stop_session() {
feed->set_active(false);
}
- [ar_session pause];
+ if (@available(iOS 11.0, *)) {
+ [ar_session pause];
+ }
}
}
@@ -92,12 +102,12 @@ void ARKitInterface::notification(int p_what) {
// TODO, this is not being called, need to find out why, possibly because this is not a node.
// in that case we need to find a way to get these notifications!
switch (p_what) {
- case MainLoop::NOTIFICATION_WM_FOCUS_IN: {
+ case DisplayServer::WINDOW_EVENT_FOCUS_IN: {
print_line("Focus in");
start_session();
}; break;
- case MainLoop::NOTIFICATION_WM_FOCUS_OUT: {
+ case DisplayServer::WINDOW_EVENT_FOCUS_OUT: {
print_line("Focus out");
stop_session();
@@ -162,37 +172,42 @@ int ARKitInterface::get_capabilities() const {
}
Array ARKitInterface::raycast(Vector2 p_screen_coord) {
- Array arr;
- Size2 screen_size = OS::get_singleton()->get_window_size();
- CGPoint point;
- point.x = p_screen_coord.x / screen_size.x;
- point.y = p_screen_coord.y / screen_size.y;
-
- ///@TODO maybe give more options here, for now we're taking just ARAchors into account that were found during plane detection keeping their size into account
- NSArray<ARHitTestResult *> *results = [ar_session.currentFrame hittest:point types:ARHitTestResultTypeExistingPlaneUsingExtent];
-
- for (ARHitTestResult *result in results) {
- Transform transform;
-
- matrix_float4x4 m44 = result.worldTransform;
- transform.basis.elements[0].x = m44.columns[0][0];
- transform.basis.elements[1].x = m44.columns[0][1];
- transform.basis.elements[2].x = m44.columns[0][2];
- transform.basis.elements[0].y = m44.columns[1][0];
- transform.basis.elements[1].y = m44.columns[1][1];
- transform.basis.elements[2].y = m44.columns[1][2];
- transform.basis.elements[0].z = m44.columns[2][0];
- transform.basis.elements[1].z = m44.columns[2][1];
- transform.basis.elements[2].z = m44.columns[2][2];
- transform.origin.x = m44.columns[3][0];
- transform.origin.y = m44.columns[3][1];
- transform.origin.z = m44.columns[3][2];
-
- /* important, NOT scaled to world_scale !! */
- arr.push_back(transform);
- }
+ if (@available(iOS 11, *)) {
+ Array arr;
+ Size2 screen_size = DisplayServer::get_singleton()->screen_get_size();
+ CGPoint point;
+ point.x = p_screen_coord.x / screen_size.x;
+ point.y = p_screen_coord.y / screen_size.y;
+
+ ///@TODO maybe give more options here, for now we're taking just ARAchors into account that were found during plane detection keeping their size into account
+
+ NSArray<ARHitTestResult *> *results = [ar_session.currentFrame hitTest:point types:ARHitTestResultTypeExistingPlaneUsingExtent];
+
+ for (ARHitTestResult *result in results) {
+ Transform transform;
+
+ matrix_float4x4 m44 = result.worldTransform;
+ transform.basis.elements[0].x = m44.columns[0][0];
+ transform.basis.elements[1].x = m44.columns[0][1];
+ transform.basis.elements[2].x = m44.columns[0][2];
+ transform.basis.elements[0].y = m44.columns[1][0];
+ transform.basis.elements[1].y = m44.columns[1][1];
+ transform.basis.elements[2].y = m44.columns[1][2];
+ transform.basis.elements[0].z = m44.columns[2][0];
+ transform.basis.elements[1].z = m44.columns[2][1];
+ transform.basis.elements[2].z = m44.columns[2][2];
+ transform.origin.x = m44.columns[3][0];
+ transform.origin.y = m44.columns[3][1];
+ transform.origin.z = m44.columns[3][2];
+
+ /* important, NOT scaled to world_scale !! */
+ arr.push_back(transform);
+ }
- return arr;
+ return arr;
+ } else {
+ return Array();
+ }
}
void ARKitInterface::_bind_methods() {
@@ -221,51 +236,55 @@ bool ARKitInterface::initialize() {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, false);
- if (!initialized) {
- print_line("initializing ARKit");
+ if (@available(iOS 11, *)) {
+ if (!initialized) {
+ print_line("initializing ARKit");
- // create our ar session and delegate
- Class ARSessionClass = NSClassFromString(@"ARSession");
- if (ARSessionClass == Nil) {
- void *arkit_handle = dlopen("/System/Library/Frameworks/ARKit.framework/ARKit", RTLD_NOW);
- if (arkit_handle) {
- ARSessionClass = NSClassFromString(@"ARSession");
- } else {
- print_line("ARKit init failed");
- return false;
+ // create our ar session and delegate
+ Class ARSessionClass = NSClassFromString(@"ARSession");
+ if (ARSessionClass == Nil) {
+ void *arkit_handle = dlopen("/System/Library/Frameworks/ARKit.framework/ARKit", RTLD_NOW);
+ if (arkit_handle) {
+ ARSessionClass = NSClassFromString(@"ARSession");
+ } else {
+ print_line("ARKit init failed");
+ return false;
+ }
}
- }
- ar_session = [ARSessionClass new];
- ar_delegate = [ARKitSessionDelegate new];
- ar_delegate.arkit_interface = this;
- ar_session.delegate = ar_delegate;
+ ar_session = [ARSessionClass new];
+ ar_delegate = [ARKitSessionDelegate new];
+ ar_delegate.arkit_interface = this;
+ ar_session.delegate = ar_delegate;
- // reset our transform
- transform = Transform();
+ // reset our transform
+ transform = Transform();
- // make this our primary interface
- xr_server->set_primary_interface(this);
+ // make this our primary interface
+ xr_server->set_primary_interface(this);
- // make sure we have our feed setup
- if (feed.is_null()) {
- feed.instance();
- feed->set_name("ARKit");
+ // make sure we have our feed setup
+ if (feed.is_null()) {
+ feed.instance();
+ feed->set_name("ARKit");
- CameraServer *cs = CameraServer::get_singleton();
- if (cs != NULL) {
- cs->add_feed(feed);
+ CameraServer *cs = CameraServer::get_singleton();
+ if (cs != NULL) {
+ cs->add_feed(feed);
+ }
}
- }
- feed->set_active(true);
+ feed->set_active(true);
- // yeah!
- initialized = true;
+ // yeah!
+ initialized = true;
- // Start our session...
- start_session();
- }
+ // Start our session...
+ start_session();
+ }
- return true;
+ return true;
+ } else {
+ return false;
+ }
}
void ARKitInterface::uninitialize() {
@@ -286,9 +305,12 @@ void ARKitInterface::uninitialize() {
remove_all_anchors();
- [ar_session release];
+ if (@available(iOS 11.0, *)) {
+ [ar_session release];
+ ar_session = NULL;
+ }
[ar_delegate release];
- ar_session = NULL;
+
ar_delegate = NULL;
initialized = false;
session_was_started = false;
@@ -296,15 +318,15 @@ void ARKitInterface::uninitialize() {
}
Size2 ARKitInterface::get_render_targetsize() {
- _THREAD_SAFE_METHOD_
+ // _THREAD_SAFE_METHOD_
- Size2 target_size = OS::get_singleton()->get_window_size();
+ Size2 target_size = DisplayServer::get_singleton()->screen_get_size();
return target_size;
}
Transform ARKitInterface::get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform) {
- _THREAD_SAFE_METHOD_
+ // _THREAD_SAFE_METHOD_
Transform transform_for_eye;
@@ -336,7 +358,7 @@ CameraMatrix ARKitInterface::get_projection_for_eye(XRInterface::Eyes p_eye, rea
}
void ARKitInterface::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) {
- _THREAD_SAFE_METHOD_
+ // _THREAD_SAFE_METHOD_
// We must have a valid render target
ERR_FAIL_COND(!p_render_target.is_valid());
@@ -345,15 +367,15 @@ void ARKitInterface::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target
ERR_FAIL_COND(p_screen_rect == Rect2());
// get the size of our screen
- Rect2 screen_rect = p_screen_rect;
+ // Rect2 screen_rect = p_screen_rect;
// screen_rect.position.x += screen_rect.size.x;
// screen_rect.size.x = -screen_rect.size.x;
// screen_rect.position.y += screen_rect.size.y;
// screen_rect.size.y = -screen_rect.size.y;
- VSG::rasterizer->set_current_render_target(RID());
- VSG::rasterizer->blit_render_target_to_screen(p_render_target, screen_rect, 0);
+ // VSG::rasterizer->set_current_render_target(RID());
+ // VSG::rasterizer->blit_render_target_to_screen(p_render_target, screen_rect, 0);
}
XRPositionalTracker *ARKitInterface::get_anchor_for_uuid(const unsigned char *p_uuid) {
@@ -432,7 +454,7 @@ void ARKitInterface::remove_all_anchors() {
}
void ARKitInterface::process() {
- _THREAD_SAFE_METHOD_
+ // _THREAD_SAFE_METHOD_
if (@available(iOS 11.0, *)) {
if (initialized) {
@@ -443,8 +465,16 @@ void ARKitInterface::process() {
last_timestamp = current_frame.timestamp;
// get some info about our screen and orientation
- Size2 screen_size = OS::get_singleton()->get_window_size();
- UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
+ Size2 screen_size = DisplayServer::get_singleton()->screen_get_size();
+ UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown;
+
+ if (@available(iOS 13, *)) {
+ orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ orientation = [[UIApplication sharedApplication] statusBarOrientation];
+#endif
+ }
// Grab our camera image for our backbuffer
CVPixelBufferRef pixelBuffer = current_frame.capturedImage;
@@ -475,27 +505,27 @@ void ARKitInterface::process() {
{
// do Y
- int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
- int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
- int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
+ size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
+ size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
+ size_t bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
if ((image_width[0] != new_width) || (image_height[0] != new_height)) {
printf("- Camera padding l:%lu r:%lu t:%lu b:%lu\n", extraLeft, extraRight, extraTop, extraBottom);
- printf("- Camera Y plane size: %i, %i - %i\n", new_width, new_height, bytes_per_row);
+ printf("- Camera Y plane size: %lu, %lu - %lu\n", new_width, new_height, bytes_per_row);
image_width[0] = new_width;
image_height[0] = new_height;
img_data[0].resize(new_width * new_height);
}
- uint8_t *w = img_data[0].write();
+ uint8_t *w = img_data[0].ptrw();
if (new_width == bytes_per_row) {
- memcpy(w.ptr(), dataY, new_width * new_height);
+ memcpy(w, dataY, new_width * new_height);
} else {
- int offset_a = 0;
- int offset_b = extraLeft + (extraTop * bytes_per_row);
- for (int r = 0; r < new_height; r++) {
- memcpy(w.ptr() + offset_a, dataY + offset_b, new_width);
+ size_t offset_a = 0;
+ size_t offset_b = extraLeft + (extraTop * bytes_per_row);
+ for (size_t r = 0; r < new_height; r++) {
+ memcpy(w + offset_a, dataY + offset_b, new_width);
offset_a += new_width;
offset_b += bytes_per_row;
}
@@ -507,26 +537,26 @@ void ARKitInterface::process() {
{
// do CbCr
- int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
- int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
- int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
+ size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
+ size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
+ size_t bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
if ((image_width[1] != new_width) || (image_height[1] != new_height)) {
- printf("- Camera CbCr plane size: %i, %i - %i\n", new_width, new_height, bytes_per_row);
+ printf("- Camera CbCr plane size: %lu, %lu - %lu\n", new_width, new_height, bytes_per_row);
image_width[1] = new_width;
image_height[1] = new_height;
img_data[1].resize(2 * new_width * new_height);
}
- uint8_t *w = img_data[1].write();
+ uint8_t *w = img_data[1].ptrw();
if ((2 * new_width) == bytes_per_row) {
- memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height);
+ memcpy(w, dataCbCr, 2 * new_width * new_height);
} else {
- int offset_a = 0;
- int offset_b = extraLeft + (extraTop * bytes_per_row);
- for (int r = 0; r < new_height; r++) {
- memcpy(w.ptr() + offset_a, dataCbCr + offset_b, 2 * new_width);
+ size_t offset_a = 0;
+ size_t offset_b = extraLeft + (extraTop * bytes_per_row);
+ for (size_t r = 0; r < new_height; r++) {
+ memcpy(w + offset_a, dataCbCr + offset_b, 2 * new_width);
offset_a += 2 * new_width;
offset_b += bytes_per_row;
}
@@ -658,69 +688,78 @@ void ARKitInterface::process() {
}
void ARKitInterface::_add_or_update_anchor(void *p_anchor) {
- _THREAD_SAFE_METHOD_
-
- ARAnchor *anchor = (ARAnchor *)p_anchor;
-
- unsigned char uuid[16];
- [anchor.identifier getUUIDBytes:uuid];
-
- XRPositionalTracker *tracker = get_anchor_for_uuid(uuid);
- if (tracker != NULL) {
- // lets update our mesh! (using Arjens code as is for now)
- // we should also probably limit how often we do this...
+ // _THREAD_SAFE_METHOD_
- // can we safely cast this?
- ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
-
- if (planeAnchor.geometry.triangleCount > 0) {
- Ref<SurfaceTool> surftool;
- surftool.instance();
- surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
+ if (@available(iOS 11.0, *)) {
+ ARAnchor *anchor = (ARAnchor *)p_anchor;
+
+ unsigned char uuid[16];
+ [anchor.identifier getUUIDBytes:uuid];
+
+ XRPositionalTracker *tracker = get_anchor_for_uuid(uuid);
+ if (tracker != NULL) {
+ // lets update our mesh! (using Arjens code as is for now)
+ // we should also probably limit how often we do this...
+
+ // can we safely cast this?
+ ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
+
+ if (@available(iOS 11.3, *)) {
+ if (planeAnchor.geometry.triangleCount > 0) {
+ Ref<SurfaceTool> surftool;
+ surftool.instance();
+ surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
+
+ for (int j = planeAnchor.geometry.triangleCount * 3 - 1; j >= 0; j--) {
+ int16_t index = planeAnchor.geometry.triangleIndices[j];
+ simd_float3 vrtx = planeAnchor.geometry.vertices[index];
+ simd_float2 textcoord = planeAnchor.geometry.textureCoordinates[index];
+ surftool->add_uv(Vector2(textcoord[0], textcoord[1]));
+ surftool->add_color(Color(0.8, 0.8, 0.8));
+ surftool->add_vertex(Vector3(vrtx[0], vrtx[1], vrtx[2]));
+ }
- for (int j = planeAnchor.geometry.triangleCount * 3 - 1; j >= 0; j--) {
- int16_t index = planeAnchor.geometry.triangleIndices[j];
- simd_float3 vrtx = planeAnchor.geometry.vertices[index];
- simd_float2 textcoord = planeAnchor.geometry.textureCoordinates[index];
- surftool->add_uv(Vector2(textcoord[0], textcoord[1]));
- surftool->add_color(Color(0.8, 0.8, 0.8));
- surftool->add_vertex(Vector3(vrtx[0], vrtx[1], vrtx[2]));
+ surftool->generate_normals();
+ tracker->set_mesh(surftool->commit());
+ } else {
+ Ref<Mesh> nomesh;
+ tracker->set_mesh(nomesh);
+ }
+ } else {
+ Ref<Mesh> nomesh;
+ tracker->set_mesh(nomesh);
}
- surftool->generate_normals();
- tracker->set_mesh(surftool->commit());
- } else {
- Ref<Mesh> nomesh;
- tracker->set_mesh(nomesh);
+ // Note, this also contains a scale factor which gives us an idea of the size of the anchor
+ // We may extract that in our XRAnchor class
+ Basis b;
+ matrix_float4x4 m44 = anchor.transform;
+ b.elements[0].x = m44.columns[0][0];
+ b.elements[1].x = m44.columns[0][1];
+ b.elements[2].x = m44.columns[0][2];
+ b.elements[0].y = m44.columns[1][0];
+ b.elements[1].y = m44.columns[1][1];
+ b.elements[2].y = m44.columns[1][2];
+ b.elements[0].z = m44.columns[2][0];
+ b.elements[1].z = m44.columns[2][1];
+ b.elements[2].z = m44.columns[2][2];
+ tracker->set_orientation(b);
+ tracker->set_rw_position(Vector3(m44.columns[3][0], m44.columns[3][1], m44.columns[3][2]));
}
-
- // Note, this also contains a scale factor which gives us an idea of the size of the anchor
- // We may extract that in our XRAnchor class
- Basis b;
- matrix_float4x4 m44 = anchor.transform;
- b.elements[0].x = m44.columns[0][0];
- b.elements[1].x = m44.columns[0][1];
- b.elements[2].x = m44.columns[0][2];
- b.elements[0].y = m44.columns[1][0];
- b.elements[1].y = m44.columns[1][1];
- b.elements[2].y = m44.columns[1][2];
- b.elements[0].z = m44.columns[2][0];
- b.elements[1].z = m44.columns[2][1];
- b.elements[2].z = m44.columns[2][2];
- tracker->set_orientation(b);
- tracker->set_rw_position(Vector3(m44.columns[3][0], m44.columns[3][1], m44.columns[3][2]));
}
}
void ARKitInterface::_remove_anchor(void *p_anchor) {
- _THREAD_SAFE_METHOD_
+ // _THREAD_SAFE_METHOD_
- ARAnchor *anchor = (ARAnchor *)p_anchor;
+ if (@available(iOS 11.0, *)) {
+ ARAnchor *anchor = (ARAnchor *)p_anchor;
- unsigned char uuid[16];
- [anchor.identifier getUUIDBytes:uuid];
+ unsigned char uuid[16];
+ [anchor.identifier getUUIDBytes:uuid];
- remove_anchor_for_uuid(uuid);
+ remove_anchor_for_uuid(uuid);
+ }
}
ARKitInterface::ARKitInterface() {
@@ -728,7 +767,9 @@ ARKitInterface::ARKitInterface() {
session_was_started = false;
plane_detection_is_enabled = false;
light_estimation_is_enabled = false;
- ar_session = NULL;
+ if (@available(iOS 11.0, *)) {
+ ar_session = NULL;
+ }
z_near = 0.01;
z_far = 1000.0;
projection.set_perspective(60.0, 1.0, z_near, z_far, false);
diff --git a/modules/arkit/arkit_session_delegate.h b/modules/arkit/arkit_session_delegate.h
index 158b80a60a..df98bf506e 100644
--- a/modules/arkit/arkit_session_delegate.h
+++ b/modules/arkit/arkit_session_delegate.h
@@ -42,9 +42,9 @@ class ARKitInterface;
@property(nonatomic) ARKitInterface *arkit_interface;
-- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor *> *)anchors;
-- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor *> *)anchors;
-- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor *> *)anchors;
+- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
+- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
+- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
@end
#endif /* !ARKIT_SESSION_DELEGATE_H */
diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp
index 70e137b16d..9dc307c629 100644
--- a/modules/bullet/space_bullet.cpp
+++ b/modules/bullet/space_bullet.cpp
@@ -158,10 +158,6 @@ bool BulletPhysicsDirectSpaceState::cast_motion(const RID &p_shape, const Transf
btVector3 bt_motion;
G_TO_B(p_motion, bt_motion);
- if (bt_motion.fuzzyZero()) {
- return false;
- }
-
ShapeBullet *shape = space->get_physics_server()->get_shape_owner()->getornull(p_shape);
ERR_FAIL_COND_V(!shape, false);
@@ -180,6 +176,10 @@ bool BulletPhysicsDirectSpaceState::cast_motion(const RID &p_shape, const Transf
btTransform bt_xform_to(bt_xform_from);
bt_xform_to.getOrigin() += bt_motion;
+ if ((bt_xform_to.getOrigin() - bt_xform_from.getOrigin()).fuzzyZero()) {
+ return false;
+ }
+
GodotClosestConvexResultCallback btResult(bt_xform_from.getOrigin(), bt_xform_to.getOrigin(), &p_exclude, p_collide_with_bodies, p_collide_with_areas);
btResult.m_collisionFilterGroup = 0;
btResult.m_collisionFilterMask = p_collision_mask;
@@ -982,7 +982,7 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f
motionVec->end();
#endif
- for (int shIndex = 0; shIndex < shape_count && !motion.fuzzyZero(); ++shIndex) {
+ for (int shIndex = 0; shIndex < shape_count; ++shIndex) {
if (p_body->is_shape_disabled(shIndex)) {
continue;
}
@@ -1004,6 +1004,11 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f
btTransform shape_world_to(shape_world_from);
shape_world_to.getOrigin() += motion;
+ if ((shape_world_to.getOrigin() - shape_world_from.getOrigin()).fuzzyZero()) {
+ motion = btVector3(0, 0, 0);
+ break;
+ }
+
GodotKinClosestConvexResultCallback btResult(shape_world_from.getOrigin(), shape_world_to.getOrigin(), p_body, p_infinite_inertia);
btResult.m_collisionFilterGroup = p_body->get_collision_layer();
btResult.m_collisionFilterMask = p_body->get_collision_mask();
diff --git a/modules/camera/camera_ios.mm b/modules/camera/camera_ios.mm
index f01135f251..c10b13b2af 100644
--- a/modules/camera/camera_ios.mm
+++ b/modules/camera/camera_ios.mm
@@ -158,25 +158,31 @@
} else if (dataCbCr == NULL) {
print_line("Couldn't access CbCr pixel buffer data");
} else {
- UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
+ UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown;
+
+ if (@available(iOS 13, *)) {
+ orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ orientation = [[UIApplication sharedApplication] statusBarOrientation];
+#endif
+ }
+
Ref<Image> img[2];
{
// do Y
- int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
- int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
- int _bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
+ size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
+ size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
if ((width[0] != new_width) || (height[0] != new_height)) {
- // printf("Camera Y plane %i, %i - %i\n", new_width, new_height, bytes_per_row);
-
width[0] = new_width;
height[0] = new_height;
img_data[0].resize(new_width * new_height);
}
uint8_t *w = img_data[0].ptrw();
- memcpy(w.ptr(), dataY, new_width * new_height);
+ memcpy(w, dataY, new_width * new_height);
img[0].instance();
img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
@@ -184,20 +190,17 @@
{
// do CbCr
- int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
- int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
- int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
+ size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
+ size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
if ((width[1] != new_width) || (height[1] != new_height)) {
- // printf("Camera CbCr plane %i, %i - %i\n", new_width, new_height, bytes_per_row);
-
width[1] = new_width;
height[1] = new_height;
img_data[1].resize(2 * new_width * new_height);
}
uint8_t *w = img_data[1].ptrw();
- memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height);
+ memcpy(w, dataCbCr, 2 * new_width * new_height);
///TODO GLES2 doesn't support FORMAT_RG8, need to do some form of conversion
img[1].instance();
@@ -359,41 +362,59 @@ void CameraIOS::update_feeds() {
// this way of doing things is deprecated but still works,
// rewrite to using AVCaptureDeviceDiscoverySession
- AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeBuiltInTelephotoCamera, AVCaptureDeviceTypeBuiltInDualCamera, AVCaptureDeviceTypeBuiltInTrueDepthCamera, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
+ NSMutableArray *deviceTypes = [NSMutableArray array];
- // remove devices that are gone..
- for (int i = feeds.size() - 1; i >= 0; i--) {
- Ref<CameraFeedIOS> feed(feeds[i]);
+ if (@available(iOS 10, *)) {
+ [deviceTypes addObject:AVCaptureDeviceTypeBuiltInWideAngleCamera];
+ [deviceTypes addObject:AVCaptureDeviceTypeBuiltInTelephotoCamera];
- if (feed.is_null()) {
- // feed not managed by us
- } else if (![session.devices containsObject:feed->get_device()]) {
- // remove it from our array, this will also destroy it ;)
- remove_feed(feed);
- };
- };
+ if (@available(iOS 10.2, *)) {
+ [deviceTypes addObject:AVCaptureDeviceTypeBuiltInDualCamera];
+ }
- // add new devices..
- for (AVCaptureDevice *device in session.devices) {
- bool found = false;
+ if (@available(iOS 11.1, *)) {
+ [deviceTypes addObject:AVCaptureDeviceTypeBuiltInTrueDepthCamera];
+ }
+
+ AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
+ discoverySessionWithDeviceTypes:deviceTypes
+ mediaType:AVMediaTypeVideo
+ position:AVCaptureDevicePositionUnspecified];
- for (int i = 0; i < feeds.size() && !found; i++) {
+ // remove devices that are gone..
+ for (int i = feeds.size() - 1; i >= 0; i--) {
Ref<CameraFeedIOS> feed(feeds[i]);
if (feed.is_null()) {
// feed not managed by us
- } else if (feed->get_device() == device) {
- found = true;
+ } else if (![session.devices containsObject:feed->get_device()]) {
+ // remove it from our array, this will also destroy it ;)
+ remove_feed(feed);
};
};
- if (!found) {
- Ref<CameraFeedIOS> newfeed;
- newfeed.instance();
- newfeed->set_device(device);
- add_feed(newfeed);
+ // add new devices..
+ for (AVCaptureDevice *device in session.devices) {
+ bool found = false;
+
+ for (int i = 0; i < feeds.size() && !found; i++) {
+ Ref<CameraFeedIOS> feed(feeds[i]);
+
+ if (feed.is_null()) {
+ // feed not managed by us
+ } else if (feed->get_device() == device) {
+ found = true;
+ };
+ };
+
+ if (!found) {
+ Ref<CameraFeedIOS> newfeed;
+ newfeed.instance();
+ newfeed->set_device(device);
+ add_feed(newfeed);
+ };
};
- };
+ }
};
CameraIOS::CameraIOS() {
diff --git a/modules/gdnative/gdnative/aabb.cpp b/modules/gdnative/gdnative/aabb.cpp
index 7f22c7dfe3..d5970e8004 100644
--- a/modules/gdnative/gdnative/aabb.cpp
+++ b/modules/gdnative/gdnative/aabb.cpp
@@ -81,6 +81,13 @@ godot_string GDAPI godot_aabb_as_string(const godot_aabb *p_self) {
return ret;
}
+godot_aabb GDAPI godot_aabb_abs(const godot_aabb *p_self) {
+ godot_aabb dest;
+ const AABB *self = (const AABB *)p_self;
+ *((AABB *)&dest) = self->abs();
+ return dest;
+}
+
godot_real GDAPI godot_aabb_get_area(const godot_aabb *p_self) {
const AABB *self = (const AABB *)p_self;
return self->get_area();
diff --git a/modules/gdnative/gdnative/plane.cpp b/modules/gdnative/gdnative/plane.cpp
index 663937f906..d4ed8d00f4 100644
--- a/modules/gdnative/gdnative/plane.cpp
+++ b/modules/gdnative/gdnative/plane.cpp
@@ -79,13 +79,6 @@ godot_vector3 GDAPI godot_plane_center(const godot_plane *p_self) {
return dest;
}
-godot_vector3 GDAPI godot_plane_get_any_point(const godot_plane *p_self) {
- godot_vector3 dest;
- const Plane *self = (const Plane *)p_self;
- *((Vector3 *)&dest) = self->get_any_point();
- return dest;
-}
-
godot_bool GDAPI godot_plane_is_point_over(const godot_plane *p_self, const godot_vector3 *p_point) {
const Plane *self = (const Plane *)p_self;
const Vector3 *point = (const Vector3 *)p_point;
diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json
index eb122089b6..9852928d22 100644
--- a/modules/gdnative/gdnative_api.json
+++ b/modules/gdnative/gdnative_api.json
@@ -54,6 +54,13 @@
]
},
{
+ "name": "godot_aabb_abs",
+ "return_type": "godot_aabb",
+ "arguments": [
+ ["const godot_aabb *", "p_self"]
+ ]
+ },
+ {
"name": "godot_aabb_get_area",
"return_type": "godot_real",
"arguments": [
@@ -2981,13 +2988,6 @@
]
},
{
- "name": "godot_plane_get_any_point",
- "return_type": "godot_vector3",
- "arguments": [
- ["const godot_plane *", "p_self"]
- ]
- },
- {
"name": "godot_plane_is_point_over",
"return_type": "godot_bool",
"arguments": [
diff --git a/modules/gdnative/include/gdnative/aabb.h b/modules/gdnative/include/gdnative/aabb.h
index 9f41e9d4c6..c776297944 100644
--- a/modules/gdnative/include/gdnative/aabb.h
+++ b/modules/gdnative/include/gdnative/aabb.h
@@ -69,6 +69,8 @@ void GDAPI godot_aabb_set_size(const godot_aabb *p_self, const godot_vector3 *p_
godot_string GDAPI godot_aabb_as_string(const godot_aabb *p_self);
+godot_aabb GDAPI godot_aabb_abs(const godot_aabb *p_self);
+
godot_real GDAPI godot_aabb_get_area(const godot_aabb *p_self);
godot_bool GDAPI godot_aabb_has_no_area(const godot_aabb *p_self);
diff --git a/modules/gdnative/include/gdnative/plane.h b/modules/gdnative/include/gdnative/plane.h
index b759a8cc1a..9843056489 100644
--- a/modules/gdnative/include/gdnative/plane.h
+++ b/modules/gdnative/include/gdnative/plane.h
@@ -68,8 +68,6 @@ godot_plane GDAPI godot_plane_normalized(const godot_plane *p_self);
godot_vector3 GDAPI godot_plane_center(const godot_plane *p_self);
-godot_vector3 GDAPI godot_plane_get_any_point(const godot_plane *p_self);
-
godot_bool GDAPI godot_plane_is_point_over(const godot_plane *p_self, const godot_vector3 *p_point);
godot_real GDAPI godot_plane_distance_to(const godot_plane *p_self, const godot_vector3 *p_point);
diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp
index d1b1513ac3..3a2d0b09a3 100644
--- a/modules/gdnative/register_types.cpp
+++ b/modules/gdnative/register_types.cpp
@@ -171,7 +171,7 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty
String entry_lib_path = config->get_value("entry", key);
if (entry_lib_path.begins_with("res://") && entry_lib_path.ends_with(".a")) {
// If we find static library that was used for export
- // we should add a fake loopup table.
+ // we should add a fake lookup table.
// In case of dynamic library being used,
// this symbols will not cause any issues with library loading.
should_fake_dynamic = true;
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index f04cb4b4c3..36de66ea52 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -52,7 +52,7 @@
<argument index="0" name="s" type="float">
</argument>
<description>
- Returns the absolute value of parameter [code]s[/code] (i.e. unsigned value, works for integer and float).
+ Returns the absolute value of parameter [code]s[/code] (i.e. positive value).
[codeblock]
# a is 1
a = abs(-1)
@@ -112,7 +112,7 @@
</argument>
<description>
Returns the arc tangent of [code]s[/code] in radians. Use it to get the angle from an angle's tangent in trigonometry: [code]atan(tan(angle)) == angle[/code].
- The method cannot know in which quadrant the angle should fall. See [method atan2] if you always want an exact angle.
+ The method cannot know in which quadrant the angle should fall. See [method atan2] if you have both [code]y[code] and [code]x[/code].
[codeblock]
a = atan(0.5) # a is 0.463648
[/codeblock]
@@ -127,6 +127,7 @@
</argument>
<description>
Returns the arc tangent of [code]y/x[/code] in radians. Use to get the angle of tangent [code]y/x[/code]. To compute the value, the method takes into account the sign of both arguments in order to determine the quadrant.
+ Important note: The Y coordinate comes first, by convention.
[codeblock]
a = atan2(0, -1) # a is 3.141593
[/codeblock]
@@ -161,7 +162,7 @@
<argument index="0" name="s" type="float">
</argument>
<description>
- Rounds [code]s[/code] upward, returning the smallest integral value that is not less than [code]s[/code].
+ Rounds [code]s[/code] upward (towards positive infinity), returning the smallest whole number that is not less than [code]s[/code].
[codeblock]
i = ceil(1.45) # i is 2
i = ceil(1.001) # i is 2
@@ -283,7 +284,7 @@
<argument index="0" name="deg" type="float">
</argument>
<description>
- Returns degrees converted to radians.
+ Converts an angle expressed in degrees to radians.
[codeblock]
# r is 3.141593
r = deg2rad(180)
@@ -307,7 +308,7 @@
<argument index="1" name="curve" type="float">
</argument>
<description>
- Easing function, based on exponent. 0 is constant, 1 is linear, 0 to 1 is ease-in, 1+ is ease out. Negative values are in-out/out in.
+ Easing function, based on exponent. The curve values are: 0 is constant, 1 is linear, 0 to 1 is ease-in, 1+ is ease out. Negative values are in-out/out in.
</description>
</method>
<method name="exp">
@@ -330,7 +331,7 @@
<argument index="0" name="s" type="float">
</argument>
<description>
- Rounds [code]s[/code] to the closest smaller integer and returns it.
+ Rounds [code]s[/code] downward (towards negative infinity), returning the largest whole number that is not more than [code]s[/code].
[codeblock]
# a is 2.0
a = floor(2.99)
@@ -530,7 +531,7 @@
<argument index="0" name="s" type="float">
</argument>
<description>
- Returns whether [code]s[/code] is a NaN (Not-A-Number) value.
+ Returns whether [code]s[/code] is a NaN ("Not a Number" or invalid) value.
</description>
</method>
<method name="is_zero_approx">
@@ -540,6 +541,7 @@
</argument>
<description>
Returns [code]true[/code] if [code]s[/code] is zero or almost zero.
+ This method is faster than using [method is_equal_approx] with one value as zero.
</description>
</method>
<method name="len">
@@ -907,7 +909,7 @@
<argument index="0" name="rad" type="float">
</argument>
<description>
- Converts from radians to degrees.
+ Converts an angle expressed in radians to degrees.
[codeblock]
rad2deg(0.523599) # Returns 30
[/codeblock]
@@ -1026,7 +1028,7 @@
<argument index="0" name="s" type="float">
</argument>
<description>
- Returns the integral value that is nearest to [code]s[/code], with halfway cases rounded away from zero.
+ Rounds [code]s[/code] to the nearest whole number, with halfway cases rounded away from zero.
[codeblock]
round(2.6) # Returns 3
[/codeblock]
@@ -1108,10 +1110,11 @@
<argument index="0" name="s" type="float">
</argument>
<description>
- Returns the square root of [code]s[/code].
+ Returns the square root of [code]s[/code], where [code]s[/code] is a non-negative number.
[codeblock]
sqrt(9) # Returns 3
[/codeblock]
+ If you need negative inputs, use [code]System.Numerics.Complex[/code] in C#.
</description>
</method>
<method name="step_decimals">
@@ -1312,27 +1315,19 @@
Wraps float [code]value[/code] between [code]min[/code] and [code]max[/code].
Usable for creating loop-alike behavior or infinite surfaces.
[codeblock]
- # a is 0.5
- a = wrapf(10.5, 0.0, 10.0)
- [/codeblock]
- [codeblock]
- # a is 9.5
- a = wrapf(-0.5, 0.0, 10.0)
- [/codeblock]
- [codeblock]
- # Infinite loop between 0.0 and 0.99
- f = wrapf(f + 0.1, 0.0, 1.0)
+ # Infinite loop between 5.0 and 9.9
+ value = wrapf(value + 0.1, 5.0, 10.0)
[/codeblock]
[codeblock]
# Infinite rotation (in radians)
angle = wrapf(angle + 0.1, 0.0, TAU)
[/codeblock]
- [b]Note:[/b] If you just want to wrap between 0.0 and [code]n[/code] (where [code]n[/code] is a positive floating-point value), it is better for performance to use the [method fmod] method like [code]fmod(number, n)[/code].
- [code]wrapf[/code] is more flexible than using the [method fmod] approach by giving the user a simple control over the minimum value. It also fully supports negative numbers, e.g.
[codeblock]
# Infinite rotation (in radians)
angle = wrapf(angle + 0.1, -PI, PI)
[/codeblock]
+ [b]Note:[/b] If [code]min[/code] is [code]0[/code], this is equivalent to [method fposmod], so prefer using that instead.
+ [code]wrapf[/code] is more flexible than using the [method fposmod] approach by giving the user control over the minimum value.
</description>
</method>
<method name="wrapi">
@@ -1348,23 +1343,15 @@
Wraps integer [code]value[/code] between [code]min[/code] and [code]max[/code].
Usable for creating loop-alike behavior or infinite surfaces.
[codeblock]
- # a is 0
- a = wrapi(10, 0, 10)
- [/codeblock]
- [codeblock]
- # a is 9
- a = wrapi(-1, 0, 10)
- [/codeblock]
- [codeblock]
- # Infinite loop between 0 and 9
- frame = wrapi(frame + 1, 0, 10)
+ # Infinite loop between 5 and 9
+ frame = wrapi(frame + 1, 5, 10)
[/codeblock]
- [b]Note:[/b] If you just want to wrap between 0 and [code]n[/code] (where [code]n[/code] is a positive integer value), it is better for performance to use the modulo operator like [code]number % n[/code].
- [code]wrapi[/code] is more flexible than using the modulo approach by giving the user a simple control over the minimum value. It also fully supports negative numbers, e.g.
[codeblock]
# result is -2
var result = wrapi(-6, -5, -1)
[/codeblock]
+ [b]Note:[/b] If [code]min[/code] is [code]0[/code], this is equivalent to [method posmod], so prefer using that instead.
+ [code]wrapi[/code] is more flexible than using the [method posmod] approach by giving the user control over the minimum value.
</description>
</method>
<method name="yield">
@@ -1406,17 +1393,16 @@
</methods>
<constants>
<constant name="PI" value="3.141593">
- Constant that represents how many times the diameter of a circle fits around its perimeter.
+ Constant that represents how many times the diameter of a circle fits around its perimeter. This is equivalent to [code]TAU / 2[/code].
</constant>
<constant name="TAU" value="6.283185">
- The circle constant, the circumference of the unit circle.
+ The circle constant, the circumference of the unit circle in radians.
</constant>
<constant name="INF" value="inf">
- A positive infinity. (For negative infinity, use -INF).
+ Positive infinity. For negative infinity, use -INF.
</constant>
<constant name="NAN" value="nan">
- Macro constant that expands to an expression of type float that represents a NaN.
- The NaN values are used to identify undefined or non-representable values for floating-point elements, such as the square root of negative numbers or the result of 0/0.
+ "Not a Number", an invalid value. [code]NaN[/code] has special properties, including that it is not equal to itself. It is output by some invalid operations, such as dividing zero by zero.
</constant>
</constants>
</class>
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 43d0116125..aba3e07134 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -288,7 +288,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
if (str[k] == '(') {
in_function_name = true;
- } else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_VAR)) {
+ } else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR)) {
in_variable_declaration = true;
}
}
@@ -357,7 +357,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
} else if (in_function_name) {
next_type = FUNCTION;
- if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_FUNCTION)) {
+ if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
color = function_definition_color;
} else {
color = function_color;
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 632407c61f..40ef0aeec6 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -39,7 +39,11 @@
#include "core/os/file_access.h"
#include "core/os/os.h"
#include "core/project_settings.h"
+#include "gdscript_analyzer.h"
+#include "gdscript_cache.h"
#include "gdscript_compiler.h"
+#include "gdscript_parser.h"
+#include "gdscript_warning.h"
///////////////////////////
@@ -79,6 +83,17 @@ Object *GDScriptNativeClass::instance() {
return ClassDB::instance(name);
}
+void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) {
+ GDScript *base = p_script->_base;
+ if (base != nullptr) {
+ _super_implicit_constructor(base, p_instance, r_error);
+ if (r_error.error != Callable::CallError::CALL_OK) {
+ return;
+ }
+ }
+ p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error);
+}
+
GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error) {
/* STEP 1, CREATE */
@@ -101,10 +116,8 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
MutexLock lock(GDScriptLanguage::singleton->lock);
instances.insert(instance->owner);
}
- if (p_argcount < 0) {
- return instance;
- }
- initializer->call(instance, p_args, p_argcount, r_error);
+
+ _super_implicit_constructor(this, instance, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
instance->script = Ref<GDScript>();
instance->owner->set_script_instance(nullptr);
@@ -114,6 +127,22 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
}
ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
}
+
+ if (p_argcount < 0) {
+ return instance;
+ }
+ if (initializer != nullptr) {
+ initializer->call(instance, p_args, p_argcount, r_error);
+ if (r_error.error != Callable::CallError::CALL_OK) {
+ instance->script = Ref<GDScript>();
+ instance->owner->set_script_instance(nullptr);
+ {
+ MutexLock lock(GDScriptLanguage::singleton->lock);
+ instances.erase(p_owner);
+ }
+ ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
+ }
+ }
//@TODO make thread safe
return instance;
}
@@ -382,13 +411,11 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
}
GDScriptParser parser;
- Error err = parser.parse(source, basedir, true, path);
+ GDScriptAnalyzer analyzer(&parser);
+ Error err = parser.parse(source, path, false);
- if (err == OK) {
- const GDScriptParser::Node *root = parser.get_parse_tree();
- ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, false);
-
- const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(root);
+ if (err == OK && analyzer.analyze() == OK) {
+ const GDScriptParser::ClassNode *c = parser.get_tree();
if (base_cache.is_valid()) {
base_cache->inheriters_cache.erase(get_instance_id());
@@ -397,8 +424,8 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
if (c->extends_used) {
String path = "";
- if (String(c->extends_file) != "" && String(c->extends_file) != get_path()) {
- path = c->extends_file;
+ if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) {
+ path = c->extends_path;
if (path.is_rel_path()) {
String base = get_path();
if (base == "" || base.is_rel_path()) {
@@ -407,8 +434,8 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
path = base.get_base_dir().plus_file(path);
}
}
- } else if (c->extends_class.size() != 0) {
- String base = c->extends_class[0];
+ } else if (c->extends.size() != 0) {
+ const StringName &base = c->extends[0];
if (ScriptServer::is_global_class(base)) {
path = ScriptServer::get_global_class_path(base);
@@ -431,20 +458,36 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
members_cache.clear();
member_default_values_cache.clear();
+ _signals.clear();
- for (int i = 0; i < c->variables.size(); i++) {
- if (c->variables[i]._export.type == Variant::NIL) {
- continue;
- }
-
- members_cache.push_back(c->variables[i]._export);
- member_default_values_cache[c->variables[i].identifier] = c->variables[i].default_value;
- }
+ for (int i = 0; i < c->members.size(); i++) {
+ const GDScriptParser::ClassNode::Member &member = c->members[i];
- _signals.clear();
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::VARIABLE: {
+ if (!member.variable->exported) {
+ continue;
+ }
- for (int i = 0; i < c->_signals.size(); i++) {
- _signals[c->_signals[i].name] = c->_signals[i].arguments;
+ members_cache.push_back(member.variable->export_info);
+ Variant default_value;
+ if (member.variable->initializer && member.variable->initializer->is_constant) {
+ default_value = member.variable->initializer->reduced_value;
+ }
+ member_default_values_cache[member.variable->identifier->name] = default_value;
+ } break;
+ case GDScriptParser::ClassNode::Member::SIGNAL: {
+ // TODO: Cache this in parser to avoid loops like this.
+ Vector<StringName> parameters_names;
+ parameters_names.resize(member.signal->parameters.size());
+ for (int j = 0; j < member.signal->parameters.size(); j++) {
+ parameters_names.write[j] = member.signal->parameters[j]->identifier->name;
+ }
+ _signals[member.signal->identifier->name] = parameters_names;
+ } break;
+ default:
+ break; // Nothing.
+ }
}
} else {
placeholder_fallback_enabled = true;
@@ -555,16 +598,29 @@ Error GDScript::reload(bool p_keep_state) {
valid = false;
GDScriptParser parser;
- Error err = parser.parse(source, basedir, false, path);
+ Error err = parser.parse(source, path, false);
+ if (err) {
+ if (EngineDebugger::is_active()) {
+ GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message);
+ }
+ // TODO: Show all error messages.
+ _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT);
+ ERR_FAIL_V(ERR_PARSE_ERROR);
+ }
+
+ GDScriptAnalyzer analyzer(&parser);
+ err = analyzer.analyze();
+
if (err) {
if (EngineDebugger::is_active()) {
- GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_error_line(), "Parser Error: " + parser.get_error());
+ GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message);
}
- _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_error_line(), ("Parse Error: " + parser.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT);
+ // TODO: Show all error messages.
+ _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT);
ERR_FAIL_V(ERR_PARSE_ERROR);
}
- bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool_script();
+ bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool();
GDScriptCompiler compiler;
err = compiler.compile(&parser, this, p_keep_state);
@@ -585,7 +641,7 @@ Error GDScript::reload(bool p_keep_state) {
const GDScriptWarning &warning = E->get();
if (EngineDebugger::is_active()) {
Vector<ScriptLanguage::StackInfo> si;
- EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si);
+ EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.start_line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si);
}
}
#endif
@@ -753,83 +809,12 @@ void GDScript::_bind_methods() {
}
Vector<uint8_t> GDScript::get_as_byte_code() const {
- GDScriptTokenizerBuffer tokenizer;
- return tokenizer.parse_code_string(source);
+ return Vector<uint8_t>();
};
+// TODO: Fully remove this. There's not this kind of "bytecode" anymore.
Error GDScript::load_byte_code(const String &p_path) {
- Vector<uint8_t> bytecode;
-
- if (p_path.ends_with("gde")) {
- FileAccess *fa = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V(!fa, ERR_CANT_OPEN);
-
- FileAccessEncrypted *fae = memnew(FileAccessEncrypted);
- ERR_FAIL_COND_V(!fae, ERR_CANT_OPEN);
-
- Vector<uint8_t> key;
- key.resize(32);
- for (int i = 0; i < key.size(); i++) {
- key.write[i] = script_encryption_key[i];
- }
-
- Error err = fae->open_and_parse(fa, key, FileAccessEncrypted::MODE_READ);
-
- if (err) {
- fa->close();
- memdelete(fa);
- memdelete(fae);
-
- ERR_FAIL_COND_V(err, err);
- }
-
- bytecode.resize(fae->get_len());
- fae->get_buffer(bytecode.ptrw(), bytecode.size());
- fae->close();
- memdelete(fae);
-
- } else {
- bytecode = FileAccess::get_file_as_array(p_path);
- }
-
- ERR_FAIL_COND_V(bytecode.size() == 0, ERR_PARSE_ERROR);
- path = p_path;
-
- String basedir = path;
-
- if (basedir == "") {
- basedir = get_path();
- }
-
- if (basedir != "") {
- basedir = basedir.get_base_dir();
- }
-
- valid = false;
- GDScriptParser parser;
- Error err = parser.parse_bytecode(bytecode, basedir, get_path());
- if (err) {
- _err_print_error("GDScript::load_byte_code", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_error_line(), ("Parse Error: " + parser.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT);
- ERR_FAIL_V(ERR_PARSE_ERROR);
- }
-
- GDScriptCompiler compiler;
- err = compiler.compile(&parser, this);
-
- if (err) {
- _err_print_error("GDScript::load_byte_code", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT);
- ERR_FAIL_V(ERR_COMPILATION_FAILED);
- }
-
- valid = true;
-
- for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) {
- _set_subclass_path(E->get(), path);
- }
-
- _init_rpc_methods_properties();
-
- return OK;
+ return ERR_COMPILATION_FAILED;
}
Error GDScript::load_source_code(const String &p_path) {
@@ -1055,6 +1040,8 @@ GDScript::~GDScript() {
memdelete(E->get());
}
+ GDScriptCache::remove_script(get_path());
+
_save_orphaned_subclasses();
#ifdef DEBUG_ENABLED
@@ -1153,6 +1140,32 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
}
{
+ // Signals.
+ const GDScript *sl = sptr;
+ while (sl) {
+ const Map<StringName, Vector<StringName>>::Element *E = sl->_signals.find(p_name);
+ if (E) {
+ r_ret = Signal(this->owner, E->key());
+ return true; //index found
+ }
+ sl = sl->_base;
+ }
+ }
+
+ {
+ // Methods.
+ const GDScript *sl = sptr;
+ while (sl) {
+ const Map<StringName, GDScriptFunction *>::Element *E = sl->member_functions.find(p_name);
+ if (E) {
+ r_ret = Callable(this->owner, E->key());
+ return true; //index found
+ }
+ sl = sl->_base;
+ }
+ }
+
+ {
const Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get);
if (E) {
Variant name = p_name;
@@ -1304,6 +1317,7 @@ void GDScriptInstance::call_multilevel(const StringName &p_method, const Variant
Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method);
if (E) {
E->get()->call(this, p_args, p_argcount, ce);
+ return;
}
sptr = sptr->_base;
}
@@ -1827,6 +1841,7 @@ void GDScriptLanguage::frame() {
/* EDITOR FUNCTIONS */
void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
+ // TODO: Add annotations here?
static const char *_reserved_words[] = {
// operators
"and",
@@ -1849,6 +1864,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
// functions
"as",
"assert",
+ "await",
"breakpoint",
"class",
"class_name",
@@ -1856,15 +1872,13 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
"is",
"func",
"preload",
- "setget",
"signal",
- "tool",
+ "super",
+ "trait",
"yield",
// var
"const",
"enum",
- "export",
- "onready",
"static",
"var",
// control flow
@@ -1878,12 +1892,6 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
"return",
"match",
"while",
- "remote",
- "master",
- "puppet",
- "remotesync",
- "mastersync",
- "puppetsync",
nullptr
};
@@ -1914,10 +1922,11 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
String source = f->get_as_utf8_string();
GDScriptParser parser;
- parser.parse(source, p_path.get_base_dir(), true, p_path, false, nullptr, true);
+ err = parser.parse(source, p_path, false);
- if (parser.get_parse_tree() && parser.get_parse_tree()->type == GDScriptParser::Node::TYPE_CLASS) {
- const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(parser.get_parse_tree());
+ // TODO: Simplify this code by using the analyzer to get full inheritance.
+ if (err == OK) {
+ const GDScriptParser::ClassNode *c = parser.get_tree();
if (r_icon_path) {
if (c->icon_path.empty() || c->icon_path.is_abs_path()) {
*r_icon_path = c->icon_path;
@@ -1931,15 +1940,15 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
GDScriptParser subparser;
while (subclass) {
if (subclass->extends_used) {
- if (subclass->extends_file) {
- if (subclass->extends_class.size() == 0) {
- get_global_class_name(subclass->extends_file, r_base_type);
+ if (!subclass->extends_path.empty()) {
+ if (subclass->extends.size() == 0) {
+ get_global_class_name(subclass->extends_path, r_base_type);
subclass = nullptr;
break;
} else {
- Vector<StringName> extend_classes = subclass->extends_class;
+ Vector<StringName> extend_classes = subclass->extends;
- FileAccessRef subfile = FileAccess::open(subclass->extends_file, FileAccess::READ);
+ FileAccessRef subfile = FileAccess::open(subclass->extends_path, FileAccess::READ);
if (!subfile) {
break;
}
@@ -1948,25 +1957,26 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
if (subsource.empty()) {
break;
}
- String subpath = subclass->extends_file;
+ String subpath = subclass->extends_path;
if (subpath.is_rel_path()) {
subpath = path.get_base_dir().plus_file(subpath).simplify_path();
}
- if (OK != subparser.parse(subsource, subpath.get_base_dir(), true, subpath, false, nullptr, true)) {
+ if (OK != subparser.parse(subsource, subpath, false)) {
break;
}
path = subpath;
- if (!subparser.get_parse_tree() || subparser.get_parse_tree()->type != GDScriptParser::Node::TYPE_CLASS) {
- break;
- }
- subclass = static_cast<const GDScriptParser::ClassNode *>(subparser.get_parse_tree());
+ subclass = subparser.get_tree();
while (extend_classes.size() > 0) {
bool found = false;
- for (int i = 0; i < subclass->subclasses.size(); i++) {
- const GDScriptParser::ClassNode *inner_class = subclass->subclasses[i];
- if (inner_class->name == extend_classes[0]) {
+ for (int i = 0; i < subclass->members.size(); i++) {
+ if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
+ continue;
+ }
+
+ const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class;
+ if (inner_class->identifier->name == extend_classes[0]) {
extend_classes.remove(0);
found = true;
subclass = inner_class;
@@ -1979,8 +1989,8 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
}
}
}
- } else if (subclass->extends_class.size() == 1) {
- *r_base_type = subclass->extends_class[0];
+ } else if (subclass->extends.size() == 1) {
+ *r_base_type = subclass->extends[0];
subclass = nullptr;
} else {
break;
@@ -1991,181 +2001,12 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
}
}
}
- return c->name;
+ return c->identifier != nullptr ? String(c->identifier->name) : String();
}
return String();
}
-#ifdef DEBUG_ENABLED
-String GDScriptWarning::get_message() const {
-#define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String());
-
- switch (code) {
- case UNASSIGNED_VARIABLE_OP_ASSIGN: {
- CHECK_SYMBOLS(1);
- return "Using assignment with operation but the variable '" + symbols[0] + "' was not previously assigned a value.";
- } break;
- case UNASSIGNED_VARIABLE: {
- CHECK_SYMBOLS(1);
- return "The variable '" + symbols[0] + "' was used but never assigned a value.";
- } break;
- case UNUSED_VARIABLE: {
- CHECK_SYMBOLS(1);
- return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'";
- } break;
- case SHADOWED_VARIABLE: {
- CHECK_SYMBOLS(2);
- return "The local variable '" + symbols[0] + "' is shadowing an already-defined variable at line " + symbols[1] + ".";
- } break;
- case UNUSED_CLASS_VARIABLE: {
- CHECK_SYMBOLS(1);
- return "The class variable '" + symbols[0] + "' is declared but never used in the script.";
- } break;
- case UNUSED_ARGUMENT: {
- CHECK_SYMBOLS(2);
- return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'";
- } break;
- case UNREACHABLE_CODE: {
- CHECK_SYMBOLS(1);
- return "Unreachable code (statement after return) in function '" + symbols[0] + "()'.";
- } break;
- case STANDALONE_EXPRESSION: {
- return "Standalone expression (the line has no effect).";
- } break;
- case VOID_ASSIGNMENT: {
- CHECK_SYMBOLS(1);
- return "Assignment operation, but the function '" + symbols[0] + "()' returns void.";
- } break;
- case NARROWING_CONVERSION: {
- return "Narrowing conversion (float is converted to int and loses precision).";
- } break;
- case FUNCTION_MAY_YIELD: {
- CHECK_SYMBOLS(1);
- return "Assigned variable is typed but the function '" + symbols[0] + "()' may yield and return a GDScriptFunctionState instead.";
- } break;
- case VARIABLE_CONFLICTS_FUNCTION: {
- CHECK_SYMBOLS(1);
- return "Variable declaration of '" + symbols[0] + "' conflicts with a function of the same name.";
- } break;
- case FUNCTION_CONFLICTS_VARIABLE: {
- CHECK_SYMBOLS(1);
- return "Function declaration of '" + symbols[0] + "()' conflicts with a variable of the same name.";
- } break;
- case FUNCTION_CONFLICTS_CONSTANT: {
- CHECK_SYMBOLS(1);
- return "Function declaration of '" + symbols[0] + "()' conflicts with a constant of the same name.";
- } break;
- case INCOMPATIBLE_TERNARY: {
- return "Values of the ternary conditional are not mutually compatible.";
- } break;
- case UNUSED_SIGNAL: {
- CHECK_SYMBOLS(1);
- return "The signal '" + symbols[0] + "' is declared but never emitted.";
- } break;
- case RETURN_VALUE_DISCARDED: {
- CHECK_SYMBOLS(1);
- return "The function '" + symbols[0] + "()' returns a value, but this value is never used.";
- } break;
- case PROPERTY_USED_AS_FUNCTION: {
- CHECK_SYMBOLS(2);
- return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a property with the same name. Did you mean to access it?";
- } break;
- case CONSTANT_USED_AS_FUNCTION: {
- CHECK_SYMBOLS(2);
- return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a constant with the same name. Did you mean to access it?";
- } break;
- case FUNCTION_USED_AS_PROPERTY: {
- CHECK_SYMBOLS(2);
- return "The property '" + symbols[0] + "' was not found in base '" + symbols[1] + "' but there's a method with the same name. Did you mean to call it?";
- } break;
- case INTEGER_DIVISION: {
- return "Integer division, decimal part will be discarded.";
- } break;
- case UNSAFE_PROPERTY_ACCESS: {
- CHECK_SYMBOLS(2);
- return "The property '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
- } break;
- case UNSAFE_METHOD_ACCESS: {
- CHECK_SYMBOLS(2);
- return "The method '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
- } break;
- case UNSAFE_CAST: {
- CHECK_SYMBOLS(1);
- return "The value is cast to '" + symbols[0] + "' but has an unknown type.";
- } break;
- case UNSAFE_CALL_ARGUMENT: {
- CHECK_SYMBOLS(4);
- return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided";
- } break;
- case DEPRECATED_KEYWORD: {
- CHECK_SYMBOLS(2);
- return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'.";
- } break;
- case STANDALONE_TERNARY: {
- return "Standalone ternary conditional operator: the return value is being discarded.";
- }
- case WARNING_MAX:
- break; // Can't happen, but silences warning
- }
- ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + ".");
-
-#undef CHECK_SYMBOLS
-}
-
-String GDScriptWarning::get_name() const {
- return get_name_from_code(code);
-}
-
-String GDScriptWarning::get_name_from_code(Code p_code) {
- ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String());
-
- static const char *names[] = {
- "UNASSIGNED_VARIABLE",
- "UNASSIGNED_VARIABLE_OP_ASSIGN",
- "UNUSED_VARIABLE",
- "SHADOWED_VARIABLE",
- "UNUSED_CLASS_VARIABLE",
- "UNUSED_ARGUMENT",
- "UNREACHABLE_CODE",
- "STANDALONE_EXPRESSION",
- "VOID_ASSIGNMENT",
- "NARROWING_CONVERSION",
- "FUNCTION_MAY_YIELD",
- "VARIABLE_CONFLICTS_FUNCTION",
- "FUNCTION_CONFLICTS_VARIABLE",
- "FUNCTION_CONFLICTS_CONSTANT",
- "INCOMPATIBLE_TERNARY",
- "UNUSED_SIGNAL",
- "RETURN_VALUE_DISCARDED",
- "PROPERTY_USED_AS_FUNCTION",
- "CONSTANT_USED_AS_FUNCTION",
- "FUNCTION_USED_AS_PROPERTY",
- "INTEGER_DIVISION",
- "UNSAFE_PROPERTY_ACCESS",
- "UNSAFE_METHOD_ACCESS",
- "UNSAFE_CAST",
- "UNSAFE_CALL_ARGUMENT",
- "DEPRECATED_KEYWORD",
- "STANDALONE_TERNARY",
- nullptr
- };
-
- return names[(int)p_code];
-}
-
-GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) {
- for (int i = 0; i < WARNING_MAX; i++) {
- if (get_name_from_code((Code)i) == p_name) {
- return (Code)i;
- }
- }
-
- ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name);
-}
-
-#endif // DEBUG_ENABLED
-
GDScriptLanguage::GDScriptLanguage() {
calls = 0;
ERR_FAIL_COND(singleton);
@@ -2204,7 +2045,7 @@ GDScriptLanguage::GDScriptLanguage() {
GLOBAL_DEF("debug/gdscript/completion/autocomplete_setters_and_getters", false);
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
- bool default_enabled = !warning.begins_with("unsafe_") && i != GDScriptWarning::UNUSED_CLASS_VARIABLE;
+ bool default_enabled = !warning.begins_with("unsafe_");
GLOBAL_DEF("debug/gdscript/warnings/" + warning, default_enabled);
}
#endif // DEBUG_ENABLED
@@ -2242,36 +2083,28 @@ RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_ori
*r_error = ERR_FILE_CANT_OPEN;
}
- GDScript *script = memnew(GDScript);
-
- Ref<GDScript> scriptres(script);
-
- if (p_path.ends_with(".gde") || p_path.ends_with(".gdc")) {
- script->set_script_path(p_original_path); // script needs this.
- script->set_path(p_original_path);
- Error err = script->load_byte_code(p_path);
- ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load byte code from file '" + p_path + "'.");
-
- } else {
- Error err = script->load_source_code(p_path);
- ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load source code from file '" + p_path + "'.");
+ Error err;
+ Ref<GDScript> script = GDScriptCache::get_full_script(p_path, err);
- script->set_script_path(p_original_path); // script needs this.
- script->set_path(p_original_path);
+ // TODO: Reintroduce binary and encrypted scripts.
- script->reload();
+ if (script.is_null()) {
+ // Don't fail loading because of parsing error.
+ script.instance();
}
+
if (r_error) {
*r_error = OK;
}
- return scriptres;
+ return script;
}
void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("gd");
- p_extensions->push_back("gdc");
- p_extensions->push_back("gde");
+ // TODO: Reintroduce binary and encrypted scripts.
+ // p_extensions->push_back("gdc");
+ // p_extensions->push_back("gde");
}
bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
@@ -2280,7 +2113,8 @@ bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const {
String el = p_path.get_extension().to_lower();
- if (el == "gd" || el == "gdc" || el == "gde") {
+ // TODO: Reintroduce binary and encrypted scripts.
+ if (el == "gd" /*|| el == "gdc" || el == "gde"*/) {
return "GDScript";
}
return "";
@@ -2296,7 +2130,7 @@ void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<S
}
GDScriptParser parser;
- if (OK != parser.parse(source, p_path.get_base_dir(), true, p_path, false, nullptr, true)) {
+ if (OK != parser.parse(source, p_path, false)) {
return;
}
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 9a5de2c293..8236464f15 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -104,7 +104,8 @@ class GDScript : public Script {
#endif
Map<StringName, PropertyInfo> member_info;
- GDScriptFunction *initializer; //direct pointer to _init , faster to locate
+ GDScriptFunction *implicit_initializer = nullptr;
+ GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
int subclass_count;
Set<Object *> instances;
@@ -117,6 +118,7 @@ class GDScript : public Script {
SelfList<GDScriptFunctionState>::List pending_func_states;
+ void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error);
GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error);
void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path);
@@ -301,52 +303,6 @@ public:
~GDScriptInstance();
};
-#ifdef DEBUG_ENABLED
-struct GDScriptWarning {
- enum Code {
- UNASSIGNED_VARIABLE, // Variable used but never assigned
- UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc)
- UNUSED_VARIABLE, // Local variable is declared but never used
- SHADOWED_VARIABLE, // Variable name shadowed by other variable
- UNUSED_CLASS_VARIABLE, // Class variable is declared but never used in the file
- UNUSED_ARGUMENT, // Function argument is never used
- UNREACHABLE_CODE, // Code after a return statement
- STANDALONE_EXPRESSION, // Expression not assigned to a variable
- VOID_ASSIGNMENT, // Function returns void but it's assigned to a variable
- NARROWING_CONVERSION, // Float value into an integer slot, precision is lost
- FUNCTION_MAY_YIELD, // Typed assign of function call that yields (it may return a function state)
- VARIABLE_CONFLICTS_FUNCTION, // Variable has the same name of a function
- FUNCTION_CONFLICTS_VARIABLE, // Function has the same name of a variable
- FUNCTION_CONFLICTS_CONSTANT, // Function has the same name of a constant
- INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible
- UNUSED_SIGNAL, // Signal is defined but never emitted
- RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used
- PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name
- CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name
- FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name
- INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded
- UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes)
- UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes)
- UNSAFE_CAST, // Cast used in an unknown type
- UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument
- DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced
- STANDALONE_TERNARY, // Return value of ternary expression is discarded
- WARNING_MAX,
- };
-
- Code code = WARNING_MAX;
- Vector<String> symbols;
- int line = -1;
-
- String get_name() const;
- String get_message() const;
- static String get_name_from_code(Code p_code);
- static Code get_code_from_name(const String &p_name);
-
- GDScriptWarning() {}
-};
-#endif // DEBUG_ENABLED
-
class GDScriptLanguage : public ScriptLanguage {
friend class GDScriptFunctionState;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
new file mode 100644
index 0000000000..51d93481b6
--- /dev/null
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -0,0 +1,3062 @@
+/*************************************************************************/
+/* gdscript_analyzer.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_analyzer.h"
+
+#include "core/class_db.h"
+#include "core/hash_map.h"
+#include "core/io/resource_loader.h"
+#include "core/project_settings.h"
+#include "core/script_language.h"
+#include "gdscript.h"
+
+// TODO: Move this to a central location (maybe core?).
+static HashMap<StringName, StringName> underscore_map;
+static const char *underscore_classes[] = {
+ "ClassDB",
+ "Directory",
+ "Engine",
+ "File",
+ "Geometry",
+ "GodotSharp",
+ "JSON",
+ "Marshalls",
+ "Mutex",
+ "OS",
+ "ResourceLoader",
+ "ResourceSaver",
+ "Semaphore",
+ "Thread",
+ "VisualScriptEditor",
+ nullptr,
+};
+static StringName get_real_class_name(const StringName &p_source) {
+ if (underscore_map.empty()) {
+ const char **class_name = underscore_classes;
+ while (*class_name != nullptr) {
+ underscore_map[*class_name] = String("_") + *class_name;
+ class_name++;
+ }
+ }
+ if (underscore_map.has(p_source)) {
+ return underscore_map[p_source];
+ }
+ return p_source;
+}
+
+static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) {
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ type.kind = GDScriptParser::DataType::BUILTIN;
+ type.builtin_type = Variant::CALLABLE;
+ type.is_constant = true;
+ type.method_info = p_info;
+ return type;
+}
+
+static GDScriptParser::DataType make_signal_type(const MethodInfo &p_info) {
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ type.kind = GDScriptParser::DataType::BUILTIN;
+ type.builtin_type = Variant::SIGNAL;
+ type.is_constant = true;
+ type.method_info = p_info;
+ return type;
+}
+
+static GDScriptParser::DataType make_native_meta_type(const StringName &p_class_name) {
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ type.kind = GDScriptParser::DataType::NATIVE;
+ type.builtin_type = Variant::OBJECT;
+ type.is_constant = true;
+ type.native_type = p_class_name;
+ type.is_meta_type = true;
+ return type;
+}
+
+static GDScriptParser::DataType make_native_enum_type(const StringName &p_native_class, const StringName &p_enum_name) {
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ type.kind = GDScriptParser::DataType::ENUM;
+ type.builtin_type = Variant::OBJECT;
+ type.is_constant = true;
+ type.is_meta_type = true;
+
+ List<StringName> enum_values;
+ StringName real_native_name = get_real_class_name(p_native_class);
+ ClassDB::get_enum_constants(real_native_name, p_enum_name, &enum_values);
+
+ for (const List<StringName>::Element *E = enum_values.front(); E != nullptr; E = E->next()) {
+ type.enum_values[E->get()] = ClassDB::get_integer_constant(real_native_name, E->get());
+ }
+
+ return type;
+}
+
+static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) {
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ type.kind = GDScriptParser::DataType::BUILTIN;
+ type.builtin_type = p_type;
+ type.is_constant = true;
+ type.is_meta_type = true;
+ return type;
+}
+
+Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) {
+ if (p_class->base_type.is_set()) {
+ // Already resolved
+ return OK;
+ }
+
+ if (p_class == parser->head) {
+ if (p_class->identifier) {
+ p_class->fqcn = p_class->identifier->name;
+ } else {
+ p_class->fqcn = parser->script_path;
+ }
+ } else {
+ p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name);
+ }
+
+ GDScriptParser::DataType result;
+
+ // Set datatype for class.
+ GDScriptParser::DataType class_type;
+ class_type.is_constant = true;
+ class_type.is_meta_type = true;
+ class_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ class_type.kind = GDScriptParser::DataType::CLASS;
+ class_type.class_type = p_class;
+ class_type.script_path = parser->script_path;
+ p_class->set_datatype(class_type);
+
+ if (!p_class->extends_used) {
+ result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.native_type = "Reference";
+ } else {
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+
+ GDScriptParser::DataType base;
+
+ int extends_index = 0;
+
+ if (!p_class->extends_path.empty()) {
+ Ref<GDScriptParserRef> parser = get_parser_for(p_class->extends_path);
+ if (parser.is_null()) {
+ push_error(vformat(R"(Could not resolve super class path "%s".)", p_class->extends_path), p_class);
+ return ERR_PARSE_ERROR;
+ }
+
+ Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ if (err != OK) {
+ push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class);
+ return err;
+ }
+
+ base = parser->get_parser()->head->get_datatype();
+ } else {
+ if (p_class->extends.empty()) {
+ return ERR_PARSE_ERROR;
+ }
+ const StringName &name = p_class->extends[extends_index++];
+ base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+
+ if (ScriptServer::is_global_class(name)) {
+ String base_path = ScriptServer::get_global_class_path(name);
+
+ if (base_path == parser->script_path) {
+ base = parser->head->get_datatype();
+ } else {
+ Ref<GDScriptParserRef> parser = get_parser_for(base_path);
+ if (parser.is_null()) {
+ push_error(vformat(R"(Could not resolve super class "%s".)", name), p_class);
+ return ERR_PARSE_ERROR;
+ }
+
+ Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ if (err != OK) {
+ push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
+ return err;
+ }
+ }
+ } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) {
+ const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name);
+ if (info.path.get_extension().to_lower() != ".gd") {
+ push_error(vformat(R"(Singleton %s is not a GDScript.)", info.name), p_class);
+ return ERR_PARSE_ERROR;
+ }
+
+ Ref<GDScriptParserRef> parser = get_parser_for(info.path);
+ if (parser.is_null()) {
+ push_error(vformat(R"(Could not parse singleton from "%s".)", info.path), p_class);
+ return ERR_PARSE_ERROR;
+ }
+
+ Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ if (err != OK) {
+ push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
+ return err;
+ }
+ } else if (class_exists(name) && ClassDB::can_instance(get_real_class_name(name))) {
+ base.kind = GDScriptParser::DataType::NATIVE;
+ base.native_type = name;
+ } else {
+ // Look for other classes in script.
+ GDScriptParser::ClassNode *look_class = p_class;
+ bool found = false;
+ while (look_class != nullptr) {
+ if (look_class->identifier && look_class->identifier->name == name) {
+ if (!look_class->get_datatype().is_set()) {
+ Error err = resolve_inheritance(look_class, false);
+ if (err) {
+ return err;
+ }
+ }
+ base = look_class->get_datatype();
+ found = true;
+ break;
+ }
+ if (look_class->members_indices.has(name) && look_class->get_member(name).type == GDScriptParser::ClassNode::Member::CLASS) {
+ GDScriptParser::ClassNode::Member member = look_class->get_member(name);
+ if (!member.m_class->get_datatype().is_set()) {
+ Error err = resolve_inheritance(member.m_class, false);
+ if (err) {
+ return err;
+ }
+ }
+ base = member.m_class->get_datatype();
+ found = true;
+ break;
+ }
+ look_class = look_class->outer;
+ }
+
+ if (!found) {
+ push_error(vformat(R"(Could not find base class "%s".)", name), p_class);
+ return ERR_PARSE_ERROR;
+ }
+ }
+ }
+
+ for (int index = extends_index; index < p_class->extends.size(); index++) {
+ if (base.kind != GDScriptParser::DataType::CLASS) {
+ push_error(R"(Super type "%s" is not a GDScript. Cannot get nested types.)", p_class);
+ return ERR_PARSE_ERROR;
+ }
+
+ // TODO: Extends could use identifier nodes. That way errors can be pointed out properly and it can be used here.
+ GDScriptParser::IdentifierNode *id = parser->alloc_node<GDScriptParser::IdentifierNode>();
+ id->name = p_class->extends[index];
+
+ reduce_identifier_from_base(id, &base);
+
+ GDScriptParser::DataType id_type = id->get_datatype();
+ if (!id_type.is_set()) {
+ push_error(vformat(R"(Could not find type "%s" under base "%s".)", id->name, base.to_string()), p_class);
+ }
+
+ base = id_type;
+ }
+
+ result = base;
+ }
+
+ if (!result.is_set()) {
+ // TODO: More specific error messages.
+ push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->identifier == nullptr ? "<main>" : p_class->identifier->name), p_class);
+ return ERR_PARSE_ERROR;
+ }
+
+ p_class->base_type = result;
+ class_type.native_type = result.native_type;
+ p_class->set_datatype(class_type);
+
+ if (p_recursive) {
+ for (int i = 0; i < p_class->members.size(); i++) {
+ if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) {
+ resolve_inheritance(p_class->members[i].m_class, true);
+ }
+ }
+ }
+
+ return OK;
+}
+
+GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::TypeNode *p_type) {
+ GDScriptParser::DataType result;
+
+ if (p_type == nullptr) {
+ result.kind = GDScriptParser::DataType::VARIANT;
+ return result;
+ }
+
+ result.type_source = result.ANNOTATED_EXPLICIT;
+ result.builtin_type = Variant::OBJECT;
+
+ if (p_type->type_chain.empty()) {
+ // void.
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::NIL;
+ p_type->set_datatype(result);
+ return result;
+ }
+
+ StringName first = p_type->type_chain[0]->name;
+
+ if (first == "Variant") {
+ result.kind = GDScriptParser::DataType::VARIANT;
+ if (p_type->type_chain.size() > 1) {
+ push_error(R"("Variant" type don't contain nested types.)", p_type->type_chain[1]);
+ return GDScriptParser::DataType();
+ }
+ return result;
+ }
+
+ if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) {
+ // Built-in types.
+ if (p_type->type_chain.size() > 1) {
+ push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]);
+ return GDScriptParser::DataType();
+ }
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = GDScriptParser::get_builtin_type(first);
+ } else if (class_exists(first)) {
+ // Native engine classes.
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.native_type = first;
+ } else if (ScriptServer::is_global_class(first)) {
+ if (parser->script_path == ScriptServer::get_global_class_path(first)) {
+ result = parser->head->get_datatype();
+ } else {
+ Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(first));
+ if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) {
+ push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type);
+ return GDScriptParser::DataType();
+ }
+ result = ref->get_parser()->head->get_datatype();
+ }
+ } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) {
+ const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first);
+ Ref<GDScriptParserRef> ref = get_parser_for(autoload.path);
+ if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) {
+ push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type);
+ return GDScriptParser::DataType();
+ }
+ result = ref->get_parser()->head->get_datatype();
+ } else if (ClassDB::has_enum(get_real_class_name(parser->current_class->base_type.native_type), first)) {
+ // Native enum in current class.
+ result = make_native_enum_type(parser->current_class->base_type.native_type, first);
+ } else {
+ // Classes in current scope.
+ GDScriptParser::ClassNode *script_class = parser->current_class;
+ bool found = false;
+ while (!found && script_class != nullptr) {
+ if (script_class->identifier && script_class->identifier->name == first) {
+ result = script_class->get_datatype();
+ found = true;
+ break;
+ }
+ if (script_class->members_indices.has(first)) {
+ GDScriptParser::ClassNode::Member member = script_class->members[script_class->members_indices[first]];
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::CLASS:
+ result = member.m_class->get_datatype();
+ found = true;
+ break;
+ case GDScriptParser::ClassNode::Member::ENUM:
+ result = member.m_enum->get_datatype();
+ found = true;
+ break;
+ case GDScriptParser::ClassNode::Member::CONSTANT:
+ if (member.constant->get_datatype().is_meta_type) {
+ result = member.constant->get_datatype();
+ found = true;
+ break;
+ }
+ [[fallthrough]];
+ default:
+ push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type);
+ return GDScriptParser::DataType();
+ }
+ }
+ script_class = script_class->outer;
+ }
+ }
+ if (!result.is_set()) {
+ push_error(vformat(R"("%s" was not found in the current scope.)", first), p_type);
+ result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type.
+ return result;
+ }
+
+ if (p_type->type_chain.size() > 1) {
+ if (result.kind == GDScriptParser::DataType::CLASS) {
+ for (int i = 1; i < p_type->type_chain.size(); i++) {
+ GDScriptParser::DataType base = result;
+ reduce_identifier_from_base(p_type->type_chain[i], &base);
+ result = p_type->type_chain[i]->get_datatype();
+ if (!result.is_set()) {
+ push_error(vformat(R"(Could not find type "%s" under base "%s".)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]);
+ result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type.
+ return result;
+ } else if (!result.is_meta_type) {
+ push_error(vformat(R"(Member "%s" under base "%s" is not a valid type.)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]);
+ result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type.
+ return result;
+ }
+ }
+ } else if (result.kind == GDScriptParser::DataType::NATIVE) {
+ // Only enums allowed for native.
+ if (ClassDB::has_enum(get_real_class_name(result.native_type), p_type->type_chain[1]->name)) {
+ if (p_type->type_chain.size() > 2) {
+ push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]);
+ } else {
+ result = make_native_enum_type(result.native_type, p_type->type_chain[1]->name);
+ }
+ }
+ } else {
+ push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]);
+ result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type.
+ return result;
+ }
+ }
+
+ p_type->set_datatype(result);
+ return result;
+}
+
+void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_class) {
+ if (p_class->resolved_interface) {
+ return;
+ }
+ p_class->resolved_interface = true;
+
+ GDScriptParser::ClassNode *previous_class = parser->current_class;
+ parser->current_class = p_class;
+
+ for (int i = 0; i < p_class->members.size(); i++) {
+ GDScriptParser::ClassNode::Member member = p_class->members[i];
+
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::VARIABLE: {
+ GDScriptParser::DataType datatype;
+ datatype.kind = GDScriptParser::DataType::VARIANT;
+ datatype.type_source = GDScriptParser::DataType::UNDETECTED;
+
+ if (member.variable->initializer != nullptr) {
+ member.variable->set_datatype(datatype); // Allow recursive usage.
+ reduce_expression(member.variable->initializer);
+ datatype = member.variable->initializer->get_datatype();
+ }
+
+ if (member.variable->datatype_specifier != nullptr) {
+ datatype = resolve_datatype(member.variable->datatype_specifier);
+ datatype.is_meta_type = false;
+
+ if (member.variable->initializer != nullptr) {
+ if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) {
+ push_error(vformat(R"(Value of type "%s" cannot be assigned to variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
+ } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) {
+#ifdef DEBUG_ENABLED
+ parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION);
+#endif
+ }
+ if (member.variable->initializer->get_datatype().is_variant()) {
+ // TODO: Warn unsafe assign.
+ mark_node_unsafe(member.variable->initializer);
+ }
+ }
+ } else if (member.variable->infer_datatype) {
+ if (member.variable->initializer == nullptr) {
+ push_error(vformat(R"(Cannot infer the type of "%s" variable because there's no default value.)", member.variable->identifier->name), member.variable->identifier);
+ } else if (!datatype.is_set() || datatype.has_no_type()) {
+ push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value doesn't have a set type.)", member.variable->identifier->name), member.variable->initializer);
+ } else if (datatype.is_variant()) {
+ push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is Variant. Use explicit "Variant" type if this is intended.)", member.variable->identifier->name), member.variable->initializer);
+ } else if (datatype.builtin_type == Variant::NIL) {
+ push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is "null".)", member.variable->identifier->name), member.variable->initializer);
+ }
+ }
+
+ datatype.is_constant = false;
+ member.variable->set_datatype(datatype);
+ if (!datatype.has_no_type()) {
+ // TODO: Move this out into a routine specific to validate annotations.
+ if (member.variable->export_info.hint == PROPERTY_HINT_TYPE_STRING) {
+ // @export annotation.
+ switch (datatype.kind) {
+ case GDScriptParser::DataType::BUILTIN:
+ member.variable->export_info.hint_string = Variant::get_type_name(datatype.builtin_type);
+ break;
+ case GDScriptParser::DataType::NATIVE:
+ if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) {
+ member.variable->export_info.hint_string = get_real_class_name(datatype.native_type);
+ } else {
+ push_error(R"(Export type can only be built-in or a resource.)", member.variable);
+ }
+ break;
+ default:
+ // TODO: Allow custom user resources.
+ push_error(R"(Export type can only be built-in or a resource.)", member.variable);
+ break;
+ }
+ }
+ }
+ } break;
+ case GDScriptParser::ClassNode::Member::CONSTANT: {
+ reduce_expression(member.constant->initializer);
+
+ GDScriptParser::DataType datatype = member.constant->get_datatype();
+ if (member.constant->initializer) {
+ if (!member.constant->initializer->is_constant) {
+ push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer);
+ }
+
+ if (member.constant->datatype_specifier != nullptr) {
+ datatype = resolve_datatype(member.constant->datatype_specifier);
+ datatype.is_meta_type = false;
+
+ if (!is_type_compatible(datatype, member.constant->initializer->get_datatype(), true)) {
+ push_error(vformat(R"(Value of type "%s" cannot be initialized to constant of type "%s".)", member.constant->initializer->get_datatype().to_string(), datatype.to_string()), member.constant->initializer);
+ } else if (datatype.builtin_type == Variant::INT && member.constant->initializer->get_datatype().builtin_type == Variant::FLOAT) {
+#ifdef DEBUG_ENABLED
+ parser->push_warning(member.constant->initializer, GDScriptWarning::NARROWING_CONVERSION);
+#endif
+ }
+ }
+ }
+ datatype.is_constant = true;
+
+ member.constant->set_datatype(datatype);
+ } break;
+ case GDScriptParser::ClassNode::Member::SIGNAL: {
+ for (int j = 0; j < member.signal->parameters.size(); j++) {
+ GDScriptParser::DataType signal_type = resolve_datatype(member.signal->parameters[j]->datatype_specifier);
+ signal_type.is_meta_type = false;
+ member.signal->parameters[j]->set_datatype(signal_type);
+ }
+ // TODO: Make MethodInfo from signal.
+ GDScriptParser::DataType signal_type;
+ signal_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ signal_type.kind = GDScriptParser::DataType::BUILTIN;
+ signal_type.builtin_type = Variant::SIGNAL;
+
+ member.signal->set_datatype(signal_type);
+ } break;
+ case GDScriptParser::ClassNode::Member::ENUM: {
+ GDScriptParser::DataType enum_type;
+ enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ enum_type.kind = GDScriptParser::DataType::ENUM;
+ enum_type.builtin_type = Variant::DICTIONARY;
+ enum_type.enum_type = member.m_enum->identifier->name;
+ enum_type.native_type = p_class->fqcn + "." + member.m_enum->identifier->name;
+ enum_type.is_meta_type = true;
+ enum_type.is_constant = true;
+
+ for (int j = 0; j < member.m_enum->values.size(); j++) {
+ enum_type.enum_values[member.m_enum->values[j].identifier->name] = member.m_enum->values[j].value;
+ }
+
+ member.m_enum->set_datatype(enum_type);
+ } break;
+ case GDScriptParser::ClassNode::Member::FUNCTION:
+ resolve_function_signature(member.function);
+ break;
+ case GDScriptParser::ClassNode::Member::ENUM_VALUE:
+ break; // Nothing to do, type and value set in parser.
+ case GDScriptParser::ClassNode::Member::CLASS:
+ break; // Done later.
+ case GDScriptParser::ClassNode::Member::UNDEFINED:
+ ERR_PRINT("Trying to resolve undefined member.");
+ break;
+ }
+ }
+
+ // Recurse nested classes.
+ for (int i = 0; i < p_class->members.size(); i++) {
+ GDScriptParser::ClassNode::Member member = p_class->members[i];
+ if (member.type != GDScriptParser::ClassNode::Member::CLASS) {
+ continue;
+ }
+
+ resolve_class_interface(member.m_class);
+ }
+
+ parser->current_class = previous_class;
+}
+
+void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) {
+ if (p_class->resolved_body) {
+ return;
+ }
+ p_class->resolved_body = true;
+
+ GDScriptParser::ClassNode *previous_class = parser->current_class;
+ parser->current_class = p_class;
+
+ // Do functions now.
+ for (int i = 0; i < p_class->members.size(); i++) {
+ GDScriptParser::ClassNode::Member member = p_class->members[i];
+ if (member.type != GDScriptParser::ClassNode::Member::FUNCTION) {
+ continue;
+ }
+
+ resolve_function_body(member.function);
+ }
+
+ parser->current_class = previous_class;
+
+ // Recurse nested classes.
+ for (int i = 0; i < p_class->members.size(); i++) {
+ GDScriptParser::ClassNode::Member member = p_class->members[i];
+ if (member.type != GDScriptParser::ClassNode::Member::CLASS) {
+ continue;
+ }
+
+ resolve_class_body(member.m_class);
+ }
+
+ // Check unused variables.
+ for (int i = 0; i < p_class->members.size(); i++) {
+ GDScriptParser::ClassNode::Member member = p_class->members[i];
+ if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) {
+ continue;
+ }
+#ifdef DEBUG_ENABLED
+ if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) {
+ parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name);
+ }
+#endif
+ }
+}
+
+void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) {
+ ERR_FAIL_COND_MSG(p_node == nullptr, "Trying to resolve type of a null node.");
+
+ switch (p_node->type) {
+ case GDScriptParser::Node::NONE:
+ break; // Unreachable.
+ case GDScriptParser::Node::CLASS:
+ resolve_class_interface(static_cast<GDScriptParser::ClassNode *>(p_node));
+ resolve_class_body(static_cast<GDScriptParser::ClassNode *>(p_node));
+ break;
+ case GDScriptParser::Node::CONSTANT:
+ resolve_constant(static_cast<GDScriptParser::ConstantNode *>(p_node));
+ break;
+ case GDScriptParser::Node::FOR:
+ resolve_for(static_cast<GDScriptParser::ForNode *>(p_node));
+ break;
+ case GDScriptParser::Node::FUNCTION:
+ resolve_function_signature(static_cast<GDScriptParser::FunctionNode *>(p_node));
+ resolve_function_body(static_cast<GDScriptParser::FunctionNode *>(p_node));
+ break;
+ case GDScriptParser::Node::IF:
+ resolve_if(static_cast<GDScriptParser::IfNode *>(p_node));
+ break;
+ case GDScriptParser::Node::SUITE:
+ resolve_suite(static_cast<GDScriptParser::SuiteNode *>(p_node));
+ break;
+ case GDScriptParser::Node::VARIABLE:
+ resolve_variable(static_cast<GDScriptParser::VariableNode *>(p_node));
+ break;
+ case GDScriptParser::Node::WHILE:
+ resolve_while(static_cast<GDScriptParser::WhileNode *>(p_node));
+ break;
+ case GDScriptParser::Node::ANNOTATION:
+ resolve_annotation(static_cast<GDScriptParser::AnnotationNode *>(p_node));
+ break;
+ case GDScriptParser::Node::ASSERT:
+ resolve_assert(static_cast<GDScriptParser::AssertNode *>(p_node));
+ break;
+ case GDScriptParser::Node::MATCH:
+ resolve_match(static_cast<GDScriptParser::MatchNode *>(p_node));
+ break;
+ case GDScriptParser::Node::MATCH_BRANCH:
+ resolve_match_branch(static_cast<GDScriptParser::MatchBranchNode *>(p_node), nullptr);
+ break;
+ case GDScriptParser::Node::PARAMETER:
+ resolve_pararameter(static_cast<GDScriptParser::ParameterNode *>(p_node));
+ break;
+ case GDScriptParser::Node::PATTERN:
+ resolve_match_pattern(static_cast<GDScriptParser::PatternNode *>(p_node), nullptr);
+ break;
+ case GDScriptParser::Node::RETURN:
+ resolve_return(static_cast<GDScriptParser::ReturnNode *>(p_node));
+ break;
+ case GDScriptParser::Node::TYPE:
+ resolve_datatype(static_cast<GDScriptParser::TypeNode *>(p_node));
+ break;
+ // Resolving expression is the same as reducing them.
+ case GDScriptParser::Node::ARRAY:
+ case GDScriptParser::Node::ASSIGNMENT:
+ case GDScriptParser::Node::AWAIT:
+ case GDScriptParser::Node::BINARY_OPERATOR:
+ case GDScriptParser::Node::CALL:
+ case GDScriptParser::Node::CAST:
+ case GDScriptParser::Node::DICTIONARY:
+ case GDScriptParser::Node::GET_NODE:
+ case GDScriptParser::Node::IDENTIFIER:
+ case GDScriptParser::Node::LITERAL:
+ case GDScriptParser::Node::PRELOAD:
+ case GDScriptParser::Node::SELF:
+ case GDScriptParser::Node::SUBSCRIPT:
+ case GDScriptParser::Node::TERNARY_OPERATOR:
+ case GDScriptParser::Node::UNARY_OPERATOR:
+ reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node));
+ break;
+ case GDScriptParser::Node::BREAK:
+ case GDScriptParser::Node::BREAKPOINT:
+ case GDScriptParser::Node::CONTINUE:
+ case GDScriptParser::Node::ENUM:
+ case GDScriptParser::Node::PASS:
+ case GDScriptParser::Node::SIGNAL:
+ // Nothing to do.
+ break;
+ }
+}
+
+void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) {
+ // TODO: Add second validation function for annotations, so they can use checked types.
+}
+
+void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function) {
+ if (p_function->resolved_signature) {
+ return;
+ }
+ p_function->resolved_signature = true;
+
+ GDScriptParser::FunctionNode *previous_function = parser->current_function;
+ parser->current_function = p_function;
+
+ for (int i = 0; i < p_function->parameters.size(); i++) {
+ resolve_pararameter(p_function->parameters[i]);
+#ifdef DEBUG_ENABLED
+ if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) {
+ parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, p_function->identifier->name, p_function->parameters[i]->identifier->name);
+ }
+ is_shadowing(p_function->parameters[i]->identifier, "function parameter");
+#endif
+ }
+
+ if (p_function->identifier->name == "_init") {
+ // Constructor.
+ GDScriptParser::DataType return_type = parser->current_class->get_datatype();
+ return_type.is_meta_type = false;
+ p_function->set_datatype(return_type);
+ if (p_function->return_type) {
+ push_error("Constructor cannot have an explicit return type.", p_function->return_type);
+ }
+ } else {
+ GDScriptParser::DataType return_type = resolve_datatype(p_function->return_type);
+ p_function->set_datatype(return_type);
+ }
+
+ parser->current_function = previous_function;
+}
+
+void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function) {
+ if (p_function->resolved_body) {
+ return;
+ }
+ p_function->resolved_body = true;
+
+ GDScriptParser::FunctionNode *previous_function = parser->current_function;
+ parser->current_function = p_function;
+
+ resolve_suite(p_function->body);
+
+ GDScriptParser::DataType return_type = p_function->body->get_datatype();
+
+ if (p_function->get_datatype().has_no_type() && return_type.is_set()) {
+ // Use the suite inferred type if return isn't explicitly set.
+ return_type.type_source = GDScriptParser::DataType::INFERRED;
+ p_function->set_datatype(p_function->body->get_datatype());
+ } else if (p_function->get_datatype().is_hard_type() && (p_function->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_function->get_datatype().builtin_type != Variant::NIL)) {
+ if (!p_function->body->has_return && p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init) {
+ push_error(R"(Not all code paths return a value.)", p_function);
+ }
+ }
+
+ parser->current_function = previous_function;
+}
+
+void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement) {
+ if (p_statement == nullptr) {
+ return;
+ }
+ switch (p_statement->type) {
+ case GDScriptParser::Node::IF:
+ case GDScriptParser::Node::FOR:
+ case GDScriptParser::Node::MATCH:
+ case GDScriptParser::Node::PATTERN:
+ case GDScriptParser::Node::RETURN:
+ case GDScriptParser::Node::WHILE:
+ // Use return or nested suite type as this suite type.
+ if (p_suite->get_datatype().is_set() && (p_suite->get_datatype() != p_statement->get_datatype())) {
+ // Mixed types.
+ // TODO: This could use the common supertype instead.
+ p_suite->datatype.kind = GDScriptParser::DataType::VARIANT;
+ p_suite->datatype.type_source = GDScriptParser::DataType::UNDETECTED;
+ } else {
+ p_suite->set_datatype(p_statement->get_datatype());
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
+ for (int i = 0; i < p_suite->statements.size(); i++) {
+ GDScriptParser::Node *stmt = p_suite->statements[i];
+ resolve_node(stmt);
+ decide_suite_type(p_suite, stmt);
+ }
+}
+
+void GDScriptAnalyzer::resolve_if(GDScriptParser::IfNode *p_if) {
+ reduce_expression(p_if->condition);
+
+ resolve_suite(p_if->true_block);
+ p_if->set_datatype(p_if->true_block->get_datatype());
+
+ if (p_if->false_block != nullptr) {
+ resolve_suite(p_if->false_block);
+ decide_suite_type(p_if, p_if->false_block);
+ }
+}
+
+void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
+ bool list_resolved = false;
+
+ // Optimize constant range() call to not allocate an array.
+ // Use int, Vector2, Vector3 instead, which also can be used as range iterators.
+ if (p_for->list && p_for->list->type == GDScriptParser::Node::CALL) {
+ GDScriptParser::CallNode *call = static_cast<GDScriptParser::CallNode *>(p_for->list);
+ if (call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ GDScriptParser::IdentifierNode *callee = static_cast<GDScriptParser::IdentifierNode *>(call->callee);
+ if (callee->name == "range") {
+ list_resolved = true;
+ if (call->arguments.size() < 1) {
+ push_error(R"*(Invalid call for "range()" function. Expected at least 1 argument, none given.)*", call->callee);
+ } else if (call->arguments.size() > 3) {
+ push_error(vformat(R"*(Invalid call for "range()" function. Expected at most 3 arguments, %d given.)*", call->arguments.size()), call->callee);
+ } else {
+ // Now we can optimize it.
+ bool all_is_constant = true;
+ Vector<Variant> args;
+ args.resize(call->arguments.size());
+ for (int i = 0; i < call->arguments.size(); i++) {
+ reduce_expression(call->arguments[i]);
+
+ if (!call->arguments[i]->is_constant) {
+ all_is_constant = false;
+ } else {
+ args.write[i] = call->arguments[i]->reduced_value;
+ }
+
+ GDScriptParser::DataType arg_type = call->arguments[i]->get_datatype();
+ if (arg_type.kind != GDScriptParser::DataType::BUILTIN) {
+ all_is_constant = false;
+ push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]);
+ } else if (arg_type.builtin_type != Variant::INT && arg_type.builtin_type != Variant::FLOAT) {
+ all_is_constant = false;
+ push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]);
+ }
+ }
+
+ Variant reduced;
+
+ if (all_is_constant) {
+ switch (args.size()) {
+ case 1:
+ reduced = args[0];
+ break;
+ case 2:
+ reduced = Vector2i(args[0], args[1]);
+ break;
+ case 3:
+ reduced = Vector3i(args[0], args[1], args[2]);
+ break;
+ }
+ p_for->list->is_constant = true;
+ p_for->list->reduced_value = reduced;
+ }
+ }
+
+ GDScriptParser::DataType list_type;
+ list_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ list_type.kind = GDScriptParser::DataType::BUILTIN;
+ list_type.builtin_type = Variant::ARRAY;
+ p_for->list->set_datatype(list_type);
+ }
+ }
+ }
+
+ if (!list_resolved) {
+ resolve_node(p_for->list);
+ }
+
+ // TODO: If list is a typed array, the variable should be an element.
+ // Also applicable for constant range() (so variable is int or float).
+
+ resolve_suite(p_for->loop);
+ p_for->set_datatype(p_for->loop->get_datatype());
+#ifdef DEBUG_ENABLED
+ if (p_for->variable) {
+ is_shadowing(p_for->variable, R"("for" iterator variable)");
+ }
+#endif
+}
+
+void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) {
+ resolve_node(p_while->condition);
+
+ resolve_suite(p_while->loop);
+ p_while->set_datatype(p_while->loop->get_datatype());
+}
+
+void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable) {
+ GDScriptParser::DataType type;
+ type.kind = GDScriptParser::DataType::VARIANT; // By default.
+
+ if (p_variable->initializer != nullptr) {
+ reduce_expression(p_variable->initializer);
+ type = p_variable->initializer->get_datatype();
+
+ if (p_variable->infer_datatype) {
+ type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+
+ if (type.has_no_type()) {
+ push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value does not have a set type.)", p_variable->identifier->name), p_variable->initializer);
+ } else if (type.is_variant()) {
+ push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is a variant. Use explicit "Variant" type if this is intended.)", p_variable->identifier->name), p_variable->initializer);
+ } else if (type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) {
+ push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_variable->identifier->name), p_variable->initializer);
+ }
+ } else {
+ type.type_source = GDScriptParser::DataType::INFERRED;
+ }
+#ifdef DEBUG_ENABLED
+ if (p_variable->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) {
+ parser->push_warning(p_variable->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_variable->initializer)->function_name);
+ }
+#endif
+ }
+
+ if (p_variable->datatype_specifier != nullptr) {
+ type = resolve_datatype(p_variable->datatype_specifier);
+ type.is_meta_type = false;
+
+ if (p_variable->initializer != nullptr) {
+ if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) {
+ push_error(vformat(R"(Value of type "%s" cannot be assigned to variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
+#ifdef DEBUG_ENABLED
+ } else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) {
+ parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION);
+#endif
+ }
+ if (p_variable->initializer->get_datatype().is_variant()) {
+ // TODO: Warn unsafe assign.
+ mark_node_unsafe(p_variable->initializer);
+ }
+ }
+ } else if (p_variable->infer_datatype) {
+ if (type.has_no_type()) {
+ push_error(vformat(R"(Cannot infer the type of variable "%s" because the initial value doesn't have a set type.)", p_variable->identifier->name), p_variable->identifier);
+ }
+ type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+ }
+
+ type.is_constant = false;
+ p_variable->set_datatype(type);
+
+#ifdef DEBUG_ENABLED
+ if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) {
+ parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name);
+ } else if (p_variable->assignments == 0) {
+ parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name);
+ }
+
+ is_shadowing(p_variable->identifier, "variable");
+#endif
+}
+
+void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant) {
+ GDScriptParser::DataType type;
+
+ reduce_expression(p_constant->initializer);
+
+ if (!p_constant->initializer->is_constant) {
+ push_error(vformat(R"(Assigned value for constant "%s" isn't a constant expression.)", p_constant->identifier->name), p_constant->initializer);
+ }
+
+ type = p_constant->initializer->get_datatype();
+
+#ifdef DEBUG_ENABLED
+ if (p_constant->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) {
+ parser->push_warning(p_constant->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_constant->initializer)->function_name);
+ }
+#endif
+
+ if (p_constant->datatype_specifier != nullptr) {
+ GDScriptParser::DataType explicit_type = resolve_datatype(p_constant->datatype_specifier);
+ explicit_type.is_meta_type = false;
+ if (!is_type_compatible(explicit_type, type)) {
+ push_error(vformat(R"(Assigned value for constant "%s" has type %s which is not compatible with defined type %s.)", p_constant->identifier->name, type.to_string(), explicit_type.to_string()), p_constant->initializer);
+#ifdef DEBUG_ENABLED
+ } else if (explicit_type.builtin_type == Variant::INT && type.builtin_type == Variant::FLOAT) {
+ parser->push_warning(p_constant->initializer, GDScriptWarning::NARROWING_CONVERSION);
+#endif
+ }
+ type = explicit_type;
+ } else if (p_constant->infer_datatype) {
+ if (type.has_no_type()) {
+ push_error(vformat(R"(Cannot infer the type of constant "%s" because the initial value doesn't have a set type.)", p_constant->identifier->name), p_constant->identifier);
+ }
+ type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+ }
+
+ type.is_constant = true;
+ p_constant->set_datatype(type);
+
+#ifdef DEBUG_ENABLED
+ if (p_constant->usages == 0) {
+ parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name);
+ }
+
+ is_shadowing(p_constant->identifier, "constant");
+#endif
+}
+
+void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) {
+ reduce_expression(p_assert->condition);
+ if (p_assert->message != nullptr) {
+ reduce_literal(p_assert->message);
+ }
+
+ p_assert->set_datatype(p_assert->condition->get_datatype());
+
+#ifdef DEBUG_ENABLED
+ if (p_assert->condition->is_constant) {
+ if (p_assert->condition->reduced_value.booleanize()) {
+ parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_TRUE);
+ } else {
+ parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_FALSE);
+ }
+ }
+#endif
+}
+
+void GDScriptAnalyzer::resolve_match(GDScriptParser::MatchNode *p_match) {
+ reduce_expression(p_match->test);
+
+ for (int i = 0; i < p_match->branches.size(); i++) {
+ resolve_match_branch(p_match->branches[i], p_match->test);
+
+ decide_suite_type(p_match, p_match->branches[i]);
+ }
+}
+
+void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test) {
+ for (int i = 0; i < p_match_branch->patterns.size(); i++) {
+ resolve_match_pattern(p_match_branch->patterns[i], p_match_test);
+ }
+
+ resolve_suite(p_match_branch->block);
+
+ decide_suite_type(p_match_branch, p_match_branch->block);
+}
+
+void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test) {
+ if (p_match_pattern == nullptr) {
+ return;
+ }
+
+ GDScriptParser::DataType result;
+
+ switch (p_match_pattern->pattern_type) {
+ case GDScriptParser::PatternNode::PT_LITERAL:
+ if (p_match_pattern->literal) {
+ reduce_literal(p_match_pattern->literal);
+ result = p_match_pattern->literal->get_datatype();
+ }
+ break;
+ case GDScriptParser::PatternNode::PT_EXPRESSION:
+ if (p_match_pattern->expression) {
+ reduce_expression(p_match_pattern->expression);
+ if (!p_match_pattern->expression->is_constant) {
+ push_error(R"(Expression in match pattern must be a constant.)", p_match_pattern->expression);
+ }
+ result = p_match_pattern->expression->get_datatype();
+ }
+ break;
+ case GDScriptParser::PatternNode::PT_BIND:
+ if (p_match_test != nullptr) {
+ result = p_match_test->get_datatype();
+ } else {
+ result.kind = GDScriptParser::DataType::VARIANT;
+ }
+ p_match_pattern->bind->set_datatype(result);
+#ifdef DEBUG_ENABLED
+ is_shadowing(p_match_pattern->bind, "pattern bind");
+ if (p_match_pattern->bind->usages == 0) {
+ parser->push_warning(p_match_pattern->bind, GDScriptWarning::UNASSIGNED_VARIABLE, p_match_pattern->bind->name);
+ }
+#endif
+ break;
+ case GDScriptParser::PatternNode::PT_ARRAY:
+ for (int i = 0; i < p_match_pattern->array.size(); i++) {
+ resolve_match_pattern(p_match_pattern->array[i], nullptr);
+ decide_suite_type(p_match_pattern, p_match_pattern->array[i]);
+ }
+ result = p_match_pattern->get_datatype();
+ break;
+ case GDScriptParser::PatternNode::PT_DICTIONARY:
+ for (int i = 0; i < p_match_pattern->dictionary.size(); i++) {
+ if (p_match_pattern->dictionary[i].key) {
+ reduce_expression(p_match_pattern->dictionary[i].key);
+ if (!p_match_pattern->dictionary[i].key->is_constant) {
+ push_error(R"(Expression in dictionary pattern key must be a constant.)", p_match_pattern->expression);
+ }
+ }
+
+ if (p_match_pattern->dictionary[i].value_pattern) {
+ resolve_match_pattern(p_match_pattern->dictionary[i].value_pattern, nullptr);
+ decide_suite_type(p_match_pattern, p_match_pattern->dictionary[i].value_pattern);
+ }
+ }
+ result = p_match_pattern->get_datatype();
+ break;
+ case GDScriptParser::PatternNode::PT_WILDCARD:
+ case GDScriptParser::PatternNode::PT_REST:
+ result.kind = GDScriptParser::DataType::VARIANT;
+ break;
+ }
+
+ p_match_pattern->set_datatype(result);
+}
+
+void GDScriptAnalyzer::resolve_pararameter(GDScriptParser::ParameterNode *p_parameter) {
+ GDScriptParser::DataType result;
+ result.kind = GDScriptParser::DataType::VARIANT;
+
+ if (p_parameter->default_value != nullptr) {
+ reduce_expression(p_parameter->default_value);
+ result = p_parameter->default_value->get_datatype();
+ result.type_source = GDScriptParser::DataType::INFERRED;
+ }
+
+ if (p_parameter->datatype_specifier != nullptr) {
+ resolve_datatype(p_parameter->datatype_specifier);
+ result = p_parameter->datatype_specifier->get_datatype();
+ result.is_meta_type = false;
+
+ if (p_parameter->default_value != nullptr) {
+ if (!is_type_compatible(result, p_parameter->default_value->get_datatype())) {
+ push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with paremeter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value);
+ } else if (p_parameter->default_value->get_datatype().is_variant()) {
+ mark_node_unsafe(p_parameter);
+ }
+ }
+ }
+
+ p_parameter->set_datatype(result);
+}
+
+void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
+ GDScriptParser::DataType result;
+
+ if (p_return->return_value != nullptr) {
+ reduce_expression(p_return->return_value);
+ result = p_return->return_value->get_datatype();
+ } else {
+ // Return type is null by default.
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::NIL;
+ result.is_constant = true;
+ }
+
+ GDScriptParser::DataType function_type = parser->current_function->get_datatype();
+ function_type.is_meta_type = false;
+ if (function_type.is_hard_type()) {
+ if (!is_type_compatible(function_type, result)) {
+ // Try other way. Okay but not safe.
+ if (!is_type_compatible(result, function_type)) {
+ push_error(vformat(R"(Cannot return value of type "%s" because the function return type is "%s".)", result.to_string(), function_type.to_string()), p_return);
+ } else {
+ // TODO: Add warning.
+ mark_node_unsafe(p_return);
+ }
+#ifdef DEBUG_ENABLED
+ } else if (function_type.builtin_type == Variant::INT && result.builtin_type == Variant::FLOAT) {
+ parser->push_warning(p_return, GDScriptWarning::NARROWING_CONVERSION);
+ } else if (result.is_variant()) {
+ mark_node_unsafe(p_return);
+#endif
+ }
+ }
+
+ p_return->set_datatype(result);
+}
+
+void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expression) {
+ // This one makes some magic happen.
+
+ if (p_expression == nullptr) {
+ return;
+ }
+
+ if (p_expression->reduced) {
+ // Don't do this more than once.
+ return;
+ }
+
+ p_expression->reduced = true;
+
+ switch (p_expression->type) {
+ case GDScriptParser::Node::ARRAY:
+ reduce_array(static_cast<GDScriptParser::ArrayNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::ASSIGNMENT:
+ reduce_assignment(static_cast<GDScriptParser::AssignmentNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::AWAIT:
+ reduce_await(static_cast<GDScriptParser::AwaitNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::BINARY_OPERATOR:
+ reduce_binary_op(static_cast<GDScriptParser::BinaryOpNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::CALL:
+ reduce_call(static_cast<GDScriptParser::CallNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::CAST:
+ reduce_cast(static_cast<GDScriptParser::CastNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::DICTIONARY:
+ reduce_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::GET_NODE:
+ reduce_get_node(static_cast<GDScriptParser::GetNodeNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::IDENTIFIER:
+ reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::LITERAL:
+ reduce_literal(static_cast<GDScriptParser::LiteralNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::PRELOAD:
+ reduce_preload(static_cast<GDScriptParser::PreloadNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::SELF:
+ reduce_self(static_cast<GDScriptParser::SelfNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::SUBSCRIPT:
+ reduce_subscript(static_cast<GDScriptParser::SubscriptNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::TERNARY_OPERATOR:
+ reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::UNARY_OPERATOR:
+ reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression));
+ break;
+ // Non-expressions. Here only to make sure new nodes aren't forgotten.
+ case GDScriptParser::Node::NONE:
+ case GDScriptParser::Node::ANNOTATION:
+ case GDScriptParser::Node::ASSERT:
+ case GDScriptParser::Node::BREAK:
+ case GDScriptParser::Node::BREAKPOINT:
+ case GDScriptParser::Node::CLASS:
+ case GDScriptParser::Node::CONSTANT:
+ case GDScriptParser::Node::CONTINUE:
+ case GDScriptParser::Node::ENUM:
+ case GDScriptParser::Node::FOR:
+ case GDScriptParser::Node::FUNCTION:
+ case GDScriptParser::Node::IF:
+ case GDScriptParser::Node::MATCH:
+ case GDScriptParser::Node::MATCH_BRANCH:
+ case GDScriptParser::Node::PARAMETER:
+ case GDScriptParser::Node::PASS:
+ case GDScriptParser::Node::PATTERN:
+ case GDScriptParser::Node::RETURN:
+ case GDScriptParser::Node::SIGNAL:
+ case GDScriptParser::Node::SUITE:
+ case GDScriptParser::Node::TYPE:
+ case GDScriptParser::Node::VARIABLE:
+ case GDScriptParser::Node::WHILE:
+ ERR_FAIL_MSG("Reaching unreachable case");
+ }
+}
+
+void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) {
+ bool all_is_constant = true;
+
+ for (int i = 0; i < p_array->elements.size(); i++) {
+ GDScriptParser::ExpressionNode *element = p_array->elements[i];
+ reduce_expression(element);
+ all_is_constant = all_is_constant && element->is_constant;
+ }
+
+ if (all_is_constant) {
+ Array array;
+ array.resize(p_array->elements.size());
+ for (int i = 0; i < p_array->elements.size(); i++) {
+ array[i] = p_array->elements[i]->reduced_value;
+ }
+ p_array->is_constant = true;
+ p_array->reduced_value = array;
+ }
+
+ // It's array in any case.
+ GDScriptParser::DataType arr_type;
+ arr_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ arr_type.kind = GDScriptParser::DataType::BUILTIN;
+ arr_type.builtin_type = Variant::ARRAY;
+ arr_type.is_constant = true;
+
+ p_array->set_datatype(arr_type);
+}
+
+void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) {
+ reduce_expression(p_assignment->assignee);
+ reduce_expression(p_assignment->assigned_value);
+
+ if (p_assignment->assigned_value == nullptr || p_assignment->assignee == nullptr) {
+ return;
+ }
+
+ if (p_assignment->assignee->get_datatype().is_constant) {
+ push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
+ }
+
+ if (!is_type_compatible(p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), true)) {
+ if (p_assignment->assignee->get_datatype().is_hard_type()) {
+ push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value);
+ } else {
+ // TODO: Warning in this case.
+ }
+ }
+
+ if (p_assignment->assignee->get_datatype().has_no_type() || p_assignment->assigned_value->get_datatype().is_variant()) {
+ mark_node_unsafe(p_assignment);
+ }
+
+ if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
+ // Change source type so it's not wrongly detected later.
+ GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee);
+
+ switch (identifier->source) {
+ case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: {
+ GDScriptParser::DataType id_type = identifier->variable_source->get_datatype();
+ if (!id_type.is_hard_type()) {
+ id_type.kind = GDScriptParser::DataType::VARIANT;
+ id_type.type_source = GDScriptParser::DataType::UNDETECTED;
+ identifier->variable_source->set_datatype(id_type);
+ }
+ } break;
+ case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: {
+ GDScriptParser::DataType id_type = identifier->variable_source->get_datatype();
+ if (!id_type.is_hard_type()) {
+ id_type = p_assignment->assigned_value->get_datatype();
+ id_type.type_source = GDScriptParser::DataType::INFERRED;
+ id_type.is_constant = false;
+ identifier->variable_source->set_datatype(id_type);
+ }
+ } break;
+ case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: {
+ GDScriptParser::DataType id_type = identifier->bind_source->get_datatype();
+ if (!id_type.is_hard_type()) {
+ id_type = p_assignment->assigned_value->get_datatype();
+ id_type.type_source = GDScriptParser::DataType::INFERRED;
+ id_type.is_constant = false;
+ identifier->variable_source->set_datatype(id_type);
+ }
+ } break;
+ default:
+ // Nothing to do.
+ break;
+ }
+ }
+
+ GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype();
+ GDScriptParser::DataType assigned_type = p_assignment->assigned_value->get_datatype();
+#ifdef DEBUG_ENABLED
+ if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_type.kind == GDScriptParser::DataType::BUILTIN && assigned_type.builtin_type == Variant::NIL) {
+ parser->push_warning(p_assignment->assigned_value, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value)->function_name);
+ } else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_type.builtin_type == Variant::FLOAT) {
+ parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION);
+ }
+#endif
+}
+
+void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) {
+ if (p_await->to_await->type == GDScriptParser::Node::CALL) {
+ reduce_call(static_cast<GDScriptParser::CallNode *>(p_await->to_await), true);
+ } else {
+ reduce_expression(p_await->to_await);
+ }
+
+ p_await->is_constant = p_await->to_await->is_constant;
+ p_await->reduced_value = p_await->to_await->reduced_value;
+
+ GDScriptParser::DataType awaiting_type = p_await->to_await->get_datatype();
+
+ p_await->set_datatype(awaiting_type);
+
+#ifdef DEBUG_ENABLED
+ if (!awaiting_type.is_coroutine && awaiting_type.builtin_type != Variant::SIGNAL) {
+ parser->push_warning(p_await, GDScriptWarning::REDUNDANT_AWAIT);
+ }
+#endif
+}
+
+void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op) {
+ reduce_expression(p_binary_op->left_operand);
+
+ if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST && p_binary_op->right_operand && p_binary_op->right_operand->type == GDScriptParser::Node::IDENTIFIER) {
+ reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_binary_op->right_operand), true);
+ } else {
+ reduce_expression(p_binary_op->right_operand);
+ }
+ // TODO: Right operand must be a valid type with the `is` operator. Need to check here.
+
+ GDScriptParser::DataType left_type;
+ if (p_binary_op->left_operand) {
+ left_type = p_binary_op->left_operand->get_datatype();
+ }
+ GDScriptParser::DataType right_type;
+ if (p_binary_op->right_operand) {
+ right_type = p_binary_op->right_operand->get_datatype();
+ }
+
+ if (!left_type.is_set() || !right_type.is_set()) {
+ return;
+ }
+
+#ifdef DEBUG_ENABLED
+ if (p_binary_op->variant_op == Variant::OP_DIVIDE && left_type.builtin_type == Variant::INT && right_type.builtin_type == Variant::INT) {
+ parser->push_warning(p_binary_op, GDScriptWarning::INTEGER_DIVISION);
+ }
+#endif
+
+ if (p_binary_op->left_operand->is_constant && p_binary_op->right_operand->is_constant) {
+ p_binary_op->is_constant = true;
+ if (p_binary_op->variant_op < Variant::OP_MAX) {
+ p_binary_op->reduced_value = Variant::evaluate(p_binary_op->variant_op, p_binary_op->left_operand->reduced_value, p_binary_op->right_operand->reduced_value);
+ } else {
+ if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
+ GDScriptParser::DataType test_type = right_type;
+ test_type.is_meta_type = false;
+
+ if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) {
+ push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand);
+ p_binary_op->reduced_value = false;
+ } else {
+ p_binary_op->reduced_value = true;
+ }
+ } else {
+ ERR_PRINT("Parser bug: unknown binary operation.");
+ }
+ }
+ p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value));
+
+ return;
+ }
+
+ GDScriptParser::DataType result;
+
+ if (left_type.is_variant() || right_type.is_variant()) {
+ // Cannot infer type because one operand can be anything.
+ result.kind = GDScriptParser::DataType::VARIANT;
+ mark_node_unsafe(p_binary_op);
+ } else {
+ if (p_binary_op->variant_op < Variant::OP_MAX) {
+ bool valid = false;
+ result = get_operation_type(p_binary_op->variant_op, p_binary_op->left_operand->get_datatype(), right_type, valid);
+
+ if (!valid) {
+ push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", p_binary_op->left_operand->get_datatype().to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
+ }
+ } else {
+ if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
+ GDScriptParser::DataType test_type = right_type;
+ test_type.is_meta_type = false;
+
+ if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) {
+ // Test reverse as well to consider for subtypes.
+ if (!is_type_compatible(p_binary_op->left_operand->get_datatype(), test_type, false)) {
+ if (p_binary_op->left_operand->get_datatype().is_hard_type()) {
+ push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", p_binary_op->left_operand->get_datatype().to_string(), test_type.to_string()), p_binary_op->left_operand);
+ } else {
+ // TODO: Warning.
+ mark_node_unsafe(p_binary_op);
+ }
+ }
+ }
+
+ // "is" operator is always a boolean anyway.
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::BOOL;
+ } else {
+ ERR_PRINT("Parser bug: unknown binary operation.");
+ }
+ }
+ }
+
+ p_binary_op->set_datatype(result);
+}
+
+void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_await) {
+ bool all_is_constant = true;
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ reduce_expression(p_call->arguments[i]);
+ all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
+ }
+
+ GDScriptParser::DataType call_type;
+
+ if (!p_call->is_super && p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ // Call to name directly.
+ StringName function_name = p_call->function_name;
+ Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name);
+ GDScriptFunctions::Function builtin_function = GDScriptParser::get_builtin_function(function_name);
+
+ if (builtin_type < Variant::VARIANT_MAX) {
+ // Is a builtin constructor.
+ call_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ call_type.kind = GDScriptParser::DataType::BUILTIN;
+ call_type.builtin_type = builtin_type;
+
+ if (builtin_type == Variant::OBJECT) {
+ call_type.kind = GDScriptParser::DataType::NATIVE;
+ call_type.native_type = function_name; // "Object".
+ }
+
+ if (all_is_constant) {
+ // Construct here.
+ Vector<const Variant *> args;
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ args.push_back(&(p_call->arguments[i]->reduced_value));
+ }
+
+ Callable::CallError err;
+ Variant value = Variant::construct(builtin_type, (const Variant **)args.ptr(), args.size(), err);
+
+ switch (err.error) {
+ case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
+ push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be %s but is %s.)", Variant::get_type_name(builtin_type), err.argument + 1,
+ Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()),
+ p_call->arguments[err.argument]);
+ break;
+ case Callable::CallError::CALL_ERROR_INVALID_METHOD: {
+ String signature = Variant::get_type_name(builtin_type) + "(";
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ if (i > 0) {
+ signature += ", ";
+ }
+ signature += p_call->arguments[i]->get_datatype().to_string();
+ }
+ push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee);
+ } break;
+ case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
+ push_error(vformat(R"(Too many arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call);
+ break;
+ case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
+ push_error(vformat(R"(Too few arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call);
+ break;
+ case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL:
+ break; // Can't happen in a builtin constructor.
+ case Callable::CallError::CALL_OK:
+ p_call->is_constant = true;
+ p_call->reduced_value = value;
+ break;
+ }
+ } else {
+ // TODO: Check constructors without constants.
+
+ // If there's one argument, try to use copy constructor (those aren't explicitly defined).
+ if (p_call->arguments.size() == 1) {
+ GDScriptParser::DataType arg_type = p_call->arguments[0]->get_datatype();
+ if (arg_type.is_variant()) {
+ mark_node_unsafe(p_call->arguments[0]);
+ } else {
+ if (arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == builtin_type) {
+ // Okay.
+ p_call->set_datatype(call_type);
+ return;
+ }
+ }
+ }
+ List<MethodInfo> constructors;
+ Variant::get_constructor_list(builtin_type, &constructors);
+ bool match = false;
+
+ for (const List<MethodInfo>::Element *E = constructors.front(); E != nullptr; E = E->next()) {
+ const MethodInfo &info = E->get();
+
+ if (p_call->arguments.size() < info.arguments.size() - info.default_arguments.size()) {
+ continue;
+ }
+ if (p_call->arguments.size() > info.arguments.size()) {
+ continue;
+ }
+
+ bool types_match = true;
+
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ GDScriptParser::DataType par_type = type_from_property(info.arguments[i]);
+
+ if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype())) {
+ types_match = false;
+ break;
+#ifdef DEBUG_ENABLED
+ } else {
+ if (par_type.builtin_type == Variant::INT && p_call->arguments[i]->get_datatype().builtin_type == Variant::FLOAT) {
+ parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name);
+ }
+#endif
+ }
+ }
+
+ if (types_match) {
+ match = true;
+ call_type = type_from_property(info.return_val);
+ break;
+ }
+ }
+
+ if (!match) {
+ String signature = Variant::get_type_name(builtin_type) + "(";
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ if (i > 0) {
+ signature += ", ";
+ }
+ signature += p_call->arguments[i]->get_datatype().to_string();
+ }
+ push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call);
+ }
+ }
+ p_call->set_datatype(call_type);
+ return;
+ } else if (builtin_function < GDScriptFunctions::FUNC_MAX) {
+ MethodInfo function_info = GDScriptFunctions::get_info(builtin_function);
+
+ if (all_is_constant && GDScriptFunctions::is_deterministic(builtin_function)) {
+ // Can call on compilation.
+ Vector<const Variant *> args;
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ args.push_back(&(p_call->arguments[i]->reduced_value));
+ }
+
+ Variant value;
+ Callable::CallError err;
+ GDScriptFunctions::call(builtin_function, (const Variant **)args.ptr(), args.size(), value, err);
+
+ switch (err.error) {
+ case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
+ PropertyInfo wrong_arg = function_info.arguments[err.argument];
+ push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", GDScriptFunctions::get_func_name(builtin_function), err.argument + 1,
+ type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()),
+ p_call->arguments[err.argument]);
+ } break;
+ case Callable::CallError::CALL_ERROR_INVALID_METHOD:
+ push_error(vformat(R"(Invalid call for function "%s".)", GDScriptFunctions::get_func_name(builtin_function)), p_call);
+ break;
+ case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
+ push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call);
+ break;
+ case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
+ push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call);
+ break;
+ case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL:
+ break; // Can't happen in a builtin constructor.
+ case Callable::CallError::CALL_OK:
+ p_call->is_constant = true;
+ p_call->reduced_value = value;
+ break;
+ }
+ } else {
+ validate_call_arg(function_info, p_call);
+ }
+ p_call->set_datatype(type_from_property(function_info.return_val));
+ return;
+ }
+ }
+
+ GDScriptParser::DataType base_type;
+ call_type.kind = GDScriptParser::DataType::VARIANT;
+ bool is_self = false;
+
+ if (p_call->is_super) {
+ base_type = parser->current_class->base_type;
+ is_self = true;
+ } else if (p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ base_type = parser->current_class->get_datatype();
+ is_self = true;
+ } else if (p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) {
+ GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee);
+ if (!subscript->is_attribute) {
+ // Invalid call. Error already sent in parser.
+ // TODO: Could check if Callable here.
+ p_call->set_datatype(call_type);
+ mark_node_unsafe(p_call);
+ return;
+ }
+ reduce_expression(subscript->base);
+
+ base_type = subscript->base->get_datatype();
+ } else {
+ // Invalid call. Error already sent in parser.
+ // TODO: Could check if Callable here too.
+ p_call->set_datatype(call_type);
+ mark_node_unsafe(p_call);
+ return;
+ }
+
+ bool is_static = false;
+ bool is_vararg = false;
+ int default_arg_count = 0;
+ GDScriptParser::DataType return_type;
+ List<GDScriptParser::DataType> par_types;
+
+ if (get_function_signature(p_call, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) {
+ validate_call_arg(par_types, default_arg_count, is_vararg, p_call);
+
+ if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) {
+ push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee);
+ }
+
+ call_type = return_type;
+ } else {
+ // Check if the name exists as something else.
+ bool found = false;
+ if (!p_call->is_super) {
+ GDScriptParser::IdentifierNode *callee_id;
+ if (p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ callee_id = static_cast<GDScriptParser::IdentifierNode *>(p_call->callee);
+ } else {
+ // Can only be attribute.
+ callee_id = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee)->attribute;
+ }
+ if (callee_id) {
+ reduce_identifier_from_base(callee_id, &base_type);
+ GDScriptParser::DataType callee_type = callee_id->get_datatype();
+ if (callee_type.is_set() && !callee_type.is_variant()) {
+ found = true;
+ if (callee_type.builtin_type == Variant::CALLABLE) {
+ push_error(vformat(R"*(Name "%s" is a Callable. You can call it with "%s.call()" instead.)*", p_call->function_name, p_call->function_name), p_call->callee);
+ } else {
+ push_error(vformat(R"*(Name "%s" called as a function but is a "%s".)*", p_call->function_name, callee_type.to_string()), p_call->callee);
+ }
+#ifdef DEBUG_ENABLED
+ } else if (!is_self) {
+ parser->push_warning(p_call, GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->function_name, base_type.to_string());
+ mark_node_unsafe(p_call);
+#endif
+ }
+ }
+ }
+ if (!found && is_self) {
+ String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
+ push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
+ }
+ }
+
+ if (call_type.is_coroutine && !is_await) {
+ push_error(vformat(R"*(Function "%s()" is a coroutine, so it must be called with "await".)*", p_call->function_name), p_call->callee);
+ }
+
+ p_call->set_datatype(call_type);
+}
+
+void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
+ reduce_expression(p_cast->operand);
+
+ GDScriptParser::DataType cast_type = resolve_datatype(p_cast->cast_type);
+
+ if (!cast_type.is_set()) {
+ return;
+ }
+
+ cast_type.is_meta_type = false; // The casted value won't be a type name.
+ p_cast->set_datatype(cast_type);
+
+ if (!cast_type.is_variant()) {
+ GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
+ if (!op_type.is_variant()) {
+ bool valid = false;
+ if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) {
+ valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type);
+ } else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) {
+ valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type);
+ }
+
+ if (!valid) {
+ push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type);
+ }
+ }
+ } else {
+ mark_node_unsafe(p_cast);
+ }
+#ifdef DEBUG_ENABLED
+ if (p_cast->operand->get_datatype().is_variant()) {
+ parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string());
+ mark_node_unsafe(p_cast);
+ }
+#endif
+
+ // TODO: Perform cast on constants.
+}
+
+void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) {
+ bool all_is_constant = true;
+
+ for (int i = 0; i < p_dictionary->elements.size(); i++) {
+ const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
+ if (p_dictionary->style == GDScriptParser::DictionaryNode::PYTHON_DICT) {
+ reduce_expression(element.key);
+ }
+ reduce_expression(element.value);
+ all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant;
+ }
+
+ if (all_is_constant) {
+ Dictionary dict;
+ for (int i = 0; i < p_dictionary->elements.size(); i++) {
+ const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
+ dict[element.key->reduced_value] = element.value->reduced_value;
+ }
+ p_dictionary->is_constant = true;
+ p_dictionary->reduced_value = dict;
+ }
+
+ // It's dictionary in any case.
+ GDScriptParser::DataType dict_type;
+ dict_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ dict_type.kind = GDScriptParser::DataType::BUILTIN;
+ dict_type.builtin_type = Variant::DICTIONARY;
+ dict_type.is_constant = true;
+
+ p_dictionary->set_datatype(dict_type);
+}
+
+void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) {
+ GDScriptParser::DataType result;
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.native_type = "Node";
+ result.builtin_type = Variant::OBJECT;
+
+ if (!ClassDB::is_parent_class(get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) {
+ push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node);
+ }
+
+ p_get_node->set_datatype(result);
+}
+
+GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const StringName &p_class_name) {
+ Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(p_class_name));
+ ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ type.kind = GDScriptParser::DataType::CLASS;
+ type.builtin_type = Variant::OBJECT;
+ type.native_type = ScriptServer::get_global_class_native_base(p_class_name);
+ type.class_type = ref->get_parser()->head;
+ type.script_path = ref->get_parser()->script_path;
+ type.is_constant = true;
+ type.is_meta_type = true;
+ return type;
+}
+
+void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base) {
+ GDScriptParser::DataType base;
+ if (p_base == nullptr) {
+ base = type_from_metatype(parser->current_class->get_datatype());
+ } else {
+ base = *p_base;
+ }
+
+ const StringName &name = p_identifier->name;
+
+ if (base.kind == GDScriptParser::DataType::BUILTIN) {
+ if (base.is_meta_type) {
+ bool valid = true;
+ Variant result = Variant::get_constant_value(base.builtin_type, name, &valid);
+ if (valid) {
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = result;
+ p_identifier->set_datatype(type_from_variant(result));
+ } else {
+ push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier);
+ }
+ } else {
+ Callable::CallError temp;
+ Variant dummy = Variant::construct(base.builtin_type, nullptr, 0, temp);
+ List<PropertyInfo> properties;
+ dummy.get_property_list(&properties);
+ for (const List<PropertyInfo>::Element *E = properties.front(); E != nullptr; E = E->next()) {
+ const PropertyInfo &prop = E->get();
+ if (prop.name == name) {
+ p_identifier->set_datatype(type_from_property(prop));
+ return;
+ }
+ }
+ push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
+ }
+ return;
+ }
+
+ if (base.kind == GDScriptParser::DataType::ENUM) {
+ if (base.is_meta_type) {
+ if (base.enum_values.has(name)) {
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = base.enum_values[name];
+
+ GDScriptParser::DataType result;
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::ENUM_VALUE;
+ result.native_type = base.native_type;
+ result.enum_type = name;
+ p_identifier->set_datatype(result);
+ } else {
+ push_error(vformat(R"(Cannot find value "%s" in "%s".)", name, base.to_string()), p_identifier);
+ }
+ } else {
+ push_error(R"(Cannot get property from enum value.)", p_identifier);
+ }
+ return;
+ }
+
+ GDScriptParser::ClassNode *base_class = base.class_type;
+
+ // TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls).
+ while (base_class != nullptr) {
+ if (base_class->identifier && base_class->identifier->name == name) {
+ p_identifier->set_datatype(base_class->get_datatype());
+ return;
+ }
+ if (base_class->has_member(name)) {
+ const GDScriptParser::ClassNode::Member &member = base_class->get_member(name);
+ p_identifier->set_datatype(member.get_datatype());
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::CONSTANT:
+ // For out-of-order resolution:
+ reduce_expression(member.constant->initializer);
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = member.constant->initializer->reduced_value;
+ p_identifier->set_datatype(member.constant->initializer->get_datatype());
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT;
+ p_identifier->constant_source = member.constant;
+ break;
+ case GDScriptParser::ClassNode::Member::ENUM_VALUE:
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = member.enum_value.value;
+ break;
+ case GDScriptParser::ClassNode::Member::VARIABLE:
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE;
+ p_identifier->variable_source = member.variable;
+ break;
+ case GDScriptParser::ClassNode::Member::FUNCTION:
+ resolve_function_signature(member.function);
+ p_identifier->set_datatype(make_callable_type(member.function->info));
+ break;
+ default:
+ break; // Type already set.
+ }
+ return;
+ }
+ // Check outer constants.
+ // TODO: Allow outer static functions.
+ GDScriptParser::ClassNode *outer = base_class->outer;
+ while (outer != nullptr) {
+ if (outer->has_member(name)) {
+ const GDScriptParser::ClassNode::Member &member = base_class->get_member(name);
+ if (member.type == GDScriptParser::ClassNode::Member::CONSTANT) {
+ // TODO: Make sure loops won't cause problem. And make special error message for those.
+ // For out-of-order resolution:
+ reduce_expression(member.constant->initializer);
+ p_identifier->set_datatype(member.get_datatype());
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = member.constant->initializer->reduced_value;
+ return;
+ }
+ }
+ outer = outer->outer;
+ }
+
+ base_class = base_class->base_type.class_type;
+ }
+
+ // Check native members.
+ const StringName &native = get_real_class_name(base.native_type);
+
+ if (class_exists(native)) {
+ PropertyInfo prop_info;
+ MethodInfo method_info;
+ if (ClassDB::get_property_info(native, name, &prop_info)) {
+ p_identifier->set_datatype(type_from_property(prop_info));
+ return;
+ }
+ if (ClassDB::get_method_info(native, name, &method_info)) {
+ // Method is callable.
+ p_identifier->set_datatype(make_callable_type(method_info));
+ return;
+ }
+ if (ClassDB::get_signal(native, name, &method_info)) {
+ // Signal is a type too.
+ p_identifier->set_datatype(make_signal_type(method_info));
+ return;
+ }
+ if (ClassDB::has_enum(native, name)) {
+ p_identifier->set_datatype(make_native_enum_type(native, name));
+ return;
+ }
+ bool valid = false;
+ int int_constant = ClassDB::get_integer_constant(native, name, &valid);
+ if (valid) {
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = int_constant;
+ p_identifier->set_datatype(type_from_variant(int_constant));
+ return;
+ }
+ }
+}
+
+void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) {
+ // TODO: This is opportunity to further infer types.
+ // Check if identifier is local.
+ // If that's the case, the declaration already was solved before.
+ switch (p_identifier->source) {
+ case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER:
+ p_identifier->set_datatype(p_identifier->parameter_source->get_datatype());
+ return;
+ case GDScriptParser::IdentifierNode::LOCAL_CONSTANT:
+ case GDScriptParser::IdentifierNode::MEMBER_CONSTANT:
+ p_identifier->set_datatype(p_identifier->constant_source->get_datatype());
+ p_identifier->is_constant = true;
+ // TODO: Constant should have a value on the node itself.
+ p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value;
+ return;
+ case GDScriptParser::IdentifierNode::MEMBER_VARIABLE:
+ p_identifier->variable_source->usages++;
+ [[fallthrough]];
+ case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
+ p_identifier->set_datatype(p_identifier->variable_source->get_datatype());
+ return;
+ case GDScriptParser::IdentifierNode::LOCAL_ITERATOR:
+ p_identifier->set_datatype(p_identifier->bind_source->get_datatype());
+ return;
+ case GDScriptParser::IdentifierNode::LOCAL_BIND: {
+ GDScriptParser::DataType result = p_identifier->bind_source->get_datatype();
+ result.is_constant = true;
+ p_identifier->set_datatype(result);
+ return;
+ }
+ case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE:
+ break;
+ }
+
+ // Not a local, so check members.
+ reduce_identifier_from_base(p_identifier);
+ if (p_identifier->get_datatype().is_set()) {
+ // Found.
+ return;
+ }
+
+ StringName name = p_identifier->name;
+ p_identifier->source = GDScriptParser::IdentifierNode::UNDEFINED_SOURCE;
+
+ // Check globals.
+ if (GDScriptParser::get_builtin_type(name) < Variant::VARIANT_MAX) {
+ if (can_be_builtin) {
+ p_identifier->set_datatype(make_builtin_meta_type(GDScriptParser::get_builtin_type(name)));
+ return;
+ } else {
+ push_error(R"(Builtin type cannot be used as a name on its own.)", p_identifier);
+ }
+ }
+
+ if (class_exists(name)) {
+ p_identifier->set_datatype(make_native_meta_type(name));
+ return;
+ }
+
+ if (ScriptServer::is_global_class(name)) {
+ p_identifier->set_datatype(make_global_class_meta_type(name));
+ return;
+ }
+
+ if (GDScriptLanguage::get_singleton()->get_global_map().has(name)) {
+ int idx = GDScriptLanguage::get_singleton()->get_global_map()[name];
+ Variant constant = GDScriptLanguage::get_singleton()->get_global_array()[idx];
+ p_identifier->set_datatype(type_from_variant(constant));
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = constant;
+ return;
+ }
+
+ if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(name)) {
+ Variant constant = GDScriptLanguage::get_singleton()->get_named_globals_map()[name];
+ p_identifier->set_datatype(type_from_variant(constant));
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = constant;
+ return;
+ }
+
+ // Not found.
+ // Check if it's a builtin function.
+ if (parser->get_builtin_function(name) < GDScriptFunctions::FUNC_MAX) {
+ push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier);
+ } else {
+ push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
+ }
+ GDScriptParser::DataType dummy;
+ dummy.kind = GDScriptParser::DataType::VARIANT;
+ p_identifier->set_datatype(dummy); // Just so type is set to something.
+}
+
+void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) {
+ p_literal->reduced_value = p_literal->value;
+ p_literal->is_constant = true;
+
+ p_literal->set_datatype(type_from_variant(p_literal->reduced_value));
+}
+
+void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
+ p_preload->is_constant = true;
+ p_preload->reduced_value = p_preload->resource;
+ p_preload->set_datatype(type_from_variant(p_preload->reduced_value));
+}
+
+void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) {
+ p_self->is_constant = false;
+ p_self->set_datatype(type_from_metatype(parser->current_class->get_datatype()));
+}
+
+void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) {
+ if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) {
+ reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true);
+ } else {
+ reduce_expression(p_subscript->base);
+ }
+
+ GDScriptParser::DataType result_type;
+
+ // Reduce index first. If it's a constant StringName, use attribute instead.
+ if (!p_subscript->is_attribute) {
+ if (p_subscript->index == nullptr) {
+ return;
+ }
+ reduce_expression(p_subscript->index);
+
+ if (p_subscript->index->is_constant && p_subscript->index->reduced_value.get_type() == Variant::STRING_NAME) {
+ GDScriptParser::IdentifierNode *attribute = parser->alloc_node<GDScriptParser::IdentifierNode>();
+ // Copy location for better error message.
+ attribute->start_line = p_subscript->index->start_line;
+ attribute->end_line = p_subscript->index->end_line;
+ attribute->leftmost_column = p_subscript->index->leftmost_column;
+ attribute->rightmost_column = p_subscript->index->rightmost_column;
+ p_subscript->is_attribute = true;
+ p_subscript->attribute = attribute;
+ }
+ }
+
+ if (p_subscript->is_attribute) {
+ if (p_subscript->attribute == nullptr) {
+ return;
+ }
+ if (p_subscript->base->is_constant) {
+ // Just try to get it.
+ bool valid = false;
+ Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, &valid);
+ if (!valid) {
+ push_error(vformat(R"(Cannot get member "%s" from "%s".)", p_subscript->attribute->name, p_subscript->base->reduced_value), p_subscript->index);
+ } else {
+ p_subscript->is_constant = true;
+ p_subscript->reduced_value = value;
+ result_type = type_from_variant(value);
+ }
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ } else {
+ GDScriptParser::DataType base_type = p_subscript->base->get_datatype();
+
+ if (base_type.is_variant()) {
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ mark_node_unsafe(p_subscript);
+ } else {
+ reduce_identifier_from_base(p_subscript->attribute, &base_type);
+ GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
+ if (attr_type.is_set()) {
+ result_type = attr_type;
+ p_subscript->is_constant = p_subscript->attribute->is_constant;
+ p_subscript->reduced_value = p_subscript->attribute->reduced_value;
+ } else {
+ if (base_type.kind == GDScriptParser::DataType::BUILTIN) {
+ push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, base_type.to_string()), p_subscript->attribute);
+#ifdef DEBUG_ENABLED
+ } else {
+ parser->push_warning(p_subscript, GDScriptWarning::UNSAFE_PROPERTY_ACCESS, p_subscript->attribute->name, base_type.to_string());
+#endif
+ }
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ }
+ }
+ }
+ } else {
+ // Index was already reduced before.
+
+ if (p_subscript->base->is_constant && p_subscript->index->is_constant) {
+ // Just try to get it.
+ bool valid = false;
+ Variant value = p_subscript->base->reduced_value.get(p_subscript->index->reduced_value, &valid);
+ if (!valid) {
+ push_error(vformat(R"(Cannot get index "%s" from "%s".)", p_subscript->index->reduced_value, p_subscript->base->reduced_value), p_subscript->index);
+ } else {
+ p_subscript->is_constant = true;
+ p_subscript->reduced_value = value;
+ result_type = type_from_variant(value);
+ }
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ } else {
+ GDScriptParser::DataType base_type = p_subscript->base->get_datatype();
+ GDScriptParser::DataType index_type = p_subscript->index->get_datatype();
+
+ if (base_type.is_variant()) {
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ mark_node_unsafe(p_subscript);
+ } else {
+ if (base_type.kind == GDScriptParser::DataType::BUILTIN && !index_type.is_variant()) {
+ // Check if indexing is valid.
+ bool error = index_type.kind != GDScriptParser::DataType::BUILTIN && base_type.builtin_type != Variant::DICTIONARY;
+ if (!error) {
+ switch (base_type.builtin_type) {
+ // Expect int or real as index.
+ case Variant::PACKED_BYTE_ARRAY:
+ case Variant::PACKED_COLOR_ARRAY:
+ case Variant::PACKED_FLOAT32_ARRAY:
+ case Variant::PACKED_FLOAT64_ARRAY:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY:
+ case Variant::PACKED_STRING_ARRAY:
+ case Variant::PACKED_VECTOR2_ARRAY:
+ case Variant::PACKED_VECTOR3_ARRAY:
+ case Variant::ARRAY:
+ case Variant::STRING:
+ error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT;
+ break;
+ // Expect String only.
+ case Variant::RECT2:
+ case Variant::RECT2I:
+ case Variant::PLANE:
+ case Variant::QUAT:
+ case Variant::AABB:
+ case Variant::OBJECT:
+ error = index_type.builtin_type != Variant::STRING;
+ break;
+ // Expect String or number.
+ case Variant::BASIS:
+ case Variant::VECTOR2:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3:
+ case Variant::VECTOR3I:
+ case Variant::TRANSFORM:
+ case Variant::TRANSFORM2D:
+ error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT &&
+ index_type.builtin_type != Variant::STRING;
+ break;
+ // Expect String or int.
+ case Variant::COLOR:
+ error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING;
+ break;
+ // Don't support indexing, but we will check it later.
+ case Variant::_RID:
+ case Variant::BOOL:
+ case Variant::CALLABLE:
+ case Variant::FLOAT:
+ case Variant::INT:
+ case Variant::NIL:
+ case Variant::NODE_PATH:
+ case Variant::SIGNAL:
+ case Variant::STRING_NAME:
+ break;
+ // Here for completeness.
+ case Variant::DICTIONARY:
+ case Variant::VARIANT_MAX:
+ break;
+ }
+
+ if (error) {
+ push_error(vformat(R"(Invalid index type "%s" for a base of type "%s".)", index_type.to_string(), base_type.to_string()), p_subscript->index);
+ }
+ }
+ } else if (base_type.kind != GDScriptParser::DataType::BUILTIN && !index_type.is_variant()) {
+ if (index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME) {
+ push_error(vformat(R"(Only String or StringName can be used as index for type "%s", but received a "%s".)", base_type.to_string(), index_type.to_string()), p_subscript->index);
+ }
+ }
+
+ // Check resulting type if possible.
+ result_type.builtin_type = Variant::NIL;
+ result_type.kind = GDScriptParser::DataType::BUILTIN;
+ result_type.type_source = GDScriptParser::DataType::INFERRED;
+
+ switch (base_type.builtin_type) {
+ // Can't index at all.
+ case Variant::_RID:
+ case Variant::BOOL:
+ case Variant::CALLABLE:
+ case Variant::FLOAT:
+ case Variant::INT:
+ case Variant::NIL:
+ case Variant::NODE_PATH:
+ case Variant::SIGNAL:
+ case Variant::STRING_NAME:
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ push_error(vformat(R"(Cannot use subscript operator on a base of type "%s".)", base_type.to_string()), p_subscript->base);
+ break;
+ // Return int.
+ case Variant::PACKED_BYTE_ARRAY:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3I:
+ result_type.builtin_type = Variant::INT;
+ break;
+ // Return float.
+ case Variant::PACKED_FLOAT32_ARRAY:
+ case Variant::PACKED_FLOAT64_ARRAY:
+ case Variant::VECTOR2:
+ case Variant::VECTOR3:
+ case Variant::QUAT:
+ result_type.builtin_type = Variant::FLOAT;
+ break;
+ // Return Color.
+ case Variant::PACKED_COLOR_ARRAY:
+ result_type.builtin_type = Variant::COLOR;
+ break;
+ // Return String.
+ case Variant::PACKED_STRING_ARRAY:
+ case Variant::STRING:
+ result_type.builtin_type = Variant::STRING;
+ break;
+ // Return Vector2.
+ case Variant::PACKED_VECTOR2_ARRAY:
+ case Variant::TRANSFORM2D:
+ case Variant::RECT2:
+ result_type.builtin_type = Variant::VECTOR2;
+ break;
+ // Return Vector2I.
+ case Variant::RECT2I:
+ result_type.builtin_type = Variant::VECTOR2I;
+ break;
+ // Return Vector3.
+ case Variant::PACKED_VECTOR3_ARRAY:
+ case Variant::AABB:
+ case Variant::BASIS:
+ result_type.builtin_type = Variant::VECTOR3;
+ break;
+ // Depends on the index.
+ case Variant::TRANSFORM:
+ case Variant::PLANE:
+ case Variant::COLOR:
+ case Variant::ARRAY:
+ case Variant::DICTIONARY:
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ result_type.type_source = GDScriptParser::DataType::UNDETECTED;
+ break;
+ // Here for completeness.
+ case Variant::OBJECT:
+ case Variant::VARIANT_MAX:
+ break;
+ }
+ }
+ }
+ }
+
+ p_subscript->set_datatype(result_type);
+}
+
+void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op) {
+ reduce_expression(p_ternary_op->condition);
+ reduce_expression(p_ternary_op->true_expr);
+ reduce_expression(p_ternary_op->false_expr);
+
+ GDScriptParser::DataType result;
+
+ if (p_ternary_op->condition && p_ternary_op->condition->is_constant && p_ternary_op->true_expr->is_constant && p_ternary_op->false_expr && p_ternary_op->false_expr->is_constant) {
+ p_ternary_op->is_constant = true;
+ if (p_ternary_op->condition->reduced_value.booleanize()) {
+ p_ternary_op->reduced_value = p_ternary_op->true_expr->reduced_value;
+ } else {
+ p_ternary_op->reduced_value = p_ternary_op->false_expr->reduced_value;
+ }
+ }
+
+ GDScriptParser::DataType true_type;
+ if (p_ternary_op->true_expr) {
+ true_type = p_ternary_op->true_expr->get_datatype();
+ } else {
+ true_type.kind = GDScriptParser::DataType::VARIANT;
+ }
+ GDScriptParser::DataType false_type;
+ if (p_ternary_op->false_expr) {
+ false_type = p_ternary_op->false_expr->get_datatype();
+ } else {
+ false_type.kind = GDScriptParser::DataType::VARIANT;
+ }
+
+ if (true_type.is_variant() || false_type.is_variant()) {
+ result.kind = GDScriptParser::DataType::VARIANT;
+ } else {
+ result = true_type;
+ if (!is_type_compatible(true_type, false_type)) {
+ result = false_type;
+ if (!is_type_compatible(false_type, true_type)) {
+ result.type_source = GDScriptParser::DataType::UNDETECTED;
+ result.kind = GDScriptParser::DataType::VARIANT;
+#ifdef DEBUG_ENABLED
+ parser->push_warning(p_ternary_op, GDScriptWarning::INCOMPATIBLE_TERNARY);
+#endif
+ }
+ }
+ }
+
+ p_ternary_op->set_datatype(result);
+}
+
+void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) {
+ reduce_expression(p_unary_op->operand);
+
+ GDScriptParser::DataType result;
+
+ if (p_unary_op->operand->is_constant) {
+ p_unary_op->is_constant = true;
+ p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant());
+ result = type_from_variant(p_unary_op->reduced_value);
+ } else if (p_unary_op->operand->get_datatype().is_variant()) {
+ result.kind = GDScriptParser::DataType::VARIANT;
+ mark_node_unsafe(p_unary_op);
+ } else {
+ bool valid = false;
+ result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), p_unary_op->operand->get_datatype(), valid);
+
+ if (!valid) {
+ push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", p_unary_op->operand->get_datatype().to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op->operand);
+ }
+ }
+
+ p_unary_op->set_datatype(result);
+}
+
+GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value) {
+ GDScriptParser::DataType result;
+ result.is_constant = true;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = p_value.get_type();
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; // Constant has explicit type.
+
+ if (p_value.get_type() == Variant::OBJECT) {
+ Object *obj = p_value;
+ if (!obj) {
+ return GDScriptParser::DataType();
+ }
+ result.native_type = obj->get_class_name();
+
+ Ref<Script> scr = p_value; // Check if value is a script itself.
+ if (scr.is_valid()) {
+ result.is_meta_type = true;
+ } else {
+ result.is_meta_type = false;
+ scr = obj->get_script();
+ }
+ if (scr.is_valid()) {
+ result.script_type = scr;
+ result.script_path = scr->get_path();
+ Ref<GDScript> gds = scr;
+ if (gds.is_valid()) {
+ result.kind = GDScriptParser::DataType::CLASS;
+ Ref<GDScriptParserRef> ref = get_parser_for(gds->get_path());
+ ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+ result.class_type = ref->get_parser()->head;
+ result.script_path = ref->get_parser()->script_path;
+ } else {
+ result.kind = GDScriptParser::DataType::SCRIPT;
+ }
+ result.native_type = scr->get_instance_base_type();
+ } else {
+ result.kind = GDScriptParser::DataType::NATIVE;
+ if (result.native_type == GDScriptNativeClass::get_class_static()) {
+ result.is_meta_type = true;
+ }
+ }
+ }
+
+ return result;
+}
+
+GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) const {
+ GDScriptParser::DataType result = p_meta_type;
+ result.is_meta_type = false;
+ result.is_constant = false;
+ return result;
+}
+
+GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property) const {
+ GDScriptParser::DataType result;
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ if (p_property.type == Variant::NIL && (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
+ // Variant
+ result.kind = GDScriptParser::DataType::VARIANT;
+ return result;
+ }
+ result.builtin_type = p_property.type;
+ if (p_property.type == Variant::OBJECT) {
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
+ } else {
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ }
+ return result;
+}
+
+bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) {
+ r_static = false;
+ r_vararg = false;
+ r_default_arg_count = 0;
+ StringName function_name = p_function;
+
+ if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) {
+ // Construct a base type to get methods.
+ Callable::CallError err;
+ Variant dummy = Variant::construct(p_base_type.builtin_type, nullptr, 0, err);
+ if (err.error != Callable::CallError::CALL_OK) {
+ ERR_FAIL_V_MSG(false, "Could not construct base Variant type.");
+ }
+ List<MethodInfo> methods;
+ dummy.get_method_list(&methods);
+
+ for (const List<MethodInfo>::Element *E = methods.front(); E != nullptr; E = E->next()) {
+ if (E->get().name == p_function) {
+ return function_signature_from_info(E->get(), r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
+ }
+ }
+
+ return false;
+ }
+
+ bool is_constructor = p_base_type.is_meta_type && p_function == "new";
+ if (is_constructor) {
+ function_name = "_init";
+ r_static = true;
+ }
+
+ GDScriptParser::ClassNode *base_class = p_base_type.class_type;
+ GDScriptParser::FunctionNode *found_function = nullptr;
+
+ while (found_function == nullptr && base_class != nullptr) {
+ if (base_class->has_member(function_name)) {
+ if (base_class->get_member(function_name).type != GDScriptParser::ClassNode::Member::FUNCTION) {
+ // TODO: If this is Callable it can have a better error message.
+ push_error(vformat(R"(Member "%s" is not a function.)", function_name), p_source);
+ return false;
+ }
+ found_function = base_class->get_member(function_name).function;
+ }
+ base_class = base_class->base_type.class_type;
+ }
+
+ if (found_function != nullptr) {
+ r_static = is_constructor || found_function->is_static;
+ for (int i = 0; i < found_function->parameters.size(); i++) {
+ r_par_types.push_back(found_function->parameters[i]->get_datatype());
+ if (found_function->parameters[i]->default_value != nullptr) {
+ r_default_arg_count++;
+ }
+ }
+ r_return_type = found_function->get_datatype();
+ r_return_type.is_coroutine = found_function->is_coroutine;
+
+ return true;
+ }
+
+ Ref<Script> base_script = p_base_type.script_type;
+
+ while (base_script.is_valid() && base_script->is_valid()) {
+ MethodInfo info = base_script->get_method_info(function_name);
+
+ if (!(info == MethodInfo())) {
+ return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
+ }
+ base_script = base_script->get_base_script();
+ }
+
+ // If the base is a script, it might be trying to access members of the Script class itself.
+ if (p_base_type.is_meta_type && !is_constructor && (p_base_type.kind == GDScriptParser::DataType::SCRIPT || p_base_type.kind == GDScriptParser::DataType::CLASS)) {
+ MethodInfo info;
+ StringName script_class = p_base_type.kind == GDScriptParser::DataType::SCRIPT ? p_base_type.script_type->get_class_name() : StringName(GDScript::get_class_static());
+
+ if (ClassDB::get_method_info(script_class, function_name, &info)) {
+ return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
+ }
+ }
+
+ StringName base_native = p_base_type.native_type;
+#ifdef DEBUG_ENABLED
+ if (base_native != StringName()) {
+ // Empty native class might happen in some Script implementations.
+ // Just ignore it.
+ if (!class_exists(base_native)) {
+ ERR_FAIL_V_MSG(false, vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native));
+ }
+ }
+#endif
+
+ if (is_constructor) {
+ // Native types always have a default constructor.
+ r_return_type = p_base_type;
+ r_return_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ r_return_type.is_meta_type = false;
+ return true;
+ }
+
+ StringName real_native = get_real_class_name(base_native);
+
+ MethodInfo info;
+ if (ClassDB::get_method_info(real_native, function_name, &info)) {
+ return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
+ }
+
+ return false;
+}
+
+bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) {
+ r_return_type = type_from_property(p_info.return_val);
+ r_default_arg_count = p_info.default_arguments.size();
+ r_vararg = (p_info.flags & METHOD_FLAG_VARARG) != 0;
+
+ for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E != nullptr; E = E->next()) {
+ r_par_types.push_back(type_from_property(E->get()));
+ }
+ return true;
+}
+
+bool GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call) {
+ List<GDScriptParser::DataType> arg_types;
+
+ for (const List<PropertyInfo>::Element *E = p_method.arguments.front(); E != nullptr; E = E->next()) {
+ arg_types.push_back(type_from_property(E->get()));
+ }
+
+ return validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call);
+}
+
+bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call) {
+ bool valid = true;
+
+ if (p_call->arguments.size() < p_par_types.size() - p_default_args_count) {
+ push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", p_call->function_name, p_par_types.size() - p_default_args_count, p_call->arguments.size()), p_call);
+ valid = false;
+ }
+ if (!p_is_vararg && p_call->arguments.size() > p_par_types.size()) {
+ push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", p_call->function_name, p_par_types.size(), p_call->arguments.size()), p_call->arguments[p_par_types.size()]);
+ valid = false;
+ }
+
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ if (i >= p_par_types.size()) {
+ // Already on vararg place.
+ break;
+ }
+ GDScriptParser::DataType par_type = p_par_types[i];
+ GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype();
+
+ if (arg_type.is_variant()) {
+ // Argument can be anything, so this is unsafe.
+ mark_node_unsafe(p_call->arguments[i]);
+ } else if (!is_type_compatible(par_type, arg_type, true)) {
+ // Supertypes are acceptable for dynamic compliance, but it's unsafe.
+ mark_node_unsafe(p_call);
+ if (!is_type_compatible(arg_type, par_type)) {
+ push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*",
+ p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()),
+ p_call->arguments[i]);
+ valid = false;
+ }
+#ifdef DEBUG_ENABLED
+ } else {
+ if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) {
+ parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name);
+ }
+#endif
+ }
+ }
+ return valid;
+}
+
+#ifdef DEBUG_ENABLED
+bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context) {
+ const StringName &name = p_local->name;
+ GDScriptParser::DataType base = parser->current_class->get_datatype();
+
+ GDScriptParser::ClassNode *base_class = base.class_type;
+
+ while (base_class != nullptr) {
+ if (base_class->has_member(name)) {
+ parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_local->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()));
+ return true;
+ }
+ base_class = base_class->base_type.class_type;
+ }
+
+ StringName base_native = base.native_type;
+
+ ERR_FAIL_COND_V_MSG(!class_exists(base_native), false, "Non-existent native base class.");
+
+ StringName parent = base_native;
+ while (parent != StringName()) {
+ StringName real_class_name = get_real_class_name(parent);
+ if (ClassDB::has_method(real_class_name, name, true)) {
+ parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent);
+ return true;
+ } else if (ClassDB::has_signal(real_class_name, name, true)) {
+ parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "signal", parent);
+ return true;
+ } else if (ClassDB::has_property(real_class_name, name, true)) {
+ parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "property", parent);
+ return true;
+ } else if (ClassDB::has_integer_constant(real_class_name, name, true)) {
+ parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "constant", parent);
+ return true;
+ } else if (ClassDB::has_enum(real_class_name, name, true)) {
+ parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "enum", parent);
+ return true;
+ }
+ parent = ClassDB::get_parent_class(real_class_name);
+ }
+
+ return false;
+}
+#endif
+
+GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid) {
+ // This function creates dummy variant values and apply the operation to those. Less error-prone than keeping a table of valid operations.
+
+ GDScriptParser::DataType result;
+ result.kind = GDScriptParser::DataType::VARIANT;
+
+ Variant::Type a_type = p_a.builtin_type;
+ Variant::Type b_type = p_b.builtin_type;
+
+ Variant a;
+ REF a_ref;
+ if (a_type == Variant::OBJECT) {
+ a_ref.instance();
+ a = a_ref;
+ } else {
+ Callable::CallError err;
+ a = Variant::construct(a_type, nullptr, 0, err);
+ if (err.error != Callable::CallError::CALL_OK) {
+ r_valid = false;
+ ERR_FAIL_V_MSG(result, vformat("Could not construct value of type %s", Variant::get_type_name(a_type)));
+ }
+ }
+ Variant b;
+ REF b_ref;
+ if (b_type == Variant::OBJECT) {
+ b_ref.instance();
+ b = b_ref;
+ } else {
+ Callable::CallError err;
+ b = Variant::construct(b_type, nullptr, 0, err);
+ if (err.error != Callable::CallError::CALL_OK) {
+ r_valid = false;
+ ERR_FAIL_V_MSG(result, vformat("Could not construct value of type %s", Variant::get_type_name(b_type)));
+ }
+ }
+
+ // Avoid division by zero.
+ switch (b_type) {
+ case Variant::INT:
+ b = 1;
+ break;
+ case Variant::FLOAT:
+ b = 1.0;
+ break;
+ case Variant::VECTOR2:
+ b = Vector2(1.0, 1.0);
+ break;
+ case Variant::VECTOR2I:
+ b = Vector2i(1, 1);
+ break;
+ case Variant::VECTOR3:
+ b = Vector3(1.0, 1.0, 1.0);
+ break;
+ case Variant::VECTOR3I:
+ b = Vector3i(1, 1, 1);
+ break;
+ case Variant::COLOR:
+ b = Color(1.0, 1.0, 1.0, 1.0);
+ break;
+ default:
+ // No change needed.
+ break;
+ }
+
+ // Avoid error in formatting operator (%) where it doesn't find a placeholder.
+ if (a_type == Variant::STRING) {
+ a = String("%s");
+ }
+
+ Variant ret;
+ Variant::evaluate(p_operation, a, b, ret, r_valid);
+
+ if (r_valid) {
+ return type_from_variant(ret);
+ }
+
+ return result;
+}
+
+// TODO: Add safe/unsafe return variable (for variant cases)
+bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion) const {
+ // These return "true" so it doesn't affect users negatively.
+ ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type");
+ ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type");
+
+ if (p_target.kind == GDScriptParser::DataType::VARIANT) {
+ // Variant can receive anything.
+ return true;
+ }
+
+ if (p_source.kind == GDScriptParser::DataType::VARIANT) {
+ // TODO: This is acceptable but unsafe. Make sure unsafe line is set.
+ return true;
+ }
+
+ if (p_target.kind == GDScriptParser::DataType::BUILTIN) {
+ bool valid = p_source.kind == GDScriptParser::DataType::BUILTIN && p_target.builtin_type == p_source.builtin_type;
+ if (!valid && p_allow_implicit_conversion) {
+ valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type);
+ }
+ if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM_VALUE) {
+ // Enum value is also integer.
+ valid = true;
+ }
+ return valid;
+ }
+
+ if (p_target.kind == GDScriptParser::DataType::ENUM) {
+ if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) {
+ return true;
+ }
+ if (p_source.kind == GDScriptParser::DataType::ENUM_VALUE) {
+ if (p_source.native_type == p_target.native_type && p_target.enum_values.has(p_source.enum_type)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // From here on the target type is an object, so we have to test polymorphism.
+
+ if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::NIL) {
+ // null is acceptable in object.
+ return true;
+ }
+
+ StringName src_native;
+ Ref<Script> src_script;
+ const GDScriptParser::ClassNode *src_class = nullptr;
+
+ switch (p_source.kind) {
+ case GDScriptParser::DataType::NATIVE:
+ if (p_target.kind != GDScriptParser::DataType::NATIVE) {
+ // Non-native class cannot be supertype of native.
+ return false;
+ }
+ if (p_source.is_meta_type) {
+ src_native = GDScriptNativeClass::get_class_static();
+ } else {
+ src_native = p_source.native_type;
+ }
+ break;
+ case GDScriptParser::DataType::SCRIPT:
+ if (p_target.kind == GDScriptParser::DataType::CLASS) {
+ // A script type cannot be a subtype of a GDScript class.
+ return false;
+ }
+ if (p_source.is_meta_type) {
+ src_native = p_source.script_type->get_class_name();
+ } else {
+ src_script = p_source.script_type;
+ src_native = src_script->get_instance_base_type();
+ }
+ break;
+ case GDScriptParser::DataType::CLASS:
+ if (p_source.is_meta_type) {
+ src_native = GDScript::get_class_static();
+ } else {
+ src_class = p_source.class_type;
+ const GDScriptParser::ClassNode *base = src_class;
+ while (base->base_type.kind == GDScriptParser::DataType::CLASS) {
+ base = base->base_type.class_type;
+ }
+ src_native = base->base_type.native_type;
+ src_script = base->base_type.script_type;
+ }
+ break;
+ case GDScriptParser::DataType::VARIANT:
+ case GDScriptParser::DataType::BUILTIN:
+ case GDScriptParser::DataType::ENUM:
+ case GDScriptParser::DataType::ENUM_VALUE:
+ case GDScriptParser::DataType::UNRESOLVED:
+ break; // Already solved before.
+ }
+
+ // Get underscore-prefixed version for some classes.
+ src_native = get_real_class_name(src_native);
+
+ switch (p_target.kind) {
+ case GDScriptParser::DataType::NATIVE: {
+ if (p_target.is_meta_type) {
+ return ClassDB::is_parent_class(src_native, GDScriptNativeClass::get_class_static());
+ }
+ StringName tgt_native = get_real_class_name(p_target.native_type);
+ return ClassDB::is_parent_class(src_native, tgt_native);
+ }
+ case GDScriptParser::DataType::SCRIPT:
+ if (p_target.is_meta_type) {
+ return ClassDB::is_parent_class(src_native, p_target.script_type->get_class_name());
+ }
+ while (src_script.is_valid()) {
+ if (src_script == p_target.script_type) {
+ return true;
+ }
+ src_script = src_script->get_base_script();
+ }
+ return false;
+ case GDScriptParser::DataType::CLASS:
+ if (p_target.is_meta_type) {
+ return ClassDB::is_parent_class(src_native, GDScript::get_class_static());
+ }
+ while (src_class != nullptr) {
+ if (src_class->fqcn == p_target.class_type->fqcn) {
+ return true;
+ }
+ src_class = src_class->base_type.class_type;
+ }
+ return false;
+ case GDScriptParser::DataType::VARIANT:
+ case GDScriptParser::DataType::BUILTIN:
+ case GDScriptParser::DataType::ENUM:
+ case GDScriptParser::DataType::ENUM_VALUE:
+ case GDScriptParser::DataType::UNRESOLVED:
+ break; // Already solved before.
+ }
+
+ return false;
+}
+
+void GDScriptAnalyzer::push_error(const String &p_message, const GDScriptParser::Node *p_origin) {
+ mark_node_unsafe(p_origin);
+ parser->push_error(p_message, p_origin);
+}
+
+void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) {
+#ifdef DEBUG_ENABLED
+ for (int i = p_node->start_line; i <= p_node->end_line; i++) {
+ parser->unsafe_lines.insert(i);
+ }
+#endif
+}
+
+bool GDScriptAnalyzer::class_exists(const StringName &p_class) {
+ StringName real_name = get_real_class_name(p_class);
+ return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name);
+}
+
+Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) {
+ Ref<GDScriptParserRef> ref;
+ if (depended_parsers.has(p_path)) {
+ ref = depended_parsers[p_path];
+ } else {
+ Error err = OK;
+ ref = GDScriptCache::get_parser(p_path, GDScriptParserRef::EMPTY, err, parser->script_path);
+ depended_parsers[p_path] = ref;
+ }
+
+ return ref;
+}
+
+Error GDScriptAnalyzer::resolve_inheritance() {
+ return resolve_inheritance(parser->head);
+}
+
+Error GDScriptAnalyzer::resolve_interface() {
+ resolve_class_interface(parser->head);
+ return parser->errors.empty() ? OK : ERR_PARSE_ERROR;
+}
+
+Error GDScriptAnalyzer::resolve_body() {
+ resolve_class_body(parser->head);
+ return parser->errors.empty() ? OK : ERR_PARSE_ERROR;
+}
+
+Error GDScriptAnalyzer::resolve_program() {
+ resolve_class_interface(parser->head);
+ resolve_class_body(parser->head);
+
+ List<String> parser_keys;
+ depended_parsers.get_key_list(&parser_keys);
+ for (const List<String>::Element *E = parser_keys.front(); E != nullptr; E = E->next()) {
+ depended_parsers[E->get()]->raise_status(GDScriptParserRef::FULLY_SOLVED);
+ }
+ depended_parsers.clear();
+ return parser->errors.empty() ? OK : ERR_PARSE_ERROR;
+}
+
+Error GDScriptAnalyzer::analyze() {
+ parser->errors.clear();
+ Error err = resolve_inheritance(parser->head);
+ if (err) {
+ return err;
+ }
+ return resolve_program();
+}
+
+GDScriptAnalyzer::GDScriptAnalyzer(GDScriptParser *p_parser) {
+ parser = p_parser;
+}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
new file mode 100644
index 0000000000..85183d3272
--- /dev/null
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -0,0 +1,118 @@
+/*************************************************************************/
+/* gdscript_analyzer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_ANALYZER_H
+#define GDSCRIPT_ANALYZER_H
+
+#include "core/object.h"
+#include "core/reference.h"
+#include "core/set.h"
+#include "gdscript_cache.h"
+#include "gdscript_parser.h"
+
+class GDScriptAnalyzer {
+ GDScriptParser *parser = nullptr;
+ HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
+
+ Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
+ GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
+
+ void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement);
+
+ // This traverses the tree to resolve all TypeNodes.
+ Error resolve_program();
+
+ void resolve_annotation(GDScriptParser::AnnotationNode *p_annotation);
+ void resolve_class_interface(GDScriptParser::ClassNode *p_class);
+ void resolve_class_body(GDScriptParser::ClassNode *p_class);
+ void resolve_function_signature(GDScriptParser::FunctionNode *p_function);
+ void resolve_function_body(GDScriptParser::FunctionNode *p_function);
+ void resolve_node(GDScriptParser::Node *p_node);
+ void resolve_suite(GDScriptParser::SuiteNode *p_suite);
+ void resolve_if(GDScriptParser::IfNode *p_if);
+ void resolve_for(GDScriptParser::ForNode *p_for);
+ void resolve_while(GDScriptParser::WhileNode *p_while);
+ void resolve_variable(GDScriptParser::VariableNode *p_variable);
+ void resolve_constant(GDScriptParser::ConstantNode *p_constant);
+ void resolve_assert(GDScriptParser::AssertNode *p_assert);
+ void resolve_match(GDScriptParser::MatchNode *p_match);
+ void resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test);
+ void resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test);
+ void resolve_pararameter(GDScriptParser::ParameterNode *p_parameter);
+ void resolve_return(GDScriptParser::ReturnNode *p_return);
+
+ // Reduction functions.
+ void reduce_expression(GDScriptParser::ExpressionNode *p_expression);
+ void reduce_array(GDScriptParser::ArrayNode *p_array);
+ void reduce_assignment(GDScriptParser::AssignmentNode *p_assignment);
+ void reduce_await(GDScriptParser::AwaitNode *p_await);
+ void reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op);
+ void reduce_call(GDScriptParser::CallNode *p_call, bool is_await = false);
+ void reduce_cast(GDScriptParser::CastNode *p_cast);
+ void reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary);
+ void reduce_get_node(GDScriptParser::GetNodeNode *p_get_node);
+ void reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin = false);
+ void reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base = nullptr);
+ void reduce_literal(GDScriptParser::LiteralNode *p_literal);
+ void reduce_preload(GDScriptParser::PreloadNode *p_preload);
+ void reduce_self(GDScriptParser::SelfNode *p_self);
+ void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript);
+ void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op);
+ void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
+
+ // Helpers.
+ GDScriptParser::DataType type_from_variant(const Variant &p_value);
+ GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const;
+ GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const;
+ GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name);
+ bool get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
+ bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
+ bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
+ bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
+ GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid);
+ bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const;
+ void push_error(const String &p_message, const GDScriptParser::Node *p_origin);
+ void mark_node_unsafe(const GDScriptParser::Node *p_node);
+ bool class_exists(const StringName &p_class);
+ Ref<GDScriptParserRef> get_parser_for(const String &p_path);
+#ifdef DEBUG_ENABLED
+ bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
+#endif
+
+public:
+ Error resolve_inheritance();
+ Error resolve_interface();
+ Error resolve_body();
+ Error analyze();
+
+ GDScriptAnalyzer(GDScriptParser *p_parser);
+};
+
+#endif // GDSCRIPT_ANALYZER_H
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
new file mode 100644
index 0000000000..583283ff46
--- /dev/null
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -0,0 +1,244 @@
+/*************************************************************************/
+/* gdscript_cache.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_cache.h"
+
+#include "core/os/file_access.h"
+#include "core/vector.h"
+#include "gdscript.h"
+#include "gdscript_analyzer.h"
+#include "gdscript_parser.h"
+
+bool GDScriptParserRef::is_valid() const {
+ return parser != nullptr;
+}
+
+GDScriptParserRef::Status GDScriptParserRef::get_status() const {
+ return status;
+}
+
+GDScriptParser *GDScriptParserRef::get_parser() const {
+ return parser;
+}
+
+Error GDScriptParserRef::raise_status(Status p_new_status) {
+ ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA);
+
+ Error result = OK;
+
+ while (p_new_status > status) {
+ switch (status) {
+ case EMPTY:
+ result = parser->parse(GDScriptCache::get_source_code(path), path, false);
+ status = PARSED;
+ break;
+ case PARSED: {
+ analyzer = memnew(GDScriptAnalyzer(parser));
+ Error inheritance_result = analyzer->resolve_inheritance();
+ status = INHERITANCE_SOLVED;
+ if (result == OK) {
+ result = inheritance_result;
+ }
+ } break;
+ case INHERITANCE_SOLVED: {
+ Error interface_result = analyzer->resolve_interface();
+ status = INTERFACE_SOLVED;
+ if (result == OK) {
+ result = interface_result;
+ }
+ } break;
+ case INTERFACE_SOLVED: {
+ Error body_result = analyzer->resolve_body();
+ status = FULLY_SOLVED;
+ if (result == OK) {
+ result = body_result;
+ }
+ } break;
+ case FULLY_SOLVED: {
+ return result;
+ }
+ }
+ }
+
+ return result;
+}
+
+GDScriptParserRef::~GDScriptParserRef() {
+ if (parser != nullptr) {
+ memdelete(parser);
+ }
+ if (analyzer != nullptr) {
+ memdelete(analyzer);
+ }
+ MutexLock(GDScriptCache::singleton->lock);
+ GDScriptCache::singleton->parser_map.erase(path);
+}
+
+GDScriptCache *GDScriptCache::singleton = nullptr;
+
+void GDScriptCache::remove_script(const String &p_path) {
+ MutexLock(singleton->lock);
+ singleton->shallow_gdscript_cache.erase(p_path);
+ singleton->full_gdscript_cache.erase(p_path);
+}
+
+Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) {
+ MutexLock(singleton->lock);
+ Ref<GDScriptParserRef> ref;
+ if (p_owner != String()) {
+ singleton->dependencies[p_owner].insert(p_path);
+ }
+ if (singleton->parser_map.has(p_path)) {
+ ref = singleton->parser_map[p_path];
+ } else {
+ GDScriptParser *parser = memnew(GDScriptParser);
+ ref.instance();
+ ref->parser = parser;
+ ref->path = p_path;
+ singleton->parser_map[p_path] = ref;
+ ref->unreference();
+ }
+
+ r_error = ref->raise_status(p_status);
+
+ return ref;
+}
+
+String GDScriptCache::get_source_code(const String &p_path) {
+ Vector<uint8_t> source_file;
+ Error err;
+ FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err);
+ if (err) {
+ ERR_FAIL_COND_V(err, "");
+ }
+
+ int len = f->get_len();
+ source_file.resize(len + 1);
+ int r = f->get_buffer(source_file.ptrw(), len);
+ f->close();
+ ERR_FAIL_COND_V(r != len, "");
+ source_file.write[len] = 0;
+
+ String source;
+ if (source.parse_utf8((const char *)source_file.ptr())) {
+ ERR_FAIL_V_MSG("", "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode.");
+ }
+ return source;
+}
+
+Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const String &p_owner) {
+ MutexLock(singleton->lock);
+ if (p_owner != String()) {
+ singleton->dependencies[p_owner].insert(p_path);
+ }
+ if (singleton->full_gdscript_cache.has(p_path)) {
+ return singleton->full_gdscript_cache[p_path];
+ }
+ if (singleton->shallow_gdscript_cache.has(p_path)) {
+ return singleton->shallow_gdscript_cache[p_path];
+ }
+
+ Ref<GDScript> script;
+ script.instance();
+ script->set_path(p_path, true);
+ script->set_script_path(p_path);
+ script->load_source_code(p_path);
+
+ singleton->shallow_gdscript_cache[p_path] = script;
+ // The one in cache is not a hard reference: if the script dies somewhere else it's fine.
+ // Scripts remove themselves from cache when they die.
+ script->unreference();
+ return script;
+}
+
+Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner) {
+ MutexLock(singleton->lock);
+
+ if (p_owner != String()) {
+ singleton->dependencies[p_owner].insert(p_path);
+ }
+
+ r_error = OK;
+ if (singleton->full_gdscript_cache.has(p_path)) {
+ return singleton->full_gdscript_cache[p_path];
+ }
+ Ref<GDScript> script = get_shallow_script(p_path);
+
+ r_error = script->load_source_code(p_path);
+
+ if (r_error) {
+ return script;
+ }
+
+ r_error = script->reload();
+ if (r_error) {
+ return script;
+ }
+
+ singleton->full_gdscript_cache[p_path] = script;
+ singleton->shallow_gdscript_cache.erase(p_path);
+
+ return script;
+}
+
+Error GDScriptCache::finish_compiling(const String &p_owner) {
+ // Mark this as compiled.
+ Ref<GDScript> script = get_shallow_script(p_owner);
+ singleton->full_gdscript_cache[p_owner] = script;
+ singleton->shallow_gdscript_cache.erase(p_owner);
+
+ Set<String> depends = singleton->dependencies[p_owner];
+
+ Error err = OK;
+ for (const Set<String>::Element *E = depends.front(); E != nullptr; E = E->next()) {
+ Error this_err = OK;
+ // No need to save the script. We assume it's already referenced in the owner.
+ get_full_script(E->get(), this_err);
+
+ if (this_err != OK) {
+ err = this_err;
+ }
+ }
+
+ singleton->dependencies.erase(p_owner);
+
+ return err;
+}
+
+GDScriptCache::GDScriptCache() {
+ singleton = this;
+}
+
+GDScriptCache::~GDScriptCache() {
+ parser_map.clear();
+ shallow_gdscript_cache.clear();
+ full_gdscript_cache.clear();
+ singleton = nullptr;
+}
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
new file mode 100644
index 0000000000..770704d6eb
--- /dev/null
+++ b/modules/gdscript/gdscript_cache.h
@@ -0,0 +1,97 @@
+/*************************************************************************/
+/* gdscript_cache.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_CACHE_H
+#define GDSCRIPT_CACHE_H
+
+#include "core/hash_map.h"
+#include "core/os/mutex.h"
+#include "core/reference.h"
+#include "core/set.h"
+#include "gdscript.h"
+
+class GDScriptAnalyzer;
+class GDScriptParser;
+
+class GDScriptParserRef : public Reference {
+public:
+ enum Status {
+ EMPTY,
+ PARSED,
+ INHERITANCE_SOLVED,
+ INTERFACE_SOLVED,
+ FULLY_SOLVED,
+ };
+
+private:
+ GDScriptParser *parser = nullptr;
+ GDScriptAnalyzer *analyzer = nullptr;
+ Status status = EMPTY;
+ String path;
+
+ friend class GDScriptCache;
+
+public:
+ bool is_valid() const;
+ Status get_status() const;
+ GDScriptParser *get_parser() const;
+ Error raise_status(Status p_new_status);
+
+ GDScriptParserRef() {}
+ ~GDScriptParserRef();
+};
+
+class GDScriptCache {
+ // String key is full path.
+ HashMap<String, Ref<GDScriptParserRef>> parser_map;
+ HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
+ HashMap<String, Ref<GDScript>> full_gdscript_cache;
+ HashMap<String, Set<String>> dependencies;
+
+ friend class GDScript;
+ friend class GDScriptParserRef;
+
+ static GDScriptCache *singleton;
+
+ Mutex lock;
+ static void remove_script(const String &p_path);
+
+public:
+ static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String());
+ static String get_source_code(const String &p_path);
+ static Ref<GDScript> get_shallow_script(const String &p_path, const String &p_owner = String());
+ static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String());
+ static Error finish_compiling(const String &p_owner);
+
+ GDScriptCache();
+ ~GDScriptCache();
+};
+
+#endif // GDSCRIPT_CACHE_H
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 5bc9003c29..e34d87f5cc 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -31,9 +31,10 @@
#include "gdscript_compiler.h"
#include "gdscript.h"
+#include "gdscript_cache.h"
bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) {
- if (codegen.function_node && codegen.function_node->_static) {
+ if (codegen.function_node && codegen.function_node->is_static) {
return false;
}
@@ -66,18 +67,16 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N
error = p_error;
if (p_node) {
- err_line = p_node->line;
- err_column = p_node->column;
+ err_line = p_node->start_line;
+ err_column = p_node->leftmost_column;
} else {
err_line = 0;
err_column = 0;
}
}
-bool GDScriptCompiler::_create_unary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level) {
- ERR_FAIL_COND_V(on->arguments.size() != 1, false);
-
- int src_address_a = _parse_expression(codegen, on->arguments[0], p_stack_level);
+bool GDScriptCompiler::_create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level) {
+ int src_address_a = _parse_expression(codegen, on->operand, p_stack_level);
if (src_address_a < 0) {
return false;
}
@@ -90,10 +89,8 @@ bool GDScriptCompiler::_create_unary_operator(CodeGen &codegen, const GDScriptPa
return true;
}
-bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level, bool p_initializer, int p_index_addr) {
- ERR_FAIL_COND_V(on->arguments.size() != 2, false);
-
- int src_address_a = _parse_expression(codegen, on->arguments[0], p_stack_level, false, p_initializer, p_index_addr);
+bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer, int p_index_addr) {
+ int src_address_a = _parse_expression(codegen, p_left_operand, p_stack_level, false, p_initializer, p_index_addr);
if (src_address_a < 0) {
return false;
}
@@ -101,7 +98,7 @@ bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptP
p_stack_level++; //uses stack for return, increase stack
}
- int src_address_b = _parse_expression(codegen, on->arguments[1], p_stack_level, false, p_initializer);
+ int src_address_b = _parse_expression(codegen, p_right_operand, p_stack_level, false, p_initializer);
if (src_address_b < 0) {
return false;
}
@@ -113,8 +110,12 @@ bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptP
return true;
}
+bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer, int p_index_addr) {
+ return _create_binary_operator(codegen, on->left_operand, on->right_operand, op, p_stack_level, p_initializer, p_index_addr);
+}
+
GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const {
- if (!p_datatype.has_type) {
+ if (!p_datatype.is_set() || !p_datatype.is_hard_type()) {
return GDScriptDataType();
}
@@ -122,6 +123,9 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
result.has_type = true;
switch (p_datatype.kind) {
+ case GDScriptParser::DataType::VARIANT: {
+ result.has_type = false;
+ } break;
case GDScriptParser::DataType::BUILTIN: {
result.kind = GDScriptDataType::BUILTIN;
result.builtin_type = p_datatype.builtin_type;
@@ -135,36 +139,49 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
result.script_type = p_datatype.script_type;
result.native_type = result.script_type->get_instance_base_type();
} break;
- case GDScriptParser::DataType::GDSCRIPT: {
- result.kind = GDScriptDataType::GDSCRIPT;
- result.script_type = p_datatype.script_type;
- result.native_type = result.script_type->get_instance_base_type();
- } break;
case GDScriptParser::DataType::CLASS: {
// Locate class by constructing the path to it and following that path
GDScriptParser::ClassNode *class_type = p_datatype.class_type;
- List<StringName> names;
- while (class_type->owner) {
- names.push_back(class_type->name);
- class_type = class_type->owner;
- }
+ if (class_type) {
+ if (class_type->fqcn.begins_with(main_script->path) || (!main_script->name.empty() && class_type->fqcn.begins_with(main_script->name))) {
+ // Local class.
+ List<StringName> names;
+ while (class_type->outer) {
+ names.push_back(class_type->identifier->name);
+ class_type = class_type->outer;
+ }
- Ref<GDScript> script = Ref<GDScript>(main_script);
- while (names.back()) {
- if (!script->subclasses.has(names.back()->get())) {
- ERR_PRINT("Parser bug: Cannot locate datatype class.");
- result.has_type = false;
- return GDScriptDataType();
+ Ref<GDScript> script = Ref<GDScript>(main_script);
+ while (names.back()) {
+ if (!script->subclasses.has(names.back()->get())) {
+ ERR_PRINT("Parser bug: Cannot locate datatype class.");
+ result.has_type = false;
+ return GDScriptDataType();
+ }
+ script = script->subclasses[names.back()->get()];
+ names.pop_back();
+ }
+ result.kind = GDScriptDataType::GDSCRIPT;
+ result.script_type = script;
+ result.native_type = script->get_instance_base_type();
+ } else {
+ result.kind = GDScriptDataType::GDSCRIPT;
+ result.script_type = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path);
+ result.native_type = p_datatype.native_type;
}
- script = script->subclasses[names.back()->get()];
- names.pop_back();
}
-
- result.kind = GDScriptDataType::GDSCRIPT;
- result.script_type = script;
- result.native_type = script->get_instance_base_type();
} break;
- default: {
+ case GDScriptParser::DataType::ENUM_VALUE:
+ result.has_type = true;
+ result.kind = GDScriptDataType::BUILTIN;
+ result.builtin_type = Variant::INT;
+ break;
+ case GDScriptParser::DataType::ENUM:
+ result.has_type = true;
+ result.kind = GDScriptDataType::BUILTIN;
+ result.builtin_type = Variant::DICTIONARY;
+ break;
+ case GDScriptParser::DataType::UNRESOLVED: {
ERR_PRINT("Parser bug: converting unresolved type.");
return GDScriptDataType();
}
@@ -173,42 +190,41 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
return result;
}
-int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level, int p_index_addr) {
+int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::AssignmentNode *p_assignment, int p_stack_level, int p_index_addr) {
Variant::Operator var_op = Variant::OP_MAX;
- switch (p_expression->op) {
- case GDScriptParser::OperatorNode::OP_ASSIGN_ADD:
+ switch (p_assignment->operation) {
+ case GDScriptParser::AssignmentNode::OP_ADDITION:
var_op = Variant::OP_ADD;
break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_SUB:
+ case GDScriptParser::AssignmentNode::OP_SUBTRACTION:
var_op = Variant::OP_SUBTRACT;
break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_MUL:
+ case GDScriptParser::AssignmentNode::OP_MULTIPLICATION:
var_op = Variant::OP_MULTIPLY;
break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_DIV:
+ case GDScriptParser::AssignmentNode::OP_DIVISION:
var_op = Variant::OP_DIVIDE;
break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_MOD:
+ case GDScriptParser::AssignmentNode::OP_MODULO:
var_op = Variant::OP_MODULE;
break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT:
+ case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_LEFT:
var_op = Variant::OP_SHIFT_LEFT;
break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
+ case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_RIGHT:
var_op = Variant::OP_SHIFT_RIGHT;
break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_AND:
+ case GDScriptParser::AssignmentNode::OP_BIT_AND:
var_op = Variant::OP_BIT_AND;
break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_OR:
+ case GDScriptParser::AssignmentNode::OP_BIT_OR:
var_op = Variant::OP_BIT_OR;
break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_XOR:
+ case GDScriptParser::AssignmentNode::OP_BIT_XOR:
var_op = Variant::OP_BIT_XOR;
break;
- case GDScriptParser::OperatorNode::OP_INIT_ASSIGN:
- case GDScriptParser::OperatorNode::OP_ASSIGN: {
+ case GDScriptParser::AssignmentNode::OP_NONE: {
//none
} break;
default: {
@@ -216,13 +232,13 @@ int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDS
}
}
- bool initializer = p_expression->op == GDScriptParser::OperatorNode::OP_INIT_ASSIGN;
+ // bool initializer = p_expression->op == GDScriptParser::OperatorNode::OP_INIT_ASSIGN;
if (var_op == Variant::OP_MAX) {
- return _parse_expression(codegen, p_expression->arguments[1], p_stack_level, false, initializer);
+ return _parse_expression(codegen, p_assignment->assigned_value, p_stack_level, false, false);
}
- if (!_create_binary_operator(codegen, p_expression, var_op, p_stack_level, initializer, p_index_addr)) {
+ if (!_create_binary_operator(codegen, p_assignment->assignee, p_assignment->assigned_value, var_op, p_stack_level, false, p_index_addr)) {
return -1;
}
@@ -232,10 +248,74 @@ int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDS
return dst_addr;
}
-int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::Node *p_expression, int p_stack_level, bool p_root, bool p_initializer, int p_index_addr) {
+bool GDScriptCompiler::_generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type) {
+ if (p_datatype.has_type && p_value_type.is_variant()) {
+ // Typed assignment
+ switch (p_datatype.kind) {
+ case GDScriptDataType::BUILTIN: {
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator
+ codegen.opcodes.push_back(p_datatype.builtin_type); // variable type
+ codegen.opcodes.push_back(p_dst_address); // argument 1
+ codegen.opcodes.push_back(p_src_address); // argument 2
+ } break;
+ case GDScriptDataType::NATIVE: {
+ int class_idx;
+ if (GDScriptLanguage::get_singleton()->get_global_map().has(p_datatype.native_type)) {
+ class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_datatype.native_type];
+ class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
+ } else {
+ // _set_error("Invalid native class type '" + String(p_datatype.native_type) + "'.", on->arguments[0]);
+ return false;
+ }
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator
+ codegen.opcodes.push_back(class_idx); // variable type
+ codegen.opcodes.push_back(p_dst_address); // argument 1
+ codegen.opcodes.push_back(p_src_address); // argument 2
+ } break;
+ case GDScriptDataType::SCRIPT:
+ case GDScriptDataType::GDSCRIPT: {
+ Variant script = p_datatype.script_type;
+ int idx = codegen.get_constant_pos(script); //make it a local constant (faster access)
+
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator
+ codegen.opcodes.push_back(idx); // variable type
+ codegen.opcodes.push_back(p_dst_address); // argument 1
+ codegen.opcodes.push_back(p_src_address); // argument 2
+ } break;
+ default: {
+ ERR_PRINT("Compiler bug: unresolved assign.");
+
+ // Shouldn't get here, but fail-safe to a regular assignment
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator
+ codegen.opcodes.push_back(p_dst_address); // argument 1
+ codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter)
+ }
+ }
+ } else {
+ if (p_datatype.kind == GDScriptDataType::BUILTIN && p_value_type.kind == GDScriptParser::DataType::BUILTIN && p_datatype.builtin_type != p_value_type.builtin_type) {
+ // Need conversion.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator
+ codegen.opcodes.push_back(p_datatype.builtin_type); // variable type
+ codegen.opcodes.push_back(p_dst_address); // argument 1
+ codegen.opcodes.push_back(p_src_address); // argument 2
+ } else {
+ // Either untyped assignment or already type-checked by the parser
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator
+ codegen.opcodes.push_back(p_dst_address); // argument 1
+ codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter)
+ }
+ }
+ return true;
+}
+
+int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root, bool p_initializer, int p_index_addr) {
+ if (p_expression->is_constant) {
+ return codegen.get_constant_pos(p_expression->reduced_value);
+ }
+
switch (p_expression->type) {
//should parse variable declaration and adjust stack accordingly...
- case GDScriptParser::Node::TYPE_IDENTIFIER: {
+ case GDScriptParser::Node::IDENTIFIER: {
//return identifier
//wait, identifier could be a local variable or something else... careful here, must reference properly
//as stack may be more interesting to work with
@@ -252,6 +332,11 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
return pos | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS);
}
+ // TRY LOCAL CONSTANTS!
+ if (codegen.local_named_constants.has(identifier)) {
+ return codegen.local_named_constants[identifier] | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS);
+ }
+
// TRY CLASS MEMBER
if (_is_class_member_property(codegen, identifier)) {
//get property
@@ -264,12 +349,26 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
}
//TRY MEMBERS!
- if (!codegen.function_node || !codegen.function_node->_static) {
+ if (!codegen.function_node || !codegen.function_node->is_static) {
// TRY MEMBER VARIABLES!
//static function
if (codegen.script->member_indices.has(identifier)) {
- int idx = codegen.script->member_indices[identifier].index;
- return idx | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); //argument (stack root)
+ if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) {
+ // Perform getter.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_RETURN);
+ codegen.opcodes.push_back(0); // Argument count.
+ codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Base (self).
+ codegen.opcodes.push_back(codegen.get_name_map_pos(codegen.script->member_indices[identifier].getter)); // Method name.
+ // Destination.
+ int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode
+ codegen.alloc_stack(p_stack_level);
+ return dst_addr;
+ } else {
+ // No getter or inside getter: direct member access.
+ int idx = codegen.script->member_indices[identifier].index;
+ return idx | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); //argument (stack root)
+ }
}
}
@@ -315,6 +414,21 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
owner = owner->_owner;
}
+ // TRY SIGNALS AND METHODS (can be made callables)
+ if (codegen.class_node->members_indices.has(identifier)) {
+ const GDScriptParser::ClassNode::Member &member = codegen.class_node->members[codegen.class_node->members_indices[identifier]];
+ if (member.type == GDScriptParser::ClassNode::Member::FUNCTION || member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
+ // Get like it was a property.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET_NAMED); // perform operator
+ codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Self.
+ codegen.opcodes.push_back(codegen.get_name_map_pos(identifier)); // argument 2 (unary only takes one parameter)
+ int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode
+ codegen.alloc_stack(p_stack_level);
+ return dst_addr;
+ }
+ }
+
if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
@@ -324,19 +438,20 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
if (ScriptServer::is_global_class(identifier)) {
const GDScriptParser::ClassNode *class_node = codegen.class_node;
- while (class_node->owner) {
- class_node = class_node->owner;
+ while (class_node->outer) {
+ class_node = class_node->outer;
}
- if (class_node->name == identifier) {
- _set_error("Using own name in class file is not allowed (creates a cyclic reference)", p_expression);
- return -1;
- }
+ RES res;
- RES res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier));
- if (res.is_null()) {
- _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
- return -1;
+ if (class_node->identifier && class_node->identifier->name == identifier) {
+ res = Ref<GDScript>(main_script);
+ } else {
+ res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier));
+ if (res.is_null()) {
+ _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
+ return -1;
+ }
}
Variant key = res;
@@ -371,9 +486,9 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
return -1;
} break;
- case GDScriptParser::Node::TYPE_CONSTANT: {
+ case GDScriptParser::Node::LITERAL: {
//return constant
- const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_expression);
+ const GDScriptParser::LiteralNode *cn = static_cast<const GDScriptParser::LiteralNode *>(p_expression);
int idx;
@@ -388,15 +503,15 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root)
} break;
- case GDScriptParser::Node::TYPE_SELF: {
+ case GDScriptParser::Node::SELF: {
//return constant
- if (codegen.function_node && codegen.function_node->_static) {
+ if (codegen.function_node && codegen.function_node->is_static) {
_set_error("'self' not present in static function!", p_expression);
return -1;
}
return (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS);
} break;
- case GDScriptParser::Node::TYPE_ARRAY: {
+ case GDScriptParser::Node::ARRAY: {
const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression);
Vector<int> values;
@@ -427,23 +542,35 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
return dst_addr;
} break;
- case GDScriptParser::Node::TYPE_DICTIONARY: {
+ case GDScriptParser::Node::DICTIONARY: {
const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression);
- Vector<int> values;
+ Vector<int> elements;
int slevel = p_stack_level;
for (int i = 0; i < dn->elements.size(); i++) {
- int ret = _parse_expression(codegen, dn->elements[i].key, slevel);
- if (ret < 0) {
- return ret;
- }
- if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
- slevel++;
- codegen.alloc_stack(slevel);
+ // Key.
+ int ret = -1;
+ switch (dn->style) {
+ case GDScriptParser::DictionaryNode::PYTHON_DICT:
+ // Python-style: key is any expression.
+ ret = _parse_expression(codegen, dn->elements[i].key, slevel);
+ if (ret < 0) {
+ return ret;
+ }
+ if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
+ slevel++;
+ codegen.alloc_stack(slevel);
+ }
+ break;
+ case GDScriptParser::DictionaryNode::LUA_TABLE:
+ // Lua-style: key is an identifier interpreted as string.
+ String key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name;
+ ret = codegen.get_constant_pos(key);
+ break;
}
- values.push_back(ret);
+ elements.push_back(ret);
ret = _parse_expression(codegen, dn->elements[i].value, slevel);
if (ret < 0) {
@@ -454,13 +581,13 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
codegen.alloc_stack(slevel);
}
- values.push_back(ret);
+ elements.push_back(ret);
}
codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY);
codegen.opcodes.push_back(dn->elements.size());
- for (int i = 0; i < values.size(); i++) {
- codegen.opcodes.push_back(values[i]);
+ for (int i = 0; i < elements.size(); i++) {
+ codegen.opcodes.push_back(elements[i]);
}
int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
@@ -469,11 +596,11 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
return dst_addr;
} break;
- case GDScriptParser::Node::TYPE_CAST: {
+ case GDScriptParser::Node::CAST: {
const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
int slevel = p_stack_level;
- int src_addr = _parse_expression(codegen, cn->source_node, slevel);
+ int src_addr = _parse_expression(codegen, cn->operand, slevel);
if (src_addr < 0) {
return src_addr;
}
@@ -482,7 +609,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
codegen.alloc_stack(slevel);
}
- GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type);
+ GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype());
switch (cast_type.kind) {
case GDScriptDataType::BUILTIN: {
@@ -504,8 +631,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
case GDScriptDataType::SCRIPT:
case GDScriptDataType::GDSCRIPT: {
Variant script = cast_type.script_type;
- int idx = codegen.get_constant_pos(script);
- idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access)
+ int idx = codegen.get_constant_pos(script); //make it a local constant (faster access)
codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator
codegen.opcodes.push_back(idx); // variable type
@@ -523,244 +649,310 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
return dst_addr;
} break;
- case GDScriptParser::Node::TYPE_OPERATOR: {
- //hell breaks loose
+ //hell breaks loose
+
+#define OPERATOR_RETURN \
+ int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); \
+ codegen.opcodes.push_back(dst_addr); \
+ codegen.alloc_stack(p_stack_level); \
+ return dst_addr
+
+ case GDScriptParser::Node::CALL: {
+ const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression);
+ if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != Variant::VARIANT_MAX) {
+ //construct a basic type
+
+ Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name);
+
+ Vector<int> arguments;
+ int slevel = p_stack_level;
+ for (int i = 0; i < call->arguments.size(); i++) {
+ int ret = _parse_expression(codegen, call->arguments[i], slevel);
+ if (ret < 0) {
+ return ret;
+ }
+ if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
+ slevel++;
+ codegen.alloc_stack(slevel);
+ }
+ arguments.push_back(ret);
+ }
- const GDScriptParser::OperatorNode *on = static_cast<const GDScriptParser::OperatorNode *>(p_expression);
- switch (on->op) {
- //call/constructor operator
- case GDScriptParser::OperatorNode::OP_PARENT_CALL: {
- ERR_FAIL_COND_V(on->arguments.size() < 1, -1);
+ //push call bytecode
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT); // basic type constructor
+ codegen.opcodes.push_back(vtype); //instance
+ codegen.opcodes.push_back(arguments.size()); //argument count
+ codegen.alloc_call(arguments.size());
+ for (int i = 0; i < arguments.size(); i++) {
+ codegen.opcodes.push_back(arguments[i]); //arguments
+ }
- const GDScriptParser::IdentifierNode *in = (const GDScriptParser::IdentifierNode *)on->arguments[0];
+ } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != GDScriptFunctions::FUNC_MAX) {
+ //built in function
- Vector<int> arguments;
- int slevel = p_stack_level;
- for (int i = 1; i < on->arguments.size(); i++) {
- int ret = _parse_expression(codegen, on->arguments[i], slevel);
- if (ret < 0) {
- return ret;
- }
- if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
- arguments.push_back(ret);
+ Vector<int> arguments;
+ int slevel = p_stack_level;
+ for (int i = 0; i < call->arguments.size(); i++) {
+ int ret = _parse_expression(codegen, call->arguments[i], slevel);
+ if (ret < 0) {
+ return ret;
}
- //push call bytecode
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_SELF_BASE); // basic type constructor
-
- codegen.opcodes.push_back(codegen.get_name_map_pos(in->name)); //instance
- codegen.opcodes.push_back(arguments.size()); //argument count
- codegen.alloc_call(arguments.size());
- for (int i = 0; i < arguments.size(); i++) {
- codegen.opcodes.push_back(arguments[i]); //arguments
+ if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
+ slevel++;
+ codegen.alloc_stack(slevel);
}
- } break;
- case GDScriptParser::OperatorNode::OP_CALL: {
- if (on->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) {
- //construct a basic type
- ERR_FAIL_COND_V(on->arguments.size() < 1, -1);
-
- const GDScriptParser::TypeNode *tn = (const GDScriptParser::TypeNode *)on->arguments[0];
- int vtype = tn->vtype;
-
- Vector<int> arguments;
- int slevel = p_stack_level;
- for (int i = 1; i < on->arguments.size(); i++) {
- int ret = _parse_expression(codegen, on->arguments[i], slevel);
- if (ret < 0) {
- return ret;
- }
- if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
- arguments.push_back(ret);
- }
+ arguments.push_back(ret);
+ }
- //push call bytecode
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT); // basic type constructor
- codegen.opcodes.push_back(vtype); //instance
- codegen.opcodes.push_back(arguments.size()); //argument count
- codegen.alloc_call(arguments.size());
- for (int i = 0; i < arguments.size(); i++) {
- codegen.opcodes.push_back(arguments[i]); //arguments
- }
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN);
+ codegen.opcodes.push_back(GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name));
+ codegen.opcodes.push_back(arguments.size());
+ codegen.alloc_call(arguments.size());
+ for (int i = 0; i < arguments.size(); i++) {
+ codegen.opcodes.push_back(arguments[i]);
+ }
+
+ } else {
+ //regular function
- } else if (on->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
- //built in function
+ const GDScriptParser::ExpressionNode *callee = call->callee;
- ERR_FAIL_COND_V(on->arguments.size() < 1, -1);
+ Vector<int> arguments;
+ int slevel = p_stack_level;
- Vector<int> arguments;
- int slevel = p_stack_level;
- for (int i = 1; i < on->arguments.size(); i++) {
- int ret = _parse_expression(codegen, on->arguments[i], slevel);
+ // TODO: Use callables when possible if needed.
+ int ret = -1;
+ int super_address = -1;
+ if (call->is_super) {
+ // Super call.
+ if (call->callee == nullptr) {
+ // Implicit super function call.
+ super_address = codegen.get_name_map_pos(codegen.function_node->identifier->name);
+ } else {
+ super_address = codegen.get_name_map_pos(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name);
+ }
+ } else {
+ if (callee->type == GDScriptParser::Node::IDENTIFIER) {
+ // Self function call.
+ if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
+ ret = (GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS);
+ } else {
+ ret = (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS);
+ }
+ arguments.push_back(ret);
+ ret = codegen.get_name_map_pos(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name);
+ arguments.push_back(ret);
+ } else if (callee->type == GDScriptParser::Node::SUBSCRIPT) {
+ const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee);
+
+ if (subscript->is_attribute) {
+ ret = _parse_expression(codegen, subscript->base, slevel);
if (ret < 0) {
return ret;
}
-
if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
slevel++;
codegen.alloc_stack(slevel);
}
-
arguments.push_back(ret);
+ arguments.push_back(codegen.get_name_map_pos(subscript->attribute->name));
+ } else {
+ _set_error("Cannot call something that isn't a function.", call->callee);
+ return -1;
}
-
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN);
- codegen.opcodes.push_back(static_cast<const GDScriptParser::BuiltInFunctionNode *>(on->arguments[0])->function);
- codegen.opcodes.push_back(on->arguments.size() - 1);
- codegen.alloc_call(on->arguments.size() - 1);
- for (int i = 0; i < arguments.size(); i++) {
- codegen.opcodes.push_back(arguments[i]);
- }
-
} else {
- //regular function
- ERR_FAIL_COND_V(on->arguments.size() < 2, -1);
-
- const GDScriptParser::Node *instance = on->arguments[0];
+ _set_error("Cannot call something that isn't a function.", call->callee);
+ return -1;
+ }
+ }
- if (instance->type == GDScriptParser::Node::TYPE_SELF) {
- //room for optimization
- }
+ for (int i = 0; i < call->arguments.size(); i++) {
+ ret = _parse_expression(codegen, call->arguments[i], slevel);
+ if (ret < 0) {
+ return ret;
+ }
+ if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
+ slevel++;
+ codegen.alloc_stack(slevel);
+ }
+ arguments.push_back(ret);
+ }
- Vector<int> arguments;
- int slevel = p_stack_level;
+ int opcode = GDScriptFunction::OPCODE_CALL_RETURN;
+ if (call->is_super) {
+ opcode = GDScriptFunction::OPCODE_CALL_SELF_BASE;
+ } else if (within_await) {
+ opcode = GDScriptFunction::OPCODE_CALL_ASYNC;
+ } else if (p_root) {
+ opcode = GDScriptFunction::OPCODE_CALL;
+ }
- for (int i = 0; i < on->arguments.size(); i++) {
- int ret;
+ codegen.opcodes.push_back(opcode); // perform operator
+ if (call->is_super) {
+ codegen.opcodes.push_back(super_address);
+ }
+ codegen.opcodes.push_back(call->arguments.size());
+ codegen.alloc_call(call->arguments.size());
+ for (int i = 0; i < arguments.size(); i++) {
+ codegen.opcodes.push_back(arguments[i]);
+ }
+ }
+ OPERATOR_RETURN;
+ } break;
+ case GDScriptParser::Node::GET_NODE: {
+ const GDScriptParser::GetNodeNode *get_node = static_cast<const GDScriptParser::GetNodeNode *>(p_expression);
- if (i == 0 && on->arguments[i]->type == GDScriptParser::Node::TYPE_SELF && codegen.function_node && codegen.function_node->_static) {
- //static call to self
- ret = (GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS);
- } else if (i == 1) {
- if (on->arguments[i]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
- _set_error("Attempt to call a non-identifier.", on);
- return -1;
- }
- GDScriptParser::IdentifierNode *id = static_cast<GDScriptParser::IdentifierNode *>(on->arguments[i]);
- ret = codegen.get_name_map_pos(id->name);
+ String node_name;
+ if (get_node->string != nullptr) {
+ node_name += String(get_node->string->value);
+ } else {
+ for (int i = 0; i < get_node->chain.size(); i++) {
+ if (i > 0) {
+ node_name += "/";
+ }
+ node_name += get_node->chain[i]->name;
+ }
+ }
- } else {
- ret = _parse_expression(codegen, on->arguments[i], slevel);
- if (ret < 0) {
- return ret;
- }
- if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
- }
- arguments.push_back(ret);
- }
+ int arg_address = codegen.get_constant_pos(NodePath(node_name));
- codegen.opcodes.push_back(p_root ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); // perform operator
- codegen.opcodes.push_back(on->arguments.size() - 2);
- codegen.alloc_call(on->arguments.size() - 2);
- for (int i = 0; i < arguments.size(); i++) {
- codegen.opcodes.push_back(arguments[i]);
- }
- }
- } break;
- case GDScriptParser::OperatorNode::OP_YIELD: {
- ERR_FAIL_COND_V(on->arguments.size() && on->arguments.size() != 2, -1);
+ codegen.opcodes.push_back(p_root ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN);
+ codegen.opcodes.push_back(1); // number of arguments.
+ codegen.alloc_call(1);
+ codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // self.
+ codegen.opcodes.push_back(codegen.get_name_map_pos("get_node")); // function.
+ codegen.opcodes.push_back(arg_address); // argument (NodePath).
+ OPERATOR_RETURN;
+ } break;
+ case GDScriptParser::Node::PRELOAD: {
+ const GDScriptParser::PreloadNode *preload = static_cast<const GDScriptParser::PreloadNode *>(p_expression);
- Vector<int> arguments;
- int slevel = p_stack_level;
- for (int i = 0; i < on->arguments.size(); i++) {
- int ret = _parse_expression(codegen, on->arguments[i], slevel);
- if (ret < 0) {
- return ret;
- }
- if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
- arguments.push_back(ret);
- }
+ // Add resource as constant.
+ return codegen.get_constant_pos(preload->resource);
+ } break;
+ case GDScriptParser::Node::AWAIT: {
+ const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression);
- //push call bytecode
- codegen.opcodes.push_back(arguments.size() == 0 ? GDScriptFunction::OPCODE_YIELD : GDScriptFunction::OPCODE_YIELD_SIGNAL); // basic type constructor
- for (int i = 0; i < arguments.size(); i++) {
- codegen.opcodes.push_back(arguments[i]); //arguments
- }
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_YIELD_RESUME);
- //next will be where to place the result :)
+ int slevel = p_stack_level;
+ within_await = true;
+ int argument = _parse_expression(codegen, await->to_await, slevel);
+ within_await = false;
+ if (argument < 0) {
+ return argument;
+ }
+ if ((argument >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
+ slevel++;
+ codegen.alloc_stack(slevel);
+ }
+ //push call bytecode
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_AWAIT);
+ codegen.opcodes.push_back(argument);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_AWAIT_RESUME);
+ //next will be where to place the result :)
- } break;
+ OPERATOR_RETURN;
+ } break;
- //indexing operator
- case GDScriptParser::OperatorNode::OP_INDEX:
- case GDScriptParser::OperatorNode::OP_INDEX_NAMED: {
- ERR_FAIL_COND_V(on->arguments.size() != 2, -1);
+ //indexing operator
+ case GDScriptParser::Node::SUBSCRIPT: {
+ int slevel = p_stack_level;
- int slevel = p_stack_level;
- bool named = (on->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED);
+ const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression);
- int from = _parse_expression(codegen, on->arguments[0], slevel);
- if (from < 0) {
- return from;
- }
+ int from = _parse_expression(codegen, subscript->base, slevel);
+ if (from < 0) {
+ return from;
+ }
- int index;
- if (p_index_addr != 0) {
- index = p_index_addr;
- } else if (named) {
- if (on->arguments[0]->type == GDScriptParser::Node::TYPE_SELF && codegen.script && codegen.function_node && !codegen.function_node->_static) {
- GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(on->arguments[1]);
- const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(identifier->name);
+ bool named = subscript->is_attribute;
+ int index;
+ if (p_index_addr != 0) {
+ index = p_index_addr;
+ } else if (subscript->is_attribute) {
+ if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) {
+ GDScriptParser::IdentifierNode *identifier = subscript->attribute;
+ const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(identifier->name);
#ifdef DEBUG_ENABLED
- if (MI && MI->get().getter == codegen.function_node->name) {
- String n = static_cast<GDScriptParser::IdentifierNode *>(on->arguments[1])->name;
- _set_error("Must use '" + n + "' instead of 'self." + n + "' in getter.", on);
- return -1;
- }
+ if (MI && MI->get().getter == codegen.function_name) {
+ String n = identifier->name;
+ _set_error("Must use '" + n + "' instead of 'self." + n + "' in getter.", identifier);
+ return -1;
+ }
#endif
- if (MI && MI->get().getter == "") {
- // Faster than indexing self (as if no self. had been used)
- return (MI->get().index) | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS);
- }
- }
+ if (MI && MI->get().getter == "") {
+ // Faster than indexing self (as if no self. had been used)
+ return (MI->get().index) | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS);
+ }
+ }
- index = codegen.get_name_map_pos(static_cast<GDScriptParser::IdentifierNode *>(on->arguments[1])->name);
+ index = codegen.get_name_map_pos(subscript->attribute->name);
- } else {
- if (on->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT && static_cast<const GDScriptParser::ConstantNode *>(on->arguments[1])->value.get_type() == Variant::STRING) {
- //also, somehow, named (speed up anyway)
- StringName name = static_cast<const GDScriptParser::ConstantNode *>(on->arguments[1])->value;
- index = codegen.get_name_map_pos(name);
- named = true;
-
- } else {
- //regular indexing
- if (from & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
+ } else {
+ if (subscript->index->type == GDScriptParser::Node::LITERAL && static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value.get_type() == Variant::STRING) {
+ //also, somehow, named (speed up anyway)
+ StringName name = static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value;
+ index = codegen.get_name_map_pos(name);
+ named = true;
- index = _parse_expression(codegen, on->arguments[1], slevel);
- if (index < 0) {
- return index;
- }
- }
+ } else {
+ //regular indexing
+ if (from & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
+ slevel++;
+ codegen.alloc_stack(slevel);
}
- codegen.opcodes.push_back(named ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET); // perform operator
- codegen.opcodes.push_back(from); // argument 1
- codegen.opcodes.push_back(index); // argument 2 (unary only takes one parameter)
+ index = _parse_expression(codegen, subscript->index, slevel);
+ if (index < 0) {
+ return index;
+ }
+ }
+ }
+ codegen.opcodes.push_back(named ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET); // perform operator
+ codegen.opcodes.push_back(from); // argument 1
+ codegen.opcodes.push_back(index); // argument 2 (unary only takes one parameter)
+ OPERATOR_RETURN;
+ } break;
+ case GDScriptParser::Node::UNARY_OPERATOR: {
+ //unary operators
+ const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression);
+ switch (unary->operation) {
+ case GDScriptParser::UnaryOpNode::OP_NEGATIVE: {
+ if (!_create_unary_operator(codegen, unary, Variant::OP_NEGATE, p_stack_level)) {
+ return -1;
+ }
+ } break;
+ case GDScriptParser::UnaryOpNode::OP_POSITIVE: {
+ if (!_create_unary_operator(codegen, unary, Variant::OP_POSITIVE, p_stack_level)) {
+ return -1;
+ }
+ } break;
+ case GDScriptParser::UnaryOpNode::OP_LOGIC_NOT: {
+ if (!_create_unary_operator(codegen, unary, Variant::OP_NOT, p_stack_level)) {
+ return -1;
+ }
} break;
- case GDScriptParser::OperatorNode::OP_AND: {
+ case GDScriptParser::UnaryOpNode::OP_COMPLEMENT: {
+ if (!_create_unary_operator(codegen, unary, Variant::OP_BIT_NEGATE, p_stack_level)) {
+ return -1;
+ }
+ } break;
+ }
+ OPERATOR_RETURN;
+ }
+ case GDScriptParser::Node::BINARY_OPERATOR: {
+ //binary operators (in precedence order)
+ const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression);
+
+ switch (binary->operation) {
+ case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: {
// AND operator with early out on failure
- int res = _parse_expression(codegen, on->arguments[0], p_stack_level);
+ int res = _parse_expression(codegen, binary->left_operand, p_stack_level);
if (res < 0) {
return res;
}
@@ -769,7 +961,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
int jump_fail_pos = codegen.opcodes.size();
codegen.opcodes.push_back(0);
- res = _parse_expression(codegen, on->arguments[1], p_stack_level);
+ res = _parse_expression(codegen, binary->right_operand, p_stack_level);
if (res < 0) {
return res;
}
@@ -791,10 +983,10 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
} break;
- case GDScriptParser::OperatorNode::OP_OR: {
+ case GDScriptParser::BinaryOpNode::OP_LOGIC_OR: {
// OR operator with early out on success
- int res = _parse_expression(codegen, on->arguments[0], p_stack_level);
+ int res = _parse_expression(codegen, binary->left_operand, p_stack_level);
if (res < 0) {
return res;
}
@@ -803,7 +995,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
int jump_success_pos = codegen.opcodes.size();
codegen.opcodes.push_back(0);
- res = _parse_expression(codegen, on->arguments[1], p_stack_level);
+ res = _parse_expression(codegen, binary->right_operand, p_stack_level);
if (res < 0) {
return res;
}
@@ -825,736 +1017,1020 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
} break;
- // ternary operators
- case GDScriptParser::OperatorNode::OP_TERNARY_IF: {
- // x IF a ELSE y operator with early out on failure
+ case GDScriptParser::BinaryOpNode::OP_TYPE_TEST: {
+ int slevel = p_stack_level;
- int res = _parse_expression(codegen, on->arguments[0], p_stack_level);
- if (res < 0) {
- return res;
+ int src_address_a = _parse_expression(codegen, binary->left_operand, slevel);
+ if (src_address_a < 0) {
+ return -1;
}
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
- codegen.opcodes.push_back(res);
- int jump_fail_pos = codegen.opcodes.size();
- codegen.opcodes.push_back(0);
- res = _parse_expression(codegen, on->arguments[1], p_stack_level);
- if (res < 0) {
- return res;
+ if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
+ slevel++; //uses stack for return, increase stack
}
- codegen.alloc_stack(p_stack_level); //it will be used..
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN);
- codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- codegen.opcodes.push_back(res);
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- int jump_past_pos = codegen.opcodes.size();
- codegen.opcodes.push_back(0);
-
- codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size();
- res = _parse_expression(codegen, on->arguments[2], p_stack_level);
- if (res < 0) {
- return res;
+ int src_address_b = -1;
+ bool builtin = false;
+ if (binary->right_operand->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name) != Variant::VARIANT_MAX) {
+ // `is` with builtin type
+ builtin = true;
+ src_address_b = (int)GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name);
+ } else {
+ src_address_b = _parse_expression(codegen, binary->right_operand, slevel);
+ if (src_address_b < 0) {
+ return -1;
+ }
}
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN);
- codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- codegen.opcodes.push_back(res);
-
- codegen.opcodes.write[jump_past_pos] = codegen.opcodes.size();
-
- return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ codegen.opcodes.push_back(builtin ? GDScriptFunction::OPCODE_IS_BUILTIN : GDScriptFunction::OPCODE_EXTENDS_TEST); // perform operator
+ codegen.opcodes.push_back(src_address_a); // argument 1
+ codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
} break;
- //unary operators
- case GDScriptParser::OperatorNode::OP_NEG: {
- if (!_create_unary_operator(codegen, on, Variant::OP_NEGATE, p_stack_level)) {
- return -1;
- }
- } break;
- case GDScriptParser::OperatorNode::OP_POS: {
- if (!_create_unary_operator(codegen, on, Variant::OP_POSITIVE, p_stack_level)) {
- return -1;
- }
- } break;
- case GDScriptParser::OperatorNode::OP_NOT: {
- if (!_create_unary_operator(codegen, on, Variant::OP_NOT, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_CONTENT_TEST: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_IN, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_BIT_INVERT: {
- if (!_create_unary_operator(codegen, on, Variant::OP_BIT_NEGATE, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_COMP_EQUAL: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_EQUAL, p_stack_level)) {
return -1;
}
} break;
- //binary operators (in precedence order)
- case GDScriptParser::OperatorNode::OP_IN: {
- if (!_create_binary_operator(codegen, on, Variant::OP_IN, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_COMP_NOT_EQUAL: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_NOT_EQUAL, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_EQUAL: {
- if (!_create_binary_operator(codegen, on, Variant::OP_EQUAL, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_COMP_LESS: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_LESS, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_NOT_EQUAL: {
- if (!_create_binary_operator(codegen, on, Variant::OP_NOT_EQUAL, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_COMP_LESS_EQUAL: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_LESS_EQUAL, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_LESS: {
- if (!_create_binary_operator(codegen, on, Variant::OP_LESS, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_COMP_GREATER: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_GREATER, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_LESS_EQUAL: {
- if (!_create_binary_operator(codegen, on, Variant::OP_LESS_EQUAL, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_COMP_GREATER_EQUAL: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_GREATER_EQUAL, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_GREATER: {
- if (!_create_binary_operator(codegen, on, Variant::OP_GREATER, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_ADDITION: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_ADD, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_GREATER_EQUAL: {
- if (!_create_binary_operator(codegen, on, Variant::OP_GREATER_EQUAL, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_SUBTRACTION: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_SUBTRACT, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_ADD: {
- if (!_create_binary_operator(codegen, on, Variant::OP_ADD, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_MULTIPLICATION: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_MULTIPLY, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_SUB: {
- if (!_create_binary_operator(codegen, on, Variant::OP_SUBTRACT, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_DIVISION: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_DIVIDE, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_MUL: {
- if (!_create_binary_operator(codegen, on, Variant::OP_MULTIPLY, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_MODULO: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_MODULE, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_DIV: {
- if (!_create_binary_operator(codegen, on, Variant::OP_DIVIDE, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_BIT_AND: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_AND, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_MOD: {
- if (!_create_binary_operator(codegen, on, Variant::OP_MODULE, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_BIT_OR: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_OR, p_stack_level)) {
return -1;
}
} break;
- //case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_LEFT,p_stack_level)) return -1;} break;
- //case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_RIGHT,p_stack_level)) return -1;} break;
- case GDScriptParser::OperatorNode::OP_BIT_AND: {
- if (!_create_binary_operator(codegen, on, Variant::OP_BIT_AND, p_stack_level)) {
- return -1;
- }
- } break;
- case GDScriptParser::OperatorNode::OP_BIT_OR: {
- if (!_create_binary_operator(codegen, on, Variant::OP_BIT_OR, p_stack_level)) {
- return -1;
- }
- } break;
- case GDScriptParser::OperatorNode::OP_BIT_XOR: {
- if (!_create_binary_operator(codegen, on, Variant::OP_BIT_XOR, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_BIT_XOR: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_XOR, p_stack_level)) {
return -1;
}
} break;
//shift
- case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: {
- if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_LEFT, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_BIT_LEFT_SHIFT: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_LEFT, p_stack_level)) {
return -1;
}
} break;
- case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: {
- if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_RIGHT, p_stack_level)) {
+ case GDScriptParser::BinaryOpNode::OP_BIT_RIGHT_SHIFT: {
+ if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_RIGHT, p_stack_level)) {
return -1;
}
} break;
- //assignment operators
- case GDScriptParser::OperatorNode::OP_ASSIGN_ADD:
- case GDScriptParser::OperatorNode::OP_ASSIGN_SUB:
- case GDScriptParser::OperatorNode::OP_ASSIGN_MUL:
- case GDScriptParser::OperatorNode::OP_ASSIGN_DIV:
- case GDScriptParser::OperatorNode::OP_ASSIGN_MOD:
- case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT:
- case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_AND:
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_OR:
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_XOR:
- case GDScriptParser::OperatorNode::OP_INIT_ASSIGN:
- case GDScriptParser::OperatorNode::OP_ASSIGN: {
- ERR_FAIL_COND_V(on->arguments.size() != 2, -1);
-
- if (on->arguments[0]->type == GDScriptParser::Node::TYPE_OPERATOR && (static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX || static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED)) {
- // SET (chained) MODE!
-#ifdef DEBUG_ENABLED
- if (static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) {
- const GDScriptParser::OperatorNode *inon = static_cast<GDScriptParser::OperatorNode *>(on->arguments[0]);
-
- if (inon->arguments[0]->type == GDScriptParser::Node::TYPE_SELF && codegen.script && codegen.function_node && !codegen.function_node->_static) {
- const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(static_cast<GDScriptParser::IdentifierNode *>(inon->arguments[1])->name);
- if (MI && MI->get().setter == codegen.function_node->name) {
- String n = static_cast<GDScriptParser::IdentifierNode *>(inon->arguments[1])->name;
- _set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", inon);
- return -1;
- }
- }
- }
-#endif
+ }
+ OPERATOR_RETURN;
+ } break;
+ // ternary operators
+ case GDScriptParser::Node::TERNARY_OPERATOR: {
+ // x IF a ELSE y operator with early out on failure
+
+ const GDScriptParser::TernaryOpNode *ternary = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression);
+ int res = _parse_expression(codegen, ternary->condition, p_stack_level);
+ if (res < 0) {
+ return res;
+ }
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(res);
+ int jump_fail_pos = codegen.opcodes.size();
+ codegen.opcodes.push_back(0);
- int slevel = p_stack_level;
+ res = _parse_expression(codegen, ternary->true_expr, p_stack_level);
+ if (res < 0) {
+ return res;
+ }
- GDScriptParser::OperatorNode *op = static_cast<GDScriptParser::OperatorNode *>(on->arguments[0]);
+ codegen.alloc_stack(p_stack_level); //it will be used..
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN);
+ codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ codegen.opcodes.push_back(res);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ int jump_past_pos = codegen.opcodes.size();
+ codegen.opcodes.push_back(0);
- /* Find chain of sets */
+ codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size();
+ res = _parse_expression(codegen, ternary->false_expr, p_stack_level);
+ if (res < 0) {
+ return res;
+ }
- StringName assign_property;
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN);
+ codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ codegen.opcodes.push_back(res);
- List<GDScriptParser::OperatorNode *> chain;
+ codegen.opcodes.write[jump_past_pos] = codegen.opcodes.size();
- {
- //create get/set chain
- GDScriptParser::OperatorNode *n = op;
- while (true) {
- chain.push_back(n);
- if (n->arguments[0]->type != GDScriptParser::Node::TYPE_OPERATOR) {
- //check for a built-in property
- if (n->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
- GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(n->arguments[0]);
- if (_is_class_member_property(codegen, identifier->name)) {
- assign_property = identifier->name;
- }
- }
- break;
- }
- n = static_cast<GDScriptParser::OperatorNode *>(n->arguments[0]);
- if (n->op != GDScriptParser::OperatorNode::OP_INDEX && n->op != GDScriptParser::OperatorNode::OP_INDEX_NAMED) {
- break;
- }
- }
- }
+ return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
- /* Chain of gets */
+ } break;
+ //assignment operators
+ case GDScriptParser::Node::ASSIGNMENT: {
+ const GDScriptParser::AssignmentNode *assignment = static_cast<const GDScriptParser::AssignmentNode *>(p_expression);
- //get at (potential) root stack pos, so it can be returned
- int prev_pos = _parse_expression(codegen, chain.back()->get()->arguments[0], slevel);
- if (prev_pos < 0) {
- return prev_pos;
+ if (assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) {
+ // SET (chained) MODE!
+ const GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(assignment->assignee);
+#ifdef DEBUG_ENABLED
+ if (subscript->is_attribute) {
+ if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) {
+ const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(subscript->attribute->name);
+ if (MI && MI->get().setter == codegen.function_name) {
+ String n = subscript->attribute->name;
+ _set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", subscript);
+ return -1;
}
- int retval = prev_pos;
+ }
+ }
+#endif
- if (retval & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
+ int slevel = p_stack_level;
- Vector<int> setchain;
+ /* Find chain of sets */
- if (assign_property != StringName()) {
- // recover and assign at the end, this allows stuff like
- // position.x+=2.0
- // in Node2D
- setchain.push_back(prev_pos);
- setchain.push_back(codegen.get_name_map_pos(assign_property));
- setchain.push_back(GDScriptFunction::OPCODE_SET_MEMBER);
- }
+ StringName assign_property;
+
+ List<const GDScriptParser::SubscriptNode *> chain;
+
+ {
+ //create get/set chain
+ const GDScriptParser::SubscriptNode *n = subscript;
+ while (true) {
+ chain.push_back(n);
- for (List<GDScriptParser::OperatorNode *>::Element *E = chain.back(); E; E = E->prev()) {
- if (E == chain.front()) { //ignore first
- break;
+ if (n->base->type != GDScriptParser::Node::SUBSCRIPT) {
+ //check for a built-in property
+ if (n->base->type == GDScriptParser::Node::IDENTIFIER) {
+ GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(n->base);
+ if (_is_class_member_property(codegen, identifier->name)) {
+ assign_property = identifier->name;
+ }
}
+ break;
+ }
+ n = static_cast<const GDScriptParser::SubscriptNode *>(n->base);
+ }
+ }
- bool named = E->get()->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED;
- int key_idx;
+ /* Chain of gets */
- if (named) {
- key_idx = codegen.get_name_map_pos(static_cast<const GDScriptParser::IdentifierNode *>(E->get()->arguments[1])->name);
- //printf("named key %x\n",key_idx);
+ //get at (potential) root stack pos, so it can be returned
+ int prev_pos = _parse_expression(codegen, chain.back()->get()->base, slevel);
+ if (prev_pos < 0) {
+ return prev_pos;
+ }
+ int retval = prev_pos;
- } else {
- if (prev_pos & (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS)) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
+ if (retval & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
+ slevel++;
+ codegen.alloc_stack(slevel);
+ }
- GDScriptParser::Node *key = E->get()->arguments[1];
- key_idx = _parse_expression(codegen, key, slevel);
- //printf("expr key %x\n",key_idx);
+ Vector<int> setchain;
- //stack was raised here if retval was stack but..
- }
+ if (assign_property != StringName()) {
+ // recover and assign at the end, this allows stuff like
+ // position.x+=2.0
+ // in Node2D
+ setchain.push_back(prev_pos);
+ setchain.push_back(codegen.get_name_map_pos(assign_property));
+ setchain.push_back(GDScriptFunction::OPCODE_SET_MEMBER);
+ }
- if (key_idx < 0) { //error
- return key_idx;
- }
+ for (List<const GDScriptParser::SubscriptNode *>::Element *E = chain.back(); E; E = E->prev()) {
+ if (E == chain.front()) { //ignore first
+ break;
+ }
+
+ const GDScriptParser::SubscriptNode *subscript_elem = E->get();
+ int key_idx;
- codegen.opcodes.push_back(named ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET);
- codegen.opcodes.push_back(prev_pos);
- codegen.opcodes.push_back(key_idx);
+ if (subscript_elem->is_attribute) {
+ key_idx = codegen.get_name_map_pos(subscript_elem->attribute->name);
+ //printf("named key %x\n",key_idx);
+
+ } else {
+ if (prev_pos & (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS)) {
slevel++;
codegen.alloc_stack(slevel);
- int dst_pos = (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) | slevel;
-
- codegen.opcodes.push_back(dst_pos);
+ }
- //add in reverse order, since it will be reverted
+ GDScriptParser::ExpressionNode *key = subscript_elem->index;
+ key_idx = _parse_expression(codegen, key, slevel);
+ //printf("expr key %x\n",key_idx);
- setchain.push_back(dst_pos);
- setchain.push_back(key_idx);
- setchain.push_back(prev_pos);
- setchain.push_back(named ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET);
+ //stack was raised here if retval was stack but..
+ }
- prev_pos = dst_pos;
- }
+ if (key_idx < 0) { //error
+ return key_idx;
+ }
- setchain.invert();
+ codegen.opcodes.push_back(subscript_elem->is_attribute ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET);
+ codegen.opcodes.push_back(prev_pos);
+ codegen.opcodes.push_back(key_idx);
+ slevel++;
+ codegen.alloc_stack(slevel);
+ int dst_pos = (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) | slevel;
- int set_index;
- bool named = false;
+ codegen.opcodes.push_back(dst_pos);
- if (op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) {
- set_index = codegen.get_name_map_pos(static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name);
- named = true;
- } else {
- set_index = _parse_expression(codegen, op->arguments[1], slevel + 1);
- named = false;
- }
+ //add in reverse order, since it will be reverted
- if (set_index < 0) { //error
- return set_index;
- }
+ setchain.push_back(dst_pos);
+ setchain.push_back(key_idx);
+ setchain.push_back(prev_pos);
+ setchain.push_back(subscript_elem->is_attribute ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET);
- if (set_index & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
+ prev_pos = dst_pos;
+ }
- int set_value = _parse_assign_right_expression(codegen, on, slevel + 1, named ? 0 : set_index);
- if (set_value < 0) { //error
- return set_value;
- }
+ setchain.invert();
- codegen.opcodes.push_back(named ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET);
- codegen.opcodes.push_back(prev_pos);
- codegen.opcodes.push_back(set_index);
- codegen.opcodes.push_back(set_value);
+ int set_index;
- for (int i = 0; i < setchain.size(); i++) {
- codegen.opcodes.push_back(setchain[i]);
- }
+ if (subscript->is_attribute) {
+ set_index = codegen.get_name_map_pos(subscript->attribute->name);
+ } else {
+ set_index = _parse_expression(codegen, subscript->index, slevel + 1);
+ }
- return retval;
+ if (set_index < 0) { //error
+ return set_index;
+ }
- } else if (on->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && _is_class_member_property(codegen, static_cast<GDScriptParser::IdentifierNode *>(on->arguments[0])->name)) {
- //assignment to member property
+ if (set_index & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
+ slevel++;
+ codegen.alloc_stack(slevel);
+ }
- int slevel = p_stack_level;
+ int set_value = _parse_assign_right_expression(codegen, assignment, slevel + 1, subscript->is_attribute ? 0 : set_index);
+ if (set_value < 0) { //error
+ return set_value;
+ }
- int src_address = _parse_assign_right_expression(codegen, on, slevel);
- if (src_address < 0) {
- return -1;
- }
+ codegen.opcodes.push_back(subscript->is_attribute ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET);
+ codegen.opcodes.push_back(prev_pos);
+ codegen.opcodes.push_back(set_index);
+ codegen.opcodes.push_back(set_value);
- StringName name = static_cast<GDScriptParser::IdentifierNode *>(on->arguments[0])->name;
+ for (int i = 0; i < setchain.size(); i++) {
+ codegen.opcodes.push_back(setchain[i]);
+ }
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_SET_MEMBER);
- codegen.opcodes.push_back(codegen.get_name_map_pos(name));
- codegen.opcodes.push_back(src_address);
+ return retval;
- return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS;
- } else {
- //REGULAR ASSIGNMENT MODE!!
+ } else if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER && _is_class_member_property(codegen, static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name)) {
+ //assignment to member property
- int slevel = p_stack_level;
+ int slevel = p_stack_level;
- int dst_address_a = _parse_expression(codegen, on->arguments[0], slevel, false, on->op == GDScriptParser::OperatorNode::OP_INIT_ASSIGN);
- if (dst_address_a < 0) {
- return -1;
- }
+ int src_address = _parse_assign_right_expression(codegen, assignment, slevel);
+ if (src_address < 0) {
+ return -1;
+ }
- if (dst_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
+ StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
- int src_address_b = _parse_assign_right_expression(codegen, on, slevel);
- if (src_address_b < 0) {
- return -1;
- }
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_SET_MEMBER);
+ codegen.opcodes.push_back(codegen.get_name_map_pos(name));
+ codegen.opcodes.push_back(src_address);
- GDScriptDataType assign_type = _gdtype_from_datatype(on->arguments[0]->get_datatype());
-
- if (assign_type.has_type && !on->datatype.has_type) {
- // Typed assignment
- switch (assign_type.kind) {
- case GDScriptDataType::BUILTIN: {
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator
- codegen.opcodes.push_back(assign_type.builtin_type); // variable type
- codegen.opcodes.push_back(dst_address_a); // argument 1
- codegen.opcodes.push_back(src_address_b); // argument 2
- } break;
- case GDScriptDataType::NATIVE: {
- int class_idx;
- if (GDScriptLanguage::get_singleton()->get_global_map().has(assign_type.native_type)) {
- class_idx = GDScriptLanguage::get_singleton()->get_global_map()[assign_type.native_type];
- class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
- } else {
- _set_error("Invalid native class type '" + String(assign_type.native_type) + "'.", on->arguments[0]);
- return -1;
- }
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator
- codegen.opcodes.push_back(class_idx); // variable type
- codegen.opcodes.push_back(dst_address_a); // argument 1
- codegen.opcodes.push_back(src_address_b); // argument 2
- } break;
- case GDScriptDataType::SCRIPT:
- case GDScriptDataType::GDSCRIPT: {
- Variant script = assign_type.script_type;
- int idx = codegen.get_constant_pos(script);
- idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access)
-
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator
- codegen.opcodes.push_back(idx); // variable type
- codegen.opcodes.push_back(dst_address_a); // argument 1
- codegen.opcodes.push_back(src_address_b); // argument 2
- } break;
- default: {
- ERR_PRINT("Compiler bug: unresolved assign.");
-
- // Shouldn't get here, but fail-safe to a regular assignment
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator
- codegen.opcodes.push_back(dst_address_a); // argument 1
- codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
- }
- }
- } else {
- // Either untyped assignment or already type-checked by the parser
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator
- codegen.opcodes.push_back(dst_address_a); // argument 1
- codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
+ return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS;
+ } else {
+ //REGULAR ASSIGNMENT MODE!!
+
+ int slevel = p_stack_level;
+ int dst_address_a = -1;
+
+ bool has_setter = false;
+ bool is_in_setter = false;
+ StringName setter_function;
+ if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
+ StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
+ if (!codegen.stack_identifiers.has(var_name) && codegen.script->member_indices.has(var_name)) {
+ setter_function = codegen.script->member_indices[var_name].setter;
+ if (setter_function != StringName()) {
+ has_setter = true;
+ is_in_setter = setter_function == codegen.function_name;
+ dst_address_a = codegen.script->member_indices[var_name].index;
}
- return dst_address_a; //if anything, returns wathever was assigned or correct stack position
}
- } break;
- case GDScriptParser::OperatorNode::OP_IS: {
- ERR_FAIL_COND_V(on->arguments.size() != 2, false);
-
- int slevel = p_stack_level;
+ }
- int src_address_a = _parse_expression(codegen, on->arguments[0], slevel);
- if (src_address_a < 0) {
+ if (has_setter) {
+ if (is_in_setter) {
+ // Use direct member access.
+ dst_address_a |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS;
+ } else {
+ // Store stack slot for the temp value.
+ dst_address_a = slevel++;
+ codegen.alloc_stack(slevel);
+ dst_address_a |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ }
+ } else {
+ dst_address_a = _parse_expression(codegen, assignment->assignee, slevel);
+ if (dst_address_a < 0) {
return -1;
}
- if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++; //uses stack for return, increase stack
+ if (dst_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
+ slevel++;
+ codegen.alloc_stack(slevel);
}
+ }
- int src_address_b = _parse_expression(codegen, on->arguments[1], slevel);
- if (src_address_b < 0) {
- return -1;
- }
+ int src_address_b = _parse_assign_right_expression(codegen, assignment, slevel);
+ if (src_address_b < 0) {
+ return -1;
+ }
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_EXTENDS_TEST); // perform operator
- codegen.opcodes.push_back(src_address_a); // argument 1
- codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
+ GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype());
+
+ if (has_setter && !is_in_setter) {
+ // Call setter.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL);
+ codegen.opcodes.push_back(1); // Argument count.
+ codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Base (self).
+ codegen.opcodes.push_back(codegen.get_name_map_pos(setter_function)); // Method name.
+ codegen.opcodes.push_back(dst_address_a); // Argument.
+ codegen.opcodes.push_back(dst_address_a); // Result address (won't be used here).
+ codegen.alloc_call(1);
+ } else if (!_generate_typed_assign(codegen, src_address_b, dst_address_a, assign_type, assignment->assigned_value->get_datatype())) {
+ return -1;
+ }
- } break;
- case GDScriptParser::OperatorNode::OP_IS_BUILTIN: {
- ERR_FAIL_COND_V(on->arguments.size() != 2, false);
- ERR_FAIL_COND_V(on->arguments[1]->type != GDScriptParser::Node::TYPE_TYPE, false);
+ return dst_address_a; //if anything, returns wathever was assigned or correct stack position
+ }
+ } break;
+#undef OPERATOR_RETURN
+ //TYPE_TYPE,
+ default: {
+ ERR_FAIL_V_MSG(-1, "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); //unreachable code
+ } break;
+ }
+}
- int slevel = p_stack_level;
+Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address) {
+ // TODO: Many "repeated" code here that could be abstracted. This compiler is going away when new VM arrives though, so...
+ switch (p_pattern->pattern_type) {
+ case GDScriptParser::PatternNode::PT_LITERAL: {
+ // Get literal type into constant map.
+ int literal_type_addr = -1;
+ if (!codegen.constant_map.has((int)p_pattern->literal->value.get_type())) {
+ literal_type_addr = codegen.constant_map.size();
+ codegen.constant_map[(int)p_pattern->literal->value.get_type()] = literal_type_addr;
- int src_address_a = _parse_expression(codegen, on->arguments[0], slevel);
- if (src_address_a < 0) {
- return -1;
- }
+ } else {
+ literal_type_addr = codegen.constant_map[(int)p_pattern->literal->value.get_type()];
+ }
+ literal_type_addr |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS;
- if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++; //uses stack for return, increase stack
- }
+ // Check type equality.
+ int equality_addr = p_stack_level++;
+ equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ codegen.alloc_stack(p_stack_level);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR);
+ codegen.opcodes.push_back(Variant::OP_EQUAL);
+ codegen.opcodes.push_back(p_type_addr);
+ codegen.opcodes.push_back(literal_type_addr);
+ codegen.opcodes.push_back(equality_addr); // Address to result.
+
+ // Jump away if not the same type.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(equality_addr);
+ r_patch_addresses.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+
+ // Get literal.
+ int literal_addr = _parse_expression(codegen, p_pattern->literal, p_stack_level);
+
+ // Check value equality.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR);
+ codegen.opcodes.push_back(Variant::OP_EQUAL);
+ codegen.opcodes.push_back(p_value_addr);
+ codegen.opcodes.push_back(literal_addr);
+ codegen.opcodes.push_back(equality_addr); // Address to result.
+
+ // Jump away if doesn't match.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(equality_addr);
+ r_patch_addresses.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+
+ // Jump to the actual block since it matches. This is needed to take multi-pattern into account.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ r_block_patch_address.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+ } break;
+ case GDScriptParser::PatternNode::PT_EXPRESSION: {
+ // Evaluate expression.
+ int expr_addr = _parse_expression(codegen, p_pattern->expression, p_stack_level);
+ if ((expr_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
+ p_stack_level++;
+ codegen.alloc_stack(p_stack_level);
+ }
- const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(on->arguments[1]);
+ // Evaluate expression type.
+ int expr_type_addr = p_stack_level++;
+ expr_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ codegen.alloc_stack(p_stack_level);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN);
+ codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF);
+ codegen.opcodes.push_back(1); // One argument.
+ codegen.opcodes.push_back(expr_addr); // Argument is the value we want to test.
+ codegen.opcodes.push_back(expr_type_addr); // Address to result.
+
+ // Check type equality.
+ int equality_addr = p_stack_level++;
+ equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ codegen.alloc_stack(p_stack_level);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR);
+ codegen.opcodes.push_back(Variant::OP_EQUAL);
+ codegen.opcodes.push_back(p_type_addr);
+ codegen.opcodes.push_back(expr_type_addr);
+ codegen.opcodes.push_back(equality_addr); // Address to result.
+
+ // Jump away if not the same type.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(equality_addr);
+ r_patch_addresses.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+
+ // Check value equality.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR);
+ codegen.opcodes.push_back(Variant::OP_EQUAL);
+ codegen.opcodes.push_back(p_value_addr);
+ codegen.opcodes.push_back(expr_addr);
+ codegen.opcodes.push_back(equality_addr); // Address to result.
+
+ // Jump away if doesn't match.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(equality_addr);
+ r_patch_addresses.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+
+ // Jump to the actual block since it matches. This is needed to take multi-pattern into account.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ r_block_patch_address.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+ } break;
+ case GDScriptParser::PatternNode::PT_BIND: {
+ // Create new stack variable.
+ int bind_addr = p_stack_level | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS);
+ codegen.add_stack_identifier(p_pattern->bind->name, p_stack_level++);
+ codegen.alloc_stack(p_stack_level);
+ r_bound_variables++;
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_IS_BUILTIN); // perform operator
- codegen.opcodes.push_back(src_address_a); // argument 1
- codegen.opcodes.push_back((int)tn->vtype); // argument 2 (unary only takes one parameter)
- } break;
- default: {
- ERR_FAIL_V_MSG(0, "Bug in bytecode compiler, unexpected operator #" + itos(on->op) + " in parse tree while parsing expression."); //unreachable code
+ // Assign value to bound variable.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN);
+ codegen.opcodes.push_back(bind_addr); // Destination.
+ codegen.opcodes.push_back(p_value_addr); // Source.
+ // Not need to block jump because bind happens only once.
+ } break;
+ case GDScriptParser::PatternNode::PT_ARRAY: {
+ int slevel = p_stack_level;
- } break;
+ // Get array type into constant map.
+ int array_type_addr = codegen.get_constant_pos(Variant::ARRAY);
+
+ // Check type equality.
+ int equality_addr = slevel++;
+ equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ codegen.alloc_stack(slevel);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR);
+ codegen.opcodes.push_back(Variant::OP_EQUAL);
+ codegen.opcodes.push_back(p_type_addr);
+ codegen.opcodes.push_back(array_type_addr);
+ codegen.opcodes.push_back(equality_addr); // Address to result.
+
+ // Jump away if not the same type.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(equality_addr);
+ r_patch_addresses.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+
+ // Store pattern length in constant map.
+ int array_length_addr = codegen.get_constant_pos(p_pattern->rest_used ? p_pattern->array.size() - 1 : p_pattern->array.size());
+
+ // Get value length.
+ int value_length_addr = slevel++;
+ codegen.alloc_stack(slevel);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN);
+ codegen.opcodes.push_back(GDScriptFunctions::LEN);
+ codegen.opcodes.push_back(1); // One argument.
+ codegen.opcodes.push_back(p_value_addr); // Argument is the value we want to test.
+ codegen.opcodes.push_back(value_length_addr); // Address to result.
+
+ // Test length compatibility.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR);
+ codegen.opcodes.push_back(p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL);
+ codegen.opcodes.push_back(value_length_addr);
+ codegen.opcodes.push_back(array_length_addr);
+ codegen.opcodes.push_back(equality_addr); // Address to result.
+
+ // Jump away if length is not compatible.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(equality_addr);
+ r_patch_addresses.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+
+ // Evaluate element by element.
+ for (int i = 0; i < p_pattern->array.size(); i++) {
+ if (p_pattern->array[i]->pattern_type == GDScriptParser::PatternNode::PT_REST) {
+ // Don't want to access an extra element of the user array.
+ break;
+ }
+
+ int stlevel = p_stack_level;
+ Vector<int> element_block_patches; // I want to internal patterns try the next element instead of going to the block.
+ // Add index to constant map.
+ int index_addr = codegen.get_constant_pos(i);
+
+ // Get the actual element from the user-sent array.
+ int element_addr = stlevel++;
+ codegen.alloc_stack(stlevel);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET);
+ codegen.opcodes.push_back(p_value_addr); // Source.
+ codegen.opcodes.push_back(index_addr); // Index.
+ codegen.opcodes.push_back(element_addr); // Destination.
+
+ // Also get type of element.
+ int element_type_addr = stlevel++;
+ element_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ codegen.alloc_stack(stlevel);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN);
+ codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF);
+ codegen.opcodes.push_back(1); // One argument.
+ codegen.opcodes.push_back(element_addr); // Argument is the value we want to test.
+ codegen.opcodes.push_back(element_type_addr); // Address to result.
+
+ // Try the pattern inside the element.
+ Error err = _parse_match_pattern(codegen, p_pattern->array[i], stlevel, element_addr, element_type_addr, r_bound_variables, r_patch_addresses, element_block_patches);
+ if (err != OK) {
+ return err;
+ }
+
+ // Patch jumps to block to try the next element.
+ for (int j = 0; j < element_block_patches.size(); j++) {
+ codegen.opcodes.write[element_block_patches[j]] = codegen.opcodes.size();
+ }
}
- int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode
- codegen.alloc_stack(p_stack_level);
- return dst_addr;
+ // Jump to the actual block since it matches. This is needed to take multi-pattern into account.
+ // Also here for the case of empty arrays.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ r_block_patch_address.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
} break;
- //TYPE_TYPE,
- default: {
- ERR_FAIL_V_MSG(-1, "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); //unreachable code
+ case GDScriptParser::PatternNode::PT_DICTIONARY: {
+ int slevel = p_stack_level;
+
+ // Get dictionary type into constant map.
+ int dict_type_addr = codegen.get_constant_pos(Variant::DICTIONARY);
+
+ // Check type equality.
+ int equality_addr = slevel++;
+ equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ codegen.alloc_stack(slevel);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR);
+ codegen.opcodes.push_back(Variant::OP_EQUAL);
+ codegen.opcodes.push_back(p_type_addr);
+ codegen.opcodes.push_back(dict_type_addr);
+ codegen.opcodes.push_back(equality_addr); // Address to result.
+
+ // Jump away if not the same type.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(equality_addr);
+ r_patch_addresses.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+
+ // Store pattern length in constant map.
+ int dict_length_addr = codegen.get_constant_pos(p_pattern->rest_used ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size());
+
+ // Get user's dictionary length.
+ int value_length_addr = slevel++;
+ codegen.alloc_stack(slevel);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN);
+ codegen.opcodes.push_back(GDScriptFunctions::LEN);
+ codegen.opcodes.push_back(1); // One argument.
+ codegen.opcodes.push_back(p_value_addr); // Argument is the value we want to test.
+ codegen.opcodes.push_back(value_length_addr); // Address to result.
+
+ // Test length compatibility.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR);
+ codegen.opcodes.push_back(p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL);
+ codegen.opcodes.push_back(value_length_addr);
+ codegen.opcodes.push_back(dict_length_addr);
+ codegen.opcodes.push_back(equality_addr); // Address to result.
+
+ // Jump away if length is not compatible.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(equality_addr);
+ r_patch_addresses.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+
+ // Evaluate element by element.
+ for (int i = 0; i < p_pattern->dictionary.size(); i++) {
+ const GDScriptParser::PatternNode::Pair &element = p_pattern->dictionary[i];
+ if (element.value_pattern && element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) {
+ // Ignore rest pattern.
+ continue;
+ }
+ int stlevel = p_stack_level;
+ Vector<int> element_block_patches; // I want to internal patterns try the next element instead of going to the block.
+
+ // Get the pattern key.
+ int pattern_key_addr = _parse_expression(codegen, element.key, stlevel);
+ if (pattern_key_addr < 0) {
+ return ERR_PARSE_ERROR;
+ }
+ if ((pattern_key_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
+ stlevel++;
+ codegen.alloc_stack(stlevel);
+ }
+
+ // Create stack slot for test result.
+ int test_result = stlevel++;
+ test_result |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ codegen.alloc_stack(stlevel);
+
+ // Check if pattern key exists in user's dictionary.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_RETURN);
+ codegen.opcodes.push_back(1); // Argument count.
+ codegen.opcodes.push_back(p_value_addr); // Base (user dictionary).
+ codegen.opcodes.push_back(codegen.get_name_map_pos("has")); // Function name.
+ codegen.opcodes.push_back(pattern_key_addr); // Argument (pattern key).
+ codegen.opcodes.push_back(test_result); // Return address.
+
+ // Jump away if key doesn't exist.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(test_result);
+ r_patch_addresses.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+
+ if (element.value_pattern != nullptr) {
+ // Get actual value from user dictionary.
+ int value_addr = stlevel++;
+ codegen.alloc_stack(stlevel);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET);
+ codegen.opcodes.push_back(p_value_addr); // Source.
+ codegen.opcodes.push_back(pattern_key_addr); // Index.
+ codegen.opcodes.push_back(value_addr); // Destination.
+
+ // Also get type of value.
+ int value_type_addr = stlevel++;
+ value_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ codegen.alloc_stack(stlevel);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN);
+ codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF);
+ codegen.opcodes.push_back(1); // One argument.
+ codegen.opcodes.push_back(value_addr); // Argument is the value we want to test.
+ codegen.opcodes.push_back(value_type_addr); // Address to result.
+
+ // Try the pattern inside the value.
+ Error err = _parse_match_pattern(codegen, element.value_pattern, stlevel, value_addr, value_type_addr, r_bound_variables, r_patch_addresses, element_block_patches);
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ // Patch jumps to block to try the next element.
+ for (int j = 0; j < element_block_patches.size(); j++) {
+ codegen.opcodes.write[element_block_patches[j]] = codegen.opcodes.size();
+ }
+ }
+
+ // Jump to the actual block since it matches. This is needed to take multi-pattern into account.
+ // Also here for the case of empty dictionaries.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ r_block_patch_address.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
+
} break;
+ case GDScriptParser::PatternNode::PT_REST:
+ // Do nothing.
+ break;
+ case GDScriptParser::PatternNode::PT_WILDCARD:
+ // This matches anything so just do the jump.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ r_block_patch_address.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be replaced.
}
+ return OK;
}
-Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::BlockNode *p_block, int p_stack_level, int p_break_addr, int p_continue_addr) {
+Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level, int p_break_addr, int p_continue_addr) {
codegen.push_stack_identifiers();
int new_identifiers = 0;
- codegen.current_line = p_block->line;
+ codegen.current_line = p_block->start_line;
for (int i = 0; i < p_block->statements.size(); i++) {
const GDScriptParser::Node *s = p_block->statements[i];
- switch (s->type) {
- case GDScriptParser::Node::TYPE_NEWLINE: {
#ifdef DEBUG_ENABLED
- const GDScriptParser::NewLineNode *nl = static_cast<const GDScriptParser::NewLineNode *>(s);
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE);
- codegen.opcodes.push_back(nl->line);
- codegen.current_line = nl->line;
+ // Add a newline before each statement, since the debugger needs those.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE);
+ codegen.opcodes.push_back(s->start_line);
+ codegen.current_line = s->start_line;
#endif
- } break;
- case GDScriptParser::Node::TYPE_CONTROL_FLOW: {
- // try subblocks
- const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(s);
+ switch (s->type) {
+ case GDScriptParser::Node::MATCH: {
+ const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s);
- switch (cf->cf_type) {
- case GDScriptParser::ControlFlowNode::CF_MATCH: {
- GDScriptParser::MatchNode *match = cf->match;
+ int slevel = p_stack_level;
- GDScriptParser::IdentifierNode *id = memnew(GDScriptParser::IdentifierNode);
- id->name = "#match_value";
+ // First, let's save the addres of the value match.
+ int temp_addr = _parse_expression(codegen, match->test, slevel);
+ if (temp_addr < 0) {
+ return ERR_PARSE_ERROR;
+ }
+ if ((temp_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
+ slevel++;
+ codegen.alloc_stack(slevel);
+ }
- // var #match_value
- // copied because there is no _parse_statement :(
- codegen.add_stack_identifier(id->name, p_stack_level++);
- codegen.alloc_stack(p_stack_level);
- new_identifiers++;
+ // Then, let's save the type of the value in the stack too, so we can reuse for later comparisons.
+ int type_addr = slevel++;
+ type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ codegen.alloc_stack(slevel);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN);
+ codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF);
+ codegen.opcodes.push_back(1); // One argument.
+ codegen.opcodes.push_back(temp_addr); // Argument is the value we want to test.
+ codegen.opcodes.push_back(type_addr); // Address to result.
- GDScriptParser::OperatorNode *op = memnew(GDScriptParser::OperatorNode);
- op->op = GDScriptParser::OperatorNode::OP_ASSIGN;
- op->arguments.push_back(id);
- op->arguments.push_back(match->val_to_match);
+ Vector<int> patch_match_end; // Will patch the jump to the end of match.
- int ret = _parse_expression(codegen, op, p_stack_level);
- if (ret < 0) {
- memdelete(id);
- memdelete(op);
- return ERR_PARSE_ERROR;
- }
+ // Now we can actually start testing.
+ // For each branch.
+ for (int j = 0; j < match->branches.size(); j++) {
+ const GDScriptParser::MatchBranchNode *branch = match->branches[j];
- // break address
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(codegen.opcodes.size() + 3);
- int break_addr = codegen.opcodes.size();
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(0); // break addr
-
- for (int j = 0; j < match->compiled_pattern_branches.size(); j++) {
- GDScriptParser::MatchNode::CompiledPatternBranch branch = match->compiled_pattern_branches[j];
-
- // jump over continue
- // jump unconditionally
- // continue address
- // compile the condition
- int ret2 = _parse_expression(codegen, branch.compiled_pattern, p_stack_level);
- if (ret2 < 0) {
- memdelete(id);
- memdelete(op);
- return ERR_PARSE_ERROR;
- }
+ int bound_variables = 0;
+ codegen.push_stack_identifiers(); // Create an extra block around for binds.
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF);
- codegen.opcodes.push_back(ret2);
- codegen.opcodes.push_back(codegen.opcodes.size() + 3);
- int continue_addr = codegen.opcodes.size();
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(0);
-
- Error err = _parse_block(codegen, branch.body, p_stack_level, p_break_addr, continue_addr);
- if (err) {
- memdelete(id);
- memdelete(op);
- return ERR_PARSE_ERROR;
+#ifdef DEBUG_ENABLED
+ // Add a newline before each branch, since the debugger needs those.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE);
+ codegen.opcodes.push_back(s->start_line);
+ codegen.current_line = s->start_line;
+#endif
+ Vector<int> patch_addrs; // Will patch with end of pattern to jump.
+ Vector<int> block_patch_addrs; // Will patch with start of block to jump.
+
+ // For each pattern in branch.
+ for (int k = 0; k < branch->patterns.size(); k++) {
+ if (k > 0) {
+ // Patch jumps per pattern to allow for multipattern. If a pattern fails it just tries the next.
+ for (int l = 0; l < patch_addrs.size(); l++) {
+ codegen.opcodes.write[patch_addrs[l]] = codegen.opcodes.size();
}
-
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(break_addr);
-
- codegen.opcodes.write[continue_addr + 1] = codegen.opcodes.size();
+ patch_addrs.clear();
+ }
+ Error err = _parse_match_pattern(codegen, branch->patterns[k], slevel, temp_addr, type_addr, bound_variables, patch_addrs, block_patch_addrs);
+ if (err != OK) {
+ return err;
}
+ }
+ // Patch jumps to the block.
+ for (int k = 0; k < block_patch_addrs.size(); k++) {
+ codegen.opcodes.write[block_patch_addrs[k]] = codegen.opcodes.size();
+ }
- codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size();
+ // Leave space for bound variables.
+ slevel += bound_variables;
+ codegen.alloc_stack(slevel);
- memdelete(id);
- memdelete(op);
+ // Parse the branch block.
+ _parse_block(codegen, branch->block, slevel, p_break_addr, p_continue_addr);
- } break;
+ // Jump to end of match.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ patch_match_end.push_back(codegen.opcodes.size());
+ codegen.opcodes.push_back(0); // Will be patched.
- case GDScriptParser::ControlFlowNode::CF_IF: {
- int ret2 = _parse_expression(codegen, cf->arguments[0], p_stack_level, false);
- if (ret2 < 0) {
- return ERR_PARSE_ERROR;
- }
+ // Patch the addresses of last pattern to jump to the end of the branch, into the next one.
+ for (int k = 0; k < patch_addrs.size(); k++) {
+ codegen.opcodes.write[patch_addrs[k]] = codegen.opcodes.size();
+ }
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
- codegen.opcodes.push_back(ret2);
- int else_addr = codegen.opcodes.size();
- codegen.opcodes.push_back(0); //temporary
+ codegen.pop_stack_identifiers(); // Get out of extra block.
+ }
+ // Patch the addresses to jump to the end of the match statement.
+ for (int j = 0; j < patch_match_end.size(); j++) {
+ codegen.opcodes.write[patch_match_end[j]] = codegen.opcodes.size();
+ }
+ } break;
- Error err = _parse_block(codegen, cf->body, p_stack_level, p_break_addr, p_continue_addr);
- if (err) {
- return err;
- }
+ case GDScriptParser::Node::IF: {
+ const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s);
+ int ret2 = _parse_expression(codegen, if_n->condition, p_stack_level, false);
+ if (ret2 < 0) {
+ return ERR_PARSE_ERROR;
+ }
- if (cf->body_else) {
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- int end_addr = codegen.opcodes.size();
- codegen.opcodes.push_back(0);
- codegen.opcodes.write[else_addr] = codegen.opcodes.size();
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(ret2);
+ int else_addr = codegen.opcodes.size();
+ codegen.opcodes.push_back(0); //temporary
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE);
- codegen.opcodes.push_back(cf->body_else->line);
- codegen.current_line = cf->body_else->line;
+ Error err = _parse_block(codegen, if_n->true_block, p_stack_level, p_break_addr, p_continue_addr);
+ if (err) {
+ return err;
+ }
- Error err2 = _parse_block(codegen, cf->body_else, p_stack_level, p_break_addr, p_continue_addr);
- if (err2) {
- return err2;
- }
+ if (if_n->false_block) {
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ int end_addr = codegen.opcodes.size();
+ codegen.opcodes.push_back(0);
+ codegen.opcodes.write[else_addr] = codegen.opcodes.size();
- codegen.opcodes.write[end_addr] = codegen.opcodes.size();
- } else {
- //end without else
- codegen.opcodes.write[else_addr] = codegen.opcodes.size();
- }
+ Error err2 = _parse_block(codegen, if_n->false_block, p_stack_level, p_break_addr, p_continue_addr);
+ if (err2) {
+ return err2;
+ }
- } break;
- case GDScriptParser::ControlFlowNode::CF_FOR: {
- int slevel = p_stack_level;
- int iter_stack_pos = slevel;
- int iterator_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- int counter_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- int container_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- codegen.alloc_stack(slevel);
+ codegen.opcodes.write[end_addr] = codegen.opcodes.size();
+ } else {
+ //end without else
+ codegen.opcodes.write[else_addr] = codegen.opcodes.size();
+ }
- codegen.push_stack_identifiers();
- codegen.add_stack_identifier(static_cast<const GDScriptParser::IdentifierNode *>(cf->arguments[0])->name, iter_stack_pos);
+ } break;
+ case GDScriptParser::Node::FOR: {
+ const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s);
+ int slevel = p_stack_level;
+ int iter_stack_pos = slevel;
+ int iterator_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ int counter_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ int container_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ codegen.alloc_stack(slevel);
- int ret2 = _parse_expression(codegen, cf->arguments[1], slevel, false);
- if (ret2 < 0) {
- return ERR_COMPILATION_FAILED;
- }
+ codegen.push_stack_identifiers();
+ codegen.add_stack_identifier(for_n->variable->name, iter_stack_pos);
- //assign container
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN);
- codegen.opcodes.push_back(container_pos);
- codegen.opcodes.push_back(ret2);
-
- //begin loop
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE_BEGIN);
- codegen.opcodes.push_back(counter_pos);
- codegen.opcodes.push_back(container_pos);
- codegen.opcodes.push_back(codegen.opcodes.size() + 4);
- codegen.opcodes.push_back(iterator_pos);
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next
- codegen.opcodes.push_back(codegen.opcodes.size() + 8);
- //break loop
- int break_pos = codegen.opcodes.size();
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next
- codegen.opcodes.push_back(0); //skip code for next
- //next loop
- int continue_pos = codegen.opcodes.size();
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE);
- codegen.opcodes.push_back(counter_pos);
- codegen.opcodes.push_back(container_pos);
- codegen.opcodes.push_back(break_pos);
- codegen.opcodes.push_back(iterator_pos);
-
- Error err = _parse_block(codegen, cf->body, slevel, break_pos, continue_pos);
- if (err) {
- return err;
- }
+ int ret2 = _parse_expression(codegen, for_n->list, slevel, false);
+ if (ret2 < 0) {
+ return ERR_COMPILATION_FAILED;
+ }
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(continue_pos);
- codegen.opcodes.write[break_pos + 1] = codegen.opcodes.size();
+ //assign container
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN);
+ codegen.opcodes.push_back(container_pos);
+ codegen.opcodes.push_back(ret2);
- codegen.pop_stack_identifiers();
+ //begin loop
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE_BEGIN);
+ codegen.opcodes.push_back(counter_pos);
+ codegen.opcodes.push_back(container_pos);
+ codegen.opcodes.push_back(codegen.opcodes.size() + 4);
+ codegen.opcodes.push_back(iterator_pos);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next
+ codegen.opcodes.push_back(codegen.opcodes.size() + 8);
+ //break loop
+ int break_pos = codegen.opcodes.size();
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next
+ codegen.opcodes.push_back(0); //skip code for next
+ //next loop
+ int continue_pos = codegen.opcodes.size();
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE);
+ codegen.opcodes.push_back(counter_pos);
+ codegen.opcodes.push_back(container_pos);
+ codegen.opcodes.push_back(break_pos);
+ codegen.opcodes.push_back(iterator_pos);
+
+ Error err = _parse_block(codegen, for_n->loop, slevel, break_pos, continue_pos);
+ if (err) {
+ return err;
+ }
- } break;
- case GDScriptParser::ControlFlowNode::CF_WHILE: {
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(codegen.opcodes.size() + 3);
- int break_addr = codegen.opcodes.size();
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(0);
- int continue_addr = codegen.opcodes.size();
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ codegen.opcodes.push_back(continue_pos);
+ codegen.opcodes.write[break_pos + 1] = codegen.opcodes.size();
- int ret2 = _parse_expression(codegen, cf->arguments[0], p_stack_level, false);
- if (ret2 < 0) {
- return ERR_PARSE_ERROR;
- }
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
- codegen.opcodes.push_back(ret2);
- codegen.opcodes.push_back(break_addr);
- Error err = _parse_block(codegen, cf->body, p_stack_level, break_addr, continue_addr);
- if (err) {
- return err;
- }
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(continue_addr);
+ codegen.pop_stack_identifiers();
- codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size();
+ } break;
+ case GDScriptParser::Node::WHILE: {
+ const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s);
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ codegen.opcodes.push_back(codegen.opcodes.size() + 3);
+ int break_addr = codegen.opcodes.size();
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ codegen.opcodes.push_back(0);
+ int continue_addr = codegen.opcodes.size();
+
+ int ret2 = _parse_expression(codegen, while_n->condition, p_stack_level, false);
+ if (ret2 < 0) {
+ return ERR_PARSE_ERROR;
+ }
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
+ codegen.opcodes.push_back(ret2);
+ codegen.opcodes.push_back(break_addr);
+ Error err = _parse_block(codegen, while_n->loop, p_stack_level, break_addr, continue_addr);
+ if (err) {
+ return err;
+ }
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ codegen.opcodes.push_back(continue_addr);
- } break;
- case GDScriptParser::ControlFlowNode::CF_BREAK: {
- if (p_break_addr < 0) {
- _set_error("'break'' not within loop", cf);
- return ERR_COMPILATION_FAILED;
- }
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(p_break_addr);
-
- } break;
- case GDScriptParser::ControlFlowNode::CF_CONTINUE: {
- if (p_continue_addr < 0) {
- _set_error("'continue' not within loop", cf);
- return ERR_COMPILATION_FAILED;
- }
+ codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size();
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(p_continue_addr);
+ } break;
+ case GDScriptParser::Node::BREAK: {
+ if (p_break_addr < 0) {
+ _set_error("'break'' not within loop", s);
+ return ERR_COMPILATION_FAILED;
+ }
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ codegen.opcodes.push_back(p_break_addr);
- } break;
- case GDScriptParser::ControlFlowNode::CF_RETURN: {
- int ret2;
+ } break;
+ case GDScriptParser::Node::CONTINUE: {
+ if (p_continue_addr < 0) {
+ _set_error("'continue' not within loop", s);
+ return ERR_COMPILATION_FAILED;
+ }
- if (cf->arguments.size()) {
- ret2 = _parse_expression(codegen, cf->arguments[0], p_stack_level, false);
- if (ret2 < 0) {
- return ERR_PARSE_ERROR;
- }
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
+ codegen.opcodes.push_back(p_continue_addr);
- } else {
- ret2 = GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS;
- }
+ } break;
+ case GDScriptParser::Node::RETURN: {
+ const GDScriptParser::ReturnNode *return_n = static_cast<const GDScriptParser::ReturnNode *>(s);
+ int ret2;
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_RETURN);
- codegen.opcodes.push_back(ret2);
+ if (return_n->return_value != nullptr) {
+ ret2 = _parse_expression(codegen, return_n->return_value, p_stack_level, false);
+ if (ret2 < 0) {
+ return ERR_PARSE_ERROR;
+ }
- } break;
+ } else {
+ ret2 = GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS;
}
+
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_RETURN);
+ codegen.opcodes.push_back(ret2);
+
} break;
- case GDScriptParser::Node::TYPE_ASSERT: {
+ case GDScriptParser::Node::ASSERT: {
#ifdef DEBUG_ENABLED
// try subblocks
@@ -1578,14 +2054,14 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo
codegen.opcodes.push_back(message_ret);
#endif
} break;
- case GDScriptParser::Node::TYPE_BREAKPOINT: {
+ case GDScriptParser::Node::BREAKPOINT: {
#ifdef DEBUG_ENABLED
// try subblocks
codegen.opcodes.push_back(GDScriptFunction::OPCODE_BREAKPOINT);
#endif
} break;
- case GDScriptParser::Node::TYPE_LOCAL_VAR: {
- const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(s);
+ case GDScriptParser::Node::VARIABLE: {
+ const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s);
// since we are using properties now for most class access, allow shadowing of class members to make user's life easier.
//
@@ -1594,20 +2070,49 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo
// return ERR_ALREADY_EXISTS;
//}
- codegen.add_stack_identifier(lv->name, p_stack_level++);
+ codegen.add_stack_identifier(lv->identifier->name, p_stack_level++);
codegen.alloc_stack(p_stack_level);
new_identifiers++;
+ if (lv->initializer != nullptr) {
+ int dst_address = codegen.stack_identifiers[lv->identifier->name];
+ dst_address |= GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS;
+
+ int src_address = _parse_expression(codegen, lv->initializer, p_stack_level);
+ if (src_address < 0) {
+ return ERR_PARSE_ERROR;
+ }
+ if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(lv->get_datatype()), lv->initializer->get_datatype())) {
+ return ERR_PARSE_ERROR;
+ }
+ }
} break;
+ case GDScriptParser::Node::CONSTANT: {
+ // Local constants.
+ const GDScriptParser::ConstantNode *lc = static_cast<const GDScriptParser::ConstantNode *>(s);
+ if (!lc->initializer->is_constant) {
+ _set_error("Local constant must have a constant value as initializer.", lc->initializer);
+ return ERR_PARSE_ERROR;
+ }
+ codegen.local_named_constants[lc->identifier->name] = codegen.get_constant_pos(lc->initializer->reduced_value);
+ } break;
+ case GDScriptParser::Node::PASS:
+ // Nothing to do.
+ break;
default: {
//expression
- int ret2 = _parse_expression(codegen, s, p_stack_level, true);
- if (ret2 < 0) {
- return ERR_PARSE_ERROR;
+ if (s->is_expression()) {
+ int ret2 = _parse_expression(codegen, static_cast<const GDScriptParser::ExpressionNode *>(s), p_stack_level, true);
+ if (ret2 < 0) {
+ return ERR_PARSE_ERROR;
+ }
+ } else {
+ ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Bug in bytecode compiler, unexpected node in parse tree while parsing statement."); //unreachable code
}
} break;
}
}
+
codegen.pop_stack_identifiers();
return OK;
}
@@ -1626,9 +2131,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
Vector<StringName> argnames;
int stack_level = 0;
+ int optional_parameters = 0;
if (p_func) {
- for (int i = 0; i < p_func->arguments.size(); i++) {
+ for (int i = 0; i < p_func->parameters.size(); i++) {
// since we are using properties now for most class access, allow shadowing of class members to make user's life easier.
//
//if (_is_class_member_property(p_script, p_func->arguments[i])) {
@@ -1636,42 +2142,81 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
// return ERR_ALREADY_EXISTS;
//}
- codegen.add_stack_identifier(p_func->arguments[i], i);
+ codegen.add_stack_identifier(p_func->parameters[i]->identifier->name, i);
#ifdef TOOLS_ENABLED
- argnames.push_back(p_func->arguments[i]);
+ argnames.push_back(p_func->parameters[i]->identifier->name);
#endif
+ if (p_func->parameters[i]->default_value != nullptr) {
+ optional_parameters++;
+ }
}
- stack_level = p_func->arguments.size();
+ stack_level = p_func->parameters.size();
}
codegen.alloc_stack(stack_level);
/* Parse initializer -if applies- */
- bool is_initializer = !p_for_ready && !p_func;
+ bool is_implicit_initializer = !p_for_ready && !p_func;
+ bool is_initializer = p_func && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init;
- if (is_initializer || (p_func && String(p_func->name) == "_init")) {
- //parse initializer for class members
- if (!p_func && p_class->extends_used && p_script->native.is_null()) {
- //call implicit parent constructor
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_SELF_BASE);
- codegen.opcodes.push_back(codegen.get_name_map_pos("_init"));
- codegen.opcodes.push_back(0);
- codegen.opcodes.push_back((GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) | 0);
- }
- Error err = _parse_block(codegen, p_class->initializer, stack_level);
- if (err) {
- return err;
+ if (is_implicit_initializer) {
+ // Initialize class fields.
+ for (int i = 0; i < p_class->members.size(); i++) {
+ if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) {
+ continue;
+ }
+ const GDScriptParser::VariableNode *field = p_class->members[i].variable;
+ if (field->onready) {
+ // Only initialize in _ready.
+ continue;
+ }
+
+ if (field->initializer) {
+ // Emit proper line change.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE);
+ codegen.opcodes.push_back(field->initializer->start_line);
+
+ int src_address = _parse_expression(codegen, field->initializer, stack_level, false, true);
+ if (src_address < 0) {
+ return ERR_PARSE_ERROR;
+ }
+ int dst_address = codegen.script->member_indices[field->identifier->name].index;
+ dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS;
+
+ if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) {
+ return ERR_PARSE_ERROR;
+ }
+ }
}
- is_initializer = true;
}
- if (p_for_ready || (p_func && String(p_func->name) == "_ready")) {
- //parse initializer for class members
- if (p_class->ready->statements.size()) {
- Error err = _parse_block(codegen, p_class->ready, stack_level);
- if (err) {
- return err;
+ if (p_for_ready || (p_func && String(p_func->identifier->name) == "_ready")) {
+ // Initialize class fields on ready.
+ for (int i = 0; i < p_class->members.size(); i++) {
+ if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) {
+ continue;
+ }
+ const GDScriptParser::VariableNode *field = p_class->members[i].variable;
+ if (!field->onready) {
+ continue;
+ }
+
+ if (field->initializer) {
+ // Emit proper line change.
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE);
+ codegen.opcodes.push_back(field->initializer->start_line);
+
+ int src_address = _parse_expression(codegen, field->initializer, stack_level, false, true);
+ if (src_address < 0) {
+ return ERR_PARSE_ERROR;
+ }
+ int dst_address = codegen.script->member_indices[field->identifier->name].index;
+ dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS;
+
+ if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) {
+ return ERR_PARSE_ERROR;
+ }
}
}
}
@@ -1682,31 +2227,39 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
StringName func_name;
if (p_func) {
- if (p_func->default_values.size()) {
+ if (optional_parameters > 0) {
codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT);
defarg_addr.push_back(codegen.opcodes.size());
- for (int i = 0; i < p_func->default_values.size(); i++) {
- _parse_expression(codegen, p_func->default_values[i], stack_level, true);
+ for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) {
+ int src_addr = _parse_expression(codegen, p_func->parameters[i]->default_value, stack_level, true);
+ if (src_addr < 0) {
+ return ERR_PARSE_ERROR;
+ }
+ int dst_addr = codegen.stack_identifiers[p_func->parameters[i]->identifier->name] | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS);
+ if (!_generate_typed_assign(codegen, src_addr, dst_addr, _gdtype_from_datatype(p_func->parameters[i]->get_datatype()), p_func->parameters[i]->default_value->get_datatype())) {
+ return ERR_PARSE_ERROR;
+ }
defarg_addr.push_back(codegen.opcodes.size());
}
-
defarg_addr.invert();
}
+ func_name = p_func->identifier->name;
+ codegen.function_name = func_name;
Error err = _parse_block(codegen, p_func->body, stack_level);
if (err) {
return err;
}
- func_name = p_func->name;
} else {
if (p_for_ready) {
func_name = "_ready";
} else {
- func_name = "_init";
+ func_name = "@implicit_new";
}
}
+ codegen.function_name = func_name;
codegen.opcodes.push_back(GDScriptFunction::OPCODE_END);
/*
@@ -1719,13 +2272,13 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
//}
if (p_func) {
- gdfunc->_static = p_func->_static;
+ gdfunc->_static = p_func->is_static;
gdfunc->rpc_mode = p_func->rpc_mode;
- gdfunc->argument_types.resize(p_func->argument_types.size());
- for (int i = 0; i < p_func->argument_types.size(); i++) {
- gdfunc->argument_types.write[i] = _gdtype_from_datatype(p_func->argument_types[i]);
+ gdfunc->argument_types.resize(p_func->parameters.size());
+ for (int i = 0; i < p_func->parameters.size(); i++) {
+ gdfunc->argument_types.write[i] = _gdtype_from_datatype(p_func->parameters[i]->get_datatype());
}
- gdfunc->return_type = _gdtype_from_datatype(p_func->return_type);
+ gdfunc->return_type = _gdtype_from_datatype(p_func->get_datatype());
} else {
gdfunc->_static = false;
gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
@@ -1797,7 +2350,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
gdfunc->_default_arg_ptr = nullptr;
}
- gdfunc->_argument_count = p_func ? p_func->arguments.size() : 0;
+ gdfunc->_argument_count = p_func ? p_func->parameters.size() : 0;
gdfunc->_stack_size = codegen.stack_max;
gdfunc->_call_size = codegen.call_max;
gdfunc->name = func_name;
@@ -1810,15 +2363,15 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
}
//loc
if (p_func) {
- signature += "::" + itos(p_func->body->line);
+ signature += "::" + itos(p_func->body->start_line);
} else {
signature += "::0";
}
//function and class
- if (p_class->name) {
- signature += "::" + String(p_class->name) + "." + String(func_name);
+ if (p_class->identifier) {
+ signature += "::" + String(p_class->identifier->name) + "." + String(func_name);
} else {
signature += "::" + String(func_name);
}
@@ -1838,10 +2391,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
#endif
if (p_func) {
- gdfunc->_initial_line = p_func->line;
+ gdfunc->_initial_line = p_func->start_line;
#ifdef TOOLS_ENABLED
- p_script->member_lines[func_name] = p_func->line;
+ p_script->member_lines[func_name] = p_func->start_line;
#endif
} else {
gdfunc->_initial_line = 0;
@@ -1854,6 +2407,153 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
if (is_initializer) {
p_script->initializer = gdfunc;
}
+ if (is_implicit_initializer) {
+ p_script->implicit_initializer = gdfunc;
+ }
+
+ return OK;
+}
+
+Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) {
+ Vector<int> bytecode;
+ CodeGen codegen;
+
+ codegen.class_node = p_class;
+ codegen.script = p_script;
+ codegen.function_node = nullptr;
+ codegen.stack_max = 0;
+ codegen.current_line = 0;
+ codegen.call_max = 0;
+ codegen.debug_stack = EngineDebugger::is_active();
+ Vector<StringName> argnames;
+
+ int stack_level = 0;
+
+ if (p_is_setter) {
+ codegen.add_stack_identifier(p_variable->setter_parameter->name, stack_level++);
+ argnames.push_back(p_variable->setter_parameter->name);
+ }
+
+ codegen.alloc_stack(stack_level);
+
+ StringName func_name;
+
+ if (p_is_setter) {
+ func_name = "@" + p_variable->identifier->name + "_setter";
+ } else {
+ func_name = "@" + p_variable->identifier->name + "_getter";
+ }
+ codegen.function_name = func_name;
+
+ Error err = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter, stack_level);
+ if (err != OK) {
+ return err;
+ }
+
+ codegen.opcodes.push_back(GDScriptFunction::OPCODE_END);
+
+ p_script->member_functions[func_name] = memnew(GDScriptFunction);
+ GDScriptFunction *gdfunc = p_script->member_functions[func_name];
+
+ gdfunc->_static = false;
+ gdfunc->rpc_mode = p_variable->rpc_mode;
+ gdfunc->argument_types.resize(p_is_setter ? 1 : 0);
+ gdfunc->return_type = _gdtype_from_datatype(p_variable->get_datatype());
+#ifdef TOOLS_ENABLED
+ gdfunc->arg_names = argnames;
+#endif
+
+ // TODO: Unify this with function compiler.
+ //constants
+ if (codegen.constant_map.size()) {
+ gdfunc->_constant_count = codegen.constant_map.size();
+ gdfunc->constants.resize(codegen.constant_map.size());
+ gdfunc->_constants_ptr = gdfunc->constants.ptrw();
+ const Variant *K = nullptr;
+ while ((K = codegen.constant_map.next(K))) {
+ int idx = codegen.constant_map[*K];
+ gdfunc->constants.write[idx] = *K;
+ }
+ } else {
+ gdfunc->_constants_ptr = nullptr;
+ gdfunc->_constant_count = 0;
+ }
+ //global names
+ if (codegen.name_map.size()) {
+ gdfunc->global_names.resize(codegen.name_map.size());
+ gdfunc->_global_names_ptr = &gdfunc->global_names[0];
+ for (Map<StringName, int>::Element *E = codegen.name_map.front(); E; E = E->next()) {
+ gdfunc->global_names.write[E->get()] = E->key();
+ }
+ gdfunc->_global_names_count = gdfunc->global_names.size();
+
+ } else {
+ gdfunc->_global_names_ptr = nullptr;
+ gdfunc->_global_names_count = 0;
+ }
+
+#ifdef TOOLS_ENABLED
+ // Named globals
+ if (codegen.named_globals.size()) {
+ gdfunc->named_globals.resize(codegen.named_globals.size());
+ gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr();
+ for (int i = 0; i < codegen.named_globals.size(); i++) {
+ gdfunc->named_globals.write[i] = codegen.named_globals[i];
+ }
+ gdfunc->_named_globals_count = gdfunc->named_globals.size();
+ }
+#endif
+
+ gdfunc->code = codegen.opcodes;
+ gdfunc->_code_ptr = &gdfunc->code[0];
+ gdfunc->_code_size = codegen.opcodes.size();
+ gdfunc->_default_arg_count = 0;
+ gdfunc->_default_arg_ptr = nullptr;
+ gdfunc->_argument_count = argnames.size();
+ gdfunc->_stack_size = codegen.stack_max;
+ gdfunc->_call_size = codegen.call_max;
+ gdfunc->name = func_name;
+#ifdef DEBUG_ENABLED
+ if (EngineDebugger::is_active()) {
+ String signature;
+ //path
+ if (p_script->get_path() != String()) {
+ signature += p_script->get_path();
+ }
+ //loc
+ signature += "::" + itos(p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line);
+
+ //function and class
+
+ if (p_class->identifier) {
+ signature += "::" + String(p_class->identifier->name) + "." + String(func_name);
+ } else {
+ signature += "::" + String(func_name);
+ }
+
+ gdfunc->profile.signature = signature;
+ }
+#endif
+ gdfunc->_script = p_script;
+ gdfunc->source = source;
+
+#ifdef DEBUG_ENABLED
+
+ {
+ gdfunc->func_cname = (String(source) + " - " + String(func_name)).utf8();
+ gdfunc->_func_cname = gdfunc->func_cname.get_data();
+ }
+
+#endif
+ gdfunc->_initial_line = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line;
+#ifdef TOOLS_ENABLED
+
+ p_script->member_lines[func_name] = gdfunc->_initial_line;
+#endif
+
+ if (codegen.debug_stack) {
+ gdfunc->stack_debug = codegen.stack_debug;
+ }
return OK;
}
@@ -1861,14 +2561,14 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
parsing_classes.insert(p_script);
- if (p_class->owner && p_class->owner->owner) {
+ if (p_class->outer && p_class->outer->outer) {
// Owner is not root
if (!parsed_classes.has(p_script->_owner)) {
if (parsing_classes.has(p_script->_owner)) {
- _set_error("Cyclic class reference for '" + String(p_class->name) + "'.", p_class);
+ _set_error("Cyclic class reference for '" + String(p_class->identifier->name) + "'.", p_class);
return ERR_PARSE_ERROR;
}
- Error err = _parse_class_level(p_script->_owner, p_class->owner, p_keep_state);
+ Error err = _parse_class_level(p_script->_owner, p_class->outer, p_keep_state);
if (err) {
return err;
}
@@ -1889,8 +2589,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
p_script->_signals.clear();
p_script->initializer = nullptr;
- p_script->tool = p_class->tool;
- p_script->name = p_class->name;
+ p_script->tool = parser->is_tool();
+ p_script->name = p_class->identifier ? p_class->identifier->name : "";
Ref<GDScriptNativeClass> native;
@@ -1907,12 +2607,12 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
Ref<GDScript> base = base_type.script_type;
p_script->base = base;
p_script->_base = base.ptr();
- p_script->member_indices = base->member_indices;
- if (p_class->base_type.kind == GDScriptParser::DataType::CLASS) {
+ if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) {
if (!parsed_classes.has(p_script->_base)) {
if (parsing_classes.has(p_script->_base)) {
- _set_error("Cyclic class reference for '" + String(p_class->name) + "'.", p_class);
+ String class_name = p_class->identifier ? p_class->identifier->name : "<main>";
+ _set_error("Cyclic class reference for '" + class_name + "'.", p_class);
return ERR_PARSE_ERROR;
}
Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state);
@@ -1921,6 +2621,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
}
}
}
+
+ p_script->member_indices = base->member_indices;
} break;
default: {
_set_error("Parser bug: invalid inheritance.", p_class);
@@ -1928,86 +2630,146 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
} break;
}
- for (int i = 0; i < p_class->variables.size(); i++) {
- StringName name = p_class->variables[i].identifier;
-
- GDScript::MemberInfo minfo;
- minfo.index = p_script->member_indices.size();
- minfo.setter = p_class->variables[i].setter;
- minfo.getter = p_class->variables[i].getter;
- minfo.rpc_mode = p_class->variables[i].rpc_mode;
- minfo.data_type = _gdtype_from_datatype(p_class->variables[i].data_type);
+ for (int i = 0; i < p_class->members.size(); i++) {
+ const GDScriptParser::ClassNode::Member &member = p_class->members[i];
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::VARIABLE: {
+ const GDScriptParser::VariableNode *variable = member.variable;
+ StringName name = variable->identifier->name;
+
+ GDScript::MemberInfo minfo;
+ minfo.index = p_script->member_indices.size();
+ switch (variable->property) {
+ case GDScriptParser::VariableNode::PROP_NONE:
+ break; // Nothing to do.
+ case GDScriptParser::VariableNode::PROP_SETGET:
+ if (variable->setter_pointer != nullptr) {
+ minfo.setter = variable->setter_pointer->name;
+ }
+ if (variable->getter_pointer != nullptr) {
+ minfo.getter = variable->getter_pointer->name;
+ }
+ break;
+ case GDScriptParser::VariableNode::PROP_INLINE:
+ if (variable->setter != nullptr) {
+ minfo.setter = "@" + variable->identifier->name + "_setter";
+ }
+ if (variable->getter != nullptr) {
+ minfo.getter = "@" + variable->identifier->name + "_getter";
+ }
+ break;
+ }
+ minfo.rpc_mode = variable->rpc_mode;
+ minfo.data_type = _gdtype_from_datatype(variable->get_datatype());
- PropertyInfo prop_info = minfo.data_type;
- prop_info.name = name;
- PropertyInfo export_info = p_class->variables[i]._export;
+ PropertyInfo prop_info = minfo.data_type;
+ prop_info.name = name;
+ PropertyInfo export_info = variable->export_info;
- if (export_info.type != Variant::NIL) {
- if (!minfo.data_type.has_type) {
- prop_info.type = export_info.type;
- prop_info.class_name = export_info.class_name;
- }
- prop_info.hint = export_info.hint;
- prop_info.hint_string = export_info.hint_string;
- prop_info.usage = export_info.usage;
+ if (variable->exported) {
+ if (!minfo.data_type.has_type) {
+ prop_info.type = export_info.type;
+ prop_info.class_name = export_info.class_name;
+ }
+ prop_info.hint = export_info.hint;
+ prop_info.hint_string = export_info.hint_string;
+ prop_info.usage = export_info.usage;
#ifdef TOOLS_ENABLED
- if (p_class->variables[i].default_value.get_type() != Variant::NIL) {
- p_script->member_default_values[name] = p_class->variables[i].default_value;
- }
+ if (variable->initializer != nullptr && variable->initializer->type == GDScriptParser::Node::LITERAL) {
+ p_script->member_default_values[name] = static_cast<const GDScriptParser::LiteralNode *>(variable->initializer)->value;
+ }
#endif
- } else {
- prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE;
- }
+ } else {
+ prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE;
+ }
- p_script->member_info[name] = prop_info;
- p_script->member_indices[name] = minfo;
- p_script->members.insert(name);
+ p_script->member_info[name] = prop_info;
+ p_script->member_indices[name] = minfo;
+ p_script->members.insert(name);
#ifdef TOOLS_ENABLED
- p_script->member_lines[name] = p_class->variables[i].line;
+ p_script->member_lines[name] = variable->start_line;
#endif
- }
+ } break;
- for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
- StringName name = E->key();
+ case GDScriptParser::ClassNode::Member::CONSTANT: {
+ const GDScriptParser::ConstantNode *constant = member.constant;
+ StringName name = constant->identifier->name;
- ERR_CONTINUE(E->get().expression->type != GDScriptParser::Node::TYPE_CONSTANT);
+ ERR_CONTINUE(constant->initializer->type != GDScriptParser::Node::LITERAL);
- GDScriptParser::ConstantNode *constant = static_cast<GDScriptParser::ConstantNode *>(E->get().expression);
+ const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(constant->initializer);
- p_script->constants.insert(name, constant->value);
+ p_script->constants.insert(name, literal->value);
#ifdef TOOLS_ENABLED
- p_script->member_lines[name] = E->get().expression->line;
+ p_script->member_lines[name] = constant->start_line;
#endif
- }
+ } break;
- for (int i = 0; i < p_class->_signals.size(); i++) {
- StringName name = p_class->_signals[i].name;
+ case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
+ const GDScriptParser::EnumNode::Value &enum_value = member.enum_value;
+ StringName name = enum_value.identifier->name;
- GDScript *c = p_script;
+ p_script->constants.insert(name, enum_value.value);
+#ifdef TOOLS_ENABLED
+ p_script->member_lines[name] = enum_value.identifier->start_line;
+#endif
+ } break;
- while (c) {
- if (c->_signals.has(name)) {
- _set_error("Signal '" + name + "' redefined (in current or parent class)", p_class);
- return ERR_ALREADY_EXISTS;
- }
+ case GDScriptParser::ClassNode::Member::SIGNAL: {
+ const GDScriptParser::SignalNode *signal = member.signal;
+ StringName name = signal->identifier->name;
- if (c->base.is_valid()) {
- c = c->base.ptr();
- } else {
- c = nullptr;
- }
- }
+ GDScript *c = p_script;
- if (native.is_valid()) {
- if (ClassDB::has_signal(native->get_name(), name)) {
- _set_error("Signal '" + name + "' redefined (original in native class '" + String(native->get_name()) + "')", p_class);
- return ERR_ALREADY_EXISTS;
- }
- }
+ while (c) {
+ if (c->_signals.has(name)) {
+ _set_error("Signal '" + name + "' redefined (in current or parent class)", p_class);
+ return ERR_ALREADY_EXISTS;
+ }
+
+ if (c->base.is_valid()) {
+ c = c->base.ptr();
+ } else {
+ c = nullptr;
+ }
+ }
+
+ if (native.is_valid()) {
+ if (ClassDB::has_signal(native->get_name(), name)) {
+ _set_error("Signal '" + name + "' redefined (original in native class '" + String(native->get_name()) + "')", p_class);
+ return ERR_ALREADY_EXISTS;
+ }
+ }
+
+ Vector<StringName> parameters_names;
+ parameters_names.resize(signal->parameters.size());
+ for (int j = 0; j < signal->parameters.size(); j++) {
+ parameters_names.write[j] = signal->parameters[j]->identifier->name;
+ }
+ p_script->_signals[name] = parameters_names;
+ } break;
+
+ case GDScriptParser::ClassNode::Member::ENUM: {
+ const GDScriptParser::EnumNode *enum_n = member.m_enum;
- p_script->_signals[name] = p_class->_signals[i].arguments;
+ // TODO: Make enums not be just a dictionary?
+ Dictionary new_enum;
+ for (int j = 0; j < enum_n->values.size(); j++) {
+ int value = enum_n->values[j].value;
+ // Needs to be string because Variant::get will convert to String.
+ new_enum[String(enum_n->values[j].identifier->name)] = value;
+ }
+
+ p_script->constants.insert(enum_n->identifier->name, new_enum);
+#ifdef TOOLS_ENABLED
+ p_script->member_lines[enum_n->identifier->name] = enum_n->start_line;
+#endif
+ } break;
+ default:
+ break; // Nothing to do here.
+ }
}
parsed_classes.insert(p_script);
@@ -2015,14 +2777,19 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
//parse sub-classes
- for (int i = 0; i < p_class->subclasses.size(); i++) {
- StringName name = p_class->subclasses[i]->name;
+ for (int i = 0; i < p_class->members.size(); i++) {
+ const GDScriptParser::ClassNode::Member &member = p_class->members[i];
+ if (member.type != member.CLASS) {
+ continue;
+ }
+ const GDScriptParser::ClassNode *inner_class = member.m_class;
+ StringName name = inner_class->identifier->name;
Ref<GDScript> &subclass = p_script->subclasses[name];
GDScript *subclass_ptr = subclass.ptr();
// Subclass might still be parsing, just skip it
if (!parsed_classes.has(subclass_ptr) && !parsing_classes.has(subclass_ptr)) {
- Error err = _parse_class_level(subclass_ptr, p_class->subclasses[i], p_keep_state);
+ Error err = _parse_class_level(subclass_ptr, inner_class, p_keep_state);
if (err) {
return err;
}
@@ -2030,7 +2797,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
#ifdef TOOLS_ENABLED
- p_script->member_lines[name] = p_class->subclasses[i]->line;
+ p_script->member_lines[name] = inner_class->start_line;
#endif
p_script->constants.insert(name, subclass); //once parsed, goes to the list of constants
@@ -2042,41 +2809,48 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
//parse methods
- bool has_initializer = false;
bool has_ready = false;
- for (int i = 0; i < p_class->functions.size(); i++) {
- if (!has_initializer && p_class->functions[i]->name == "_init") {
- has_initializer = true;
- }
- if (!has_ready && p_class->functions[i]->name == "_ready") {
- has_ready = true;
- }
- Error err = _parse_function(p_script, p_class, p_class->functions[i]);
- if (err) {
- return err;
- }
- }
-
- //parse static methods
-
- for (int i = 0; i < p_class->static_functions.size(); i++) {
- Error err = _parse_function(p_script, p_class, p_class->static_functions[i]);
- if (err) {
- return err;
+ for (int i = 0; i < p_class->members.size(); i++) {
+ const GDScriptParser::ClassNode::Member &member = p_class->members[i];
+ if (member.type == member.FUNCTION) {
+ const GDScriptParser::FunctionNode *function = member.function;
+ if (!has_ready && function->identifier->name == "_ready") {
+ has_ready = true;
+ }
+ Error err = _parse_function(p_script, p_class, function);
+ if (err) {
+ return err;
+ }
+ } else if (member.type == member.VARIABLE) {
+ const GDScriptParser::VariableNode *variable = member.variable;
+ if (variable->property == GDScriptParser::VariableNode::PROP_INLINE) {
+ if (variable->setter != nullptr) {
+ Error err = _parse_setter_getter(p_script, p_class, variable, true);
+ if (err) {
+ return err;
+ }
+ }
+ if (variable->getter != nullptr) {
+ Error err = _parse_setter_getter(p_script, p_class, variable, false);
+ if (err) {
+ return err;
+ }
+ }
+ }
}
}
- if (!has_initializer) {
- //create a constructor
+ {
+ // Create an implicit constructor in any case.
Error err = _parse_function(p_script, p_class, nullptr);
if (err) {
return err;
}
}
- if (!has_ready && p_class->ready->statements.size()) {
- //create a constructor
+ if (!has_ready && p_class->onready_used) {
+ //create a _ready constructor
Error err = _parse_function(p_script, p_class, nullptr, true);
if (err) {
return err;
@@ -2132,11 +2906,15 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa
}
#endif
- for (int i = 0; i < p_class->subclasses.size(); i++) {
- StringName name = p_class->subclasses[i]->name;
+ for (int i = 0; i < p_class->members.size(); i++) {
+ if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
+ continue;
+ }
+ const GDScriptParser::ClassNode *inner_class = p_class->members[i].m_class;
+ StringName name = inner_class->identifier->name;
GDScript *subclass = p_script->subclasses[name].ptr();
- Error err = _parse_class_blocks(subclass, p_class->subclasses[i], p_keep_state);
+ Error err = _parse_class_blocks(subclass, inner_class, p_keep_state);
if (err) {
return err;
}
@@ -2155,8 +2933,12 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C
p_script->subclasses.clear();
- for (int i = 0; i < p_class->subclasses.size(); i++) {
- StringName name = p_class->subclasses[i]->name;
+ for (int i = 0; i < p_class->members.size(); i++) {
+ if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
+ continue;
+ }
+ const GDScriptParser::ClassNode *inner_class = p_class->members[i].m_class;
+ StringName name = inner_class->identifier->name;
Ref<GDScript> subclass;
String fully_qualified_name = p_script->fully_qualified_name + "::" + name;
@@ -2176,7 +2958,7 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C
subclass->fully_qualified_name = fully_qualified_name;
p_script->subclasses.insert(name, subclass);
- _make_scripts(subclass.ptr(), p_class->subclasses[i], false);
+ _make_scripts(subclass.ptr(), inner_class, false);
}
}
@@ -2186,8 +2968,7 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
error = "";
parser = p_parser;
main_script = p_script;
- const GDScriptParser::Node *root = parser->get_parse_tree();
- ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, ERR_INVALID_DATA);
+ const GDScriptParser::ClassNode *root = parser->get_tree();
source = p_script->get_path();
@@ -2195,22 +2976,22 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
p_script->fully_qualified_name = p_script->path;
// Create scripts for subclasses beforehand so they can be referenced
- _make_scripts(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state);
+ _make_scripts(p_script, root, p_keep_state);
p_script->_owner = nullptr;
- Error err = _parse_class_level(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state);
+ Error err = _parse_class_level(p_script, root, p_keep_state);
if (err) {
return err;
}
- err = _parse_class_blocks(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state);
+ err = _parse_class_blocks(p_script, root, p_keep_state);
if (err) {
return err;
}
- return OK;
+ return GDScriptCache::finish_compiling(p_script->get_path());
}
String GDScriptCompiler::get_error() const {
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index 315d4f1842..e8601f69c7 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -33,6 +33,7 @@
#include "core/set.h"
#include "gdscript.h"
+#include "gdscript_function.h"
#include "gdscript_parser.h"
class GDScriptCompiler {
@@ -44,6 +45,7 @@ class GDScriptCompiler {
GDScript *script;
const GDScriptParser::ClassNode *class_node;
const GDScriptParser::FunctionNode *function_node;
+ StringName function_name;
bool debug_stack;
List<Map<StringName, int>> stack_id_stack;
@@ -52,6 +54,7 @@ class GDScriptCompiler {
List<GDScriptFunction::StackDebug> stack_debug;
List<Map<StringName, int>> block_identifier_stack;
Map<StringName, int> block_identifiers;
+ Map<StringName, int> local_named_constants;
void add_stack_identifier(const StringName &p_id, int p_stackpos) {
stack_identifiers[p_id] = p_stackpos;
@@ -111,11 +114,11 @@ class GDScriptCompiler {
int get_constant_pos(const Variant &p_constant) {
if (constant_map.has(p_constant)) {
- return constant_map[p_constant];
+ return constant_map[p_constant] | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS);
}
int pos = constant_map.size();
constant_map[p_constant] = pos;
- return pos;
+ return pos | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS);
}
Vector<int> opcodes;
@@ -140,15 +143,19 @@ class GDScriptCompiler {
void _set_error(const String &p_error, const GDScriptParser::Node *p_node);
- bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level);
- bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0);
+ bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level);
+ bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0);
+ bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0);
+ bool _generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type);
GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const;
- int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level, int p_index_addr = 0);
- int _parse_expression(CodeGen &codegen, const GDScriptParser::Node *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false, int p_index_addr = 0);
- Error _parse_block(CodeGen &codegen, const GDScriptParser::BlockNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1);
+ int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::AssignmentNode *p_assignment, int p_stack_level, int p_index_addr = 0);
+ int _parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false, int p_index_addr = 0);
+ Error _parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address);
+ Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1);
Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false);
+ Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
void _make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
@@ -156,6 +163,7 @@ class GDScriptCompiler {
int err_column;
StringName source;
String error;
+ bool within_await = false;
public:
Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 3a5db3687b..239015060e 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -33,9 +33,13 @@
#include "core/engine.h"
#include "core/global_constants.h"
#include "core/os/file_access.h"
+#include "gdscript_analyzer.h"
#include "gdscript_compiler.h"
+#include "gdscript_parser.h"
+#include "gdscript_tokenizer.h"
#ifdef TOOLS_ENABLED
+#include "core/project_settings.h"
#include "editor/editor_file_system.h"
#include "editor/editor_settings.h"
#endif
@@ -114,16 +118,35 @@ void GDScriptLanguage::make_template(const String &p_class_name, const String &p
p_script->set_source_code(_template);
}
+static void get_function_names_recursively(const GDScriptParser::ClassNode *p_class, const String &p_prefix, Map<int, String> &r_funcs) {
+ for (int i = 0; i < p_class->members.size(); i++) {
+ if (p_class->members[i].type == GDScriptParser::ClassNode::Member::FUNCTION) {
+ const GDScriptParser::FunctionNode *function = p_class->members[i].function;
+ r_funcs[function->start_line] = p_prefix.empty() ? String(function->identifier->name) : p_prefix + "." + String(function->identifier->name);
+ } else if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) {
+ String new_prefix = p_class->members[i].m_class->identifier->name;
+ get_function_names_recursively(p_class->members[i].m_class, p_prefix.empty() ? new_prefix : p_prefix + "." + new_prefix, r_funcs);
+ }
+ }
+}
+
bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const {
GDScriptParser parser;
+ GDScriptAnalyzer analyzer(&parser);
- Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path, false, r_safe_lines);
+ Error err = parser.parse(p_script, p_path, false);
+ if (err == OK) {
+ err = analyzer.analyze();
+ }
#ifdef DEBUG_ENABLED
if (r_warnings) {
for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
const GDScriptWarning &warn = E->get();
ScriptLanguage::Warning w;
- w.line = warn.line;
+ w.start_line = warn.start_line;
+ w.end_line = warn.end_line;
+ w.leftmost_column = warn.leftmost_column;
+ w.rightmost_column = warn.rightmost_column;
w.code = (int)warn.code;
w.string_code = GDScriptWarning::get_name_from_code(warn.code);
w.message = warn.get_message();
@@ -132,38 +155,33 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &
}
#endif
if (err) {
- r_line_error = parser.get_error_line();
- r_col_error = parser.get_error_column();
- r_test_error = parser.get_error();
+ GDScriptParser::ParserError parse_error = parser.get_errors().front()->get();
+ r_line_error = parse_error.line;
+ r_col_error = parse_error.column;
+ r_test_error = parse_error.message;
return false;
} else {
- const GDScriptParser::Node *root = parser.get_parse_tree();
- ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, false);
-
- const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root);
+ const GDScriptParser::ClassNode *cl = parser.get_tree();
Map<int, String> funcs;
- for (int i = 0; i < cl->functions.size(); i++) {
- funcs[cl->functions[i]->line] = cl->functions[i]->name;
- }
-
- for (int i = 0; i < cl->static_functions.size(); i++) {
- funcs[cl->static_functions[i]->line] = cl->static_functions[i]->name;
- }
- for (int i = 0; i < cl->subclasses.size(); i++) {
- for (int j = 0; j < cl->subclasses[i]->functions.size(); j++) {
- funcs[cl->subclasses[i]->functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->functions[j]->name;
- }
- for (int j = 0; j < cl->subclasses[i]->static_functions.size(); j++) {
- funcs[cl->subclasses[i]->static_functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->static_functions[j]->name;
- }
- }
+ get_function_names_recursively(cl, "", funcs);
for (Map<int, String>::Element *E = funcs.front(); E; E = E->next()) {
r_functions->push_back(E->get() + ":" + itos(E->key()));
}
}
+#ifdef DEBUG_ENABLED
+ if (r_safe_lines) {
+ const Set<int> &unsafe_lines = parser.get_unsafe_lines();
+ for (int i = 1; i <= parser.get_last_line_number(); i++) {
+ if (!unsafe_lines.has(i)) {
+ r_safe_lines->insert(i);
+ }
+ }
+ }
+#endif
+
return true;
}
@@ -176,20 +194,26 @@ bool GDScriptLanguage::supports_builtin_mode() const {
}
int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const {
- GDScriptTokenizerText tokenizer;
- tokenizer.set_code(p_code);
+ GDScriptTokenizer tokenizer;
+ tokenizer.set_source_code(p_code);
int indent = 0;
- while (tokenizer.get_token() != GDScriptTokenizer::TK_EOF && tokenizer.get_token() != GDScriptTokenizer::TK_ERROR) {
- if (tokenizer.get_token() == GDScriptTokenizer::TK_NEWLINE) {
- indent = tokenizer.get_token_line_indent();
- }
- if (indent == 0 && tokenizer.get_token() == GDScriptTokenizer::TK_PR_FUNCTION && tokenizer.get_token(1) == GDScriptTokenizer::TK_IDENTIFIER) {
- String identifier = tokenizer.get_token_identifier(1);
- if (identifier == p_function) {
- return tokenizer.get_token_line();
+ GDScriptTokenizer::Token current = tokenizer.scan();
+ while (current.type != GDScriptTokenizer::Token::TK_EOF && current.type != GDScriptTokenizer::Token::ERROR) {
+ if (current.type == GDScriptTokenizer::Token::INDENT) {
+ indent++;
+ } else if (current.type == GDScriptTokenizer::Token::DEDENT) {
+ indent--;
+ }
+ if (indent == 0 && current.type == GDScriptTokenizer::Token::FUNC) {
+ current = tokenizer.scan();
+ if (current.is_identifier()) {
+ String identifier = current.get_identifier();
+ if (identifier == p_function) {
+ return current.start_line;
+ }
}
}
- tokenizer.advance();
+ current = tokenizer.scan();
}
return -1;
}
@@ -397,16 +421,6 @@ void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const
}
{
MethodInfo mi;
- mi.name = "yield";
- mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "object"));
- mi.arguments.push_back(PropertyInfo(Variant::STRING, "signal"));
- mi.default_arguments.push_back(Variant());
- mi.default_arguments.push_back(String());
- mi.return_val = PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, "GDScriptFunctionState");
- p_functions->push_back(mi);
- }
- {
- MethodInfo mi;
mi.name = "assert";
mi.return_val.type = Variant::NIL;
mi.arguments.push_back(PropertyInfo(Variant::BOOL, "condition"));
@@ -467,44 +481,46 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na
//////// COMPLETION //////////
-#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
-
-struct GDScriptCompletionContext {
- const GDScriptParser::ClassNode *_class = nullptr;
- const GDScriptParser::FunctionNode *function = nullptr;
- const GDScriptParser::BlockNode *block = nullptr;
- Object *base = nullptr;
- String base_path;
- int line = 0;
- uint32_t depth = 0;
-
- GDScriptCompletionContext() {}
-};
+#ifdef TOOLS_ENABLED
struct GDScriptCompletionIdentifier {
GDScriptParser::DataType type;
String enumeration;
Variant value;
- const GDScriptParser::Node *assigned_expression = nullptr;
-
- GDScriptCompletionIdentifier() {}
+ const GDScriptParser::ExpressionNode *assigned_expression = nullptr;
};
-static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String, ScriptCodeCompletionOption> &r_list) {
- const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
-
- for (int i = 0; i < p_dir->get_file_count(); i++) {
- ScriptCodeCompletionOption option(p_dir->get_file_path(i), ScriptCodeCompletionOption::KIND_FILE_PATH);
- option.insert_text = quote_style + option.display + quote_style;
- r_list.insert(option.display, option);
- }
-
- for (int i = 0; i < p_dir->get_subdir_count(); i++) {
- _get_directory_contents(p_dir->get_subdir(i), r_list);
+// TODO: Move this to a central location (maybe core?).
+static const char *underscore_classes[] = {
+ "ClassDB",
+ "Directory",
+ "Engine",
+ "File",
+ "Geometry",
+ "GodotSharp",
+ "JSON",
+ "Marshalls",
+ "Mutex",
+ "OS",
+ "ResourceLoader",
+ "ResourceSaver",
+ "Semaphore",
+ "Thread",
+ "VisualScriptEditor",
+ nullptr,
+};
+static StringName _get_real_class_name(const StringName &p_source) {
+ const char **class_name = underscore_classes;
+ while (*class_name != nullptr) {
+ if (p_source == *class_name) {
+ return String("_") + p_source;
+ }
+ class_name++;
}
+ return p_source;
}
-static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = true) {
+static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) {
if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
String enum_name = p_info.class_name;
if (enum_name.find(".") == -1) {
@@ -527,7 +543,7 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = tr
}
}
if (p_info.type == Variant::NIL) {
- if (p_isarg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
+ if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
return "Variant";
} else {
return "void";
@@ -537,11 +553,538 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = tr
return Variant::get_type_name(p_info.type);
}
+static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx, bool p_is_annotation = false) {
+ String arghint;
+ if (!p_is_annotation) {
+ arghint += _get_visual_datatype(p_info.return_val, false) + " ";
+ }
+ arghint += p_info.name + "(";
+
+ int def_args = p_info.arguments.size() - p_info.default_arguments.size();
+ int i = 0;
+ for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E; E = E->next()) {
+ if (i > 0) {
+ arghint += ", ";
+ }
+
+ if (i == p_arg_idx) {
+ arghint += String::chr(0xFFFF);
+ }
+ arghint += E->get().name + ": " + _get_visual_datatype(E->get(), true);
+
+ if (i - def_args >= 0) {
+ arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string();
+ }
+
+ if (i == p_arg_idx) {
+ arghint += String::chr(0xFFFF);
+ }
+
+ i++;
+ }
+
+ if (p_info.flags & METHOD_FLAG_VARARG) {
+ if (p_info.arguments.size() > 0) {
+ arghint += ", ";
+ }
+ if (p_arg_idx >= p_info.arguments.size()) {
+ arghint += String::chr(0xFFFF);
+ }
+ arghint += "...";
+ if (p_arg_idx >= p_info.arguments.size()) {
+ arghint += String::chr(0xFFFF);
+ }
+ }
+
+ arghint += ")";
+
+ return arghint;
+}
+
+static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) {
+ String arghint = p_function->get_datatype().to_string() + " " + p_function->identifier->name.operator String() + "(";
+
+ for (int i = 0; i < p_function->parameters.size(); i++) {
+ if (i > 0) {
+ arghint += ", ";
+ }
+
+ if (i == p_arg_idx) {
+ arghint += String::chr(0xFFFF);
+ }
+ const GDScriptParser::ParameterNode *par = p_function->parameters[i];
+ arghint += par->identifier->name.operator String() + ": " + par->get_datatype().to_string();
+
+ if (par->default_value) {
+ String def_val = "<unknown>";
+ if (par->default_value->type == GDScriptParser::Node::LITERAL) {
+ const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->default_value);
+ def_val = literal->value.get_construct_string();
+ } else if (par->default_value->type == GDScriptParser::Node::IDENTIFIER) {
+ const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->default_value);
+ def_val = id->name.operator String();
+ }
+ arghint += " = " + def_val;
+ }
+ if (i == p_arg_idx) {
+ arghint += String::chr(0xFFFF);
+ }
+ }
+
+ arghint += ")";
+
+ return arghint;
+}
+
+static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String, ScriptCodeCompletionOption> &r_list) {
+ const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
+
+ for (int i = 0; i < p_dir->get_file_count(); i++) {
+ ScriptCodeCompletionOption option(p_dir->get_file_path(i), ScriptCodeCompletionOption::KIND_FILE_PATH);
+ option.insert_text = quote_style + option.display + quote_style;
+ r_list.insert(option.display, option);
+ }
+
+ for (int i = 0; i < p_dir->get_subdir_count(); i++) {
+ _get_directory_contents(p_dir->get_subdir(i), r_list);
+ }
+}
+
+static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, Map<String, ScriptCodeCompletionOption> &r_result) {
+ if (p_annotation->name == "@export_range" || p_annotation->name == "@export_exp_range") {
+ if (p_argument == 3 || p_argument == 4) {
+ // Slider hint.
+ ScriptCodeCompletionOption slider1("or_greater", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ slider1.insert_text = p_quote_style + slider1.display + p_quote_style;
+ r_result.insert(slider1.display, slider1);
+ ScriptCodeCompletionOption slider2("or_lesser", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ slider2.insert_text = p_quote_style + slider2.display + p_quote_style;
+ r_result.insert(slider2.display, slider2);
+ }
+ } else if (p_annotation->name == "@export_exp_easing") {
+ if (p_argument == 0 || p_argument == 1) {
+ // Easing hint.
+ ScriptCodeCompletionOption hint1("attenuation", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ hint1.insert_text = p_quote_style + hint1.display + p_quote_style;
+ r_result.insert(hint1.display, hint1);
+ ScriptCodeCompletionOption hint2("inout", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ hint2.insert_text = p_quote_style + hint2.display + p_quote_style;
+ r_result.insert(hint2.display, hint2);
+ }
+ } else if (p_annotation->name == "@export_node_path") {
+ ScriptCodeCompletionOption node("Node", ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(node.display, node);
+ List<StringName> node_types;
+ ClassDB::get_inheriters_from_class("Node", &node_types);
+ for (const List<StringName>::Element *E = node_types.front(); E != nullptr; E = E->next()) {
+ if (!ClassDB::is_class_exposed(E->get())) {
+ continue;
+ }
+ ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
+ }
+ }
+}
+
+static void _list_available_types(bool p_inherit_only, GDScriptParser::CompletionContext &p_context, Map<String, ScriptCodeCompletionOption> &r_result) {
+ List<StringName> native_types;
+ ClassDB::get_class_list(&native_types);
+ for (const List<StringName>::Element *E = native_types.front(); E != nullptr; E = E->next()) {
+ if (ClassDB::is_class_exposed(E->get()) && !Engine::get_singleton()->has_singleton(E->get())) {
+ ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
+ }
+ }
+
+ if (p_context.current_class) {
+ if (!p_inherit_only && p_context.current_class->base_type.is_set()) {
+ // Native enums from base class
+ List<StringName> enums;
+ ClassDB::get_enum_list(p_context.current_class->base_type.native_type, &enums);
+ for (const List<StringName>::Element *E = enums.front(); E != nullptr; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_ENUM);
+ r_result.insert(option.display, option);
+ }
+ }
+ // Check current class for potential types
+ const GDScriptParser::ClassNode *current = p_context.current_class;
+ while (current) {
+ for (int i = 0; i < current->members.size(); i++) {
+ const GDScriptParser::ClassNode::Member &member = current->members[i];
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::CLASS: {
+ ScriptCodeCompletionOption option(member.m_class->identifier->name, ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
+ } break;
+ case GDScriptParser::ClassNode::Member::ENUM: {
+ if (!p_inherit_only) {
+ ScriptCodeCompletionOption option(member.m_enum->identifier->name, ScriptCodeCompletionOption::KIND_ENUM);
+ r_result.insert(option.display, option);
+ }
+ } break;
+ case GDScriptParser::ClassNode::Member::CONSTANT: {
+ if (member.constant->get_datatype().is_meta_type && p_context.current_class->outer != nullptr) {
+ ScriptCodeCompletionOption option(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+ current = current->outer;
+ }
+ }
+
+ // Global scripts
+ List<StringName> global_classes;
+ ScriptServer::get_global_class_list(&global_classes);
+ for (const List<StringName>::Element *E = global_classes.front(); E != nullptr; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
+ }
+
+ // Autoload singletons
+ Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) {
+ const ProjectSettings::AutoloadInfo &info = E->get();
+ if (!info.is_singleton || info.path.get_extension().to_lower() != "gd") {
+ continue;
+ }
+ ScriptCodeCompletionOption option(info.name, ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
+ }
+}
+
+static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, Map<String, ScriptCodeCompletionOption> &r_result) {
+ for (int i = 0; i < p_suite->locals.size(); i++) {
+ ScriptCodeCompletionOption option(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_VARIABLE);
+ r_result.insert(option.display, option);
+ }
+ if (p_suite->parent_block) {
+ _find_identifiers_in_suite(p_suite->parent_block, r_result);
+ }
+}
+
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result);
+
+static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) {
+ if (!p_parent_only) {
+ bool outer = false;
+ const GDScriptParser::ClassNode *clss = p_class;
+ while (clss) {
+ for (int i = 0; i < clss->members.size(); i++) {
+ const GDScriptParser::ClassNode::Member &member = clss->members[i];
+ ScriptCodeCompletionOption option;
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::VARIABLE:
+ if (p_only_functions || outer || (p_static)) {
+ continue;
+ }
+ option = ScriptCodeCompletionOption(member.variable->identifier->name, ScriptCodeCompletionOption::KIND_MEMBER);
+ break;
+ case GDScriptParser::ClassNode::Member::CONSTANT:
+ if (p_only_functions) {
+ continue;
+ }
+ option = ScriptCodeCompletionOption(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT);
+ break;
+ case GDScriptParser::ClassNode::Member::CLASS:
+ if (p_only_functions) {
+ continue;
+ }
+ option = ScriptCodeCompletionOption(member.m_class->identifier->name, ScriptCodeCompletionOption::KIND_CLASS);
+ break;
+ case GDScriptParser::ClassNode::Member::ENUM_VALUE:
+ if (p_only_functions) {
+ continue;
+ }
+ option = ScriptCodeCompletionOption(member.enum_value.identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT);
+ break;
+ case GDScriptParser::ClassNode::Member::ENUM:
+ if (p_only_functions) {
+ continue;
+ }
+ option = ScriptCodeCompletionOption(member.m_enum->identifier->name, ScriptCodeCompletionOption::KIND_ENUM);
+ break;
+ case GDScriptParser::ClassNode::Member::FUNCTION:
+ if (outer || (p_static && !member.function->is_static) || member.function->identifier->name.operator String().begins_with("@")) {
+ continue;
+ }
+ option = ScriptCodeCompletionOption(member.function->identifier->name, ScriptCodeCompletionOption::KIND_FUNCTION);
+ if (member.function->parameters.size() > 0) {
+ option.insert_text += "(";
+ } else {
+ option.insert_text += "()";
+ }
+ break;
+ case GDScriptParser::ClassNode::Member::SIGNAL:
+ if (p_only_functions || outer) {
+ clss = clss->outer;
+ continue;
+ }
+ option = ScriptCodeCompletionOption(member.signal->identifier->name, ScriptCodeCompletionOption::KIND_SIGNAL);
+ break;
+ case GDScriptParser::ClassNode::Member::UNDEFINED:
+ break;
+ }
+ r_result.insert(option.display, option);
+ }
+ outer = true;
+ clss = clss->outer;
+ }
+ }
+
+ // Parents.
+ GDScriptCompletionIdentifier base_type;
+ base_type.type = p_class->base_type;
+ base_type.type.is_meta_type = p_static;
+
+ _find_identifiers_in_base(base_type, p_only_functions, r_result);
+}
+
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
+ GDScriptParser::DataType base_type = p_base.type;
+ bool _static = base_type.is_meta_type;
+
+ if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) {
+ ScriptCodeCompletionOption option("new", ScriptCodeCompletionOption::KIND_FUNCTION);
+ option.insert_text += "(";
+ r_result.insert(option.display, option);
+ }
+
+ while (!base_type.has_no_type()) {
+ switch (base_type.kind) {
+ case GDScriptParser::DataType::CLASS: {
+ _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result);
+ // This already finds all parent identifiers, so we are done.
+ base_type = GDScriptParser::DataType();
+ } break;
+ case GDScriptParser::DataType::SCRIPT: {
+ Ref<Script> scr = base_type.script_type;
+ if (scr.is_valid()) {
+ if (!p_only_functions) {
+ if (!_static) {
+ List<PropertyInfo> members;
+ scr->get_script_property_list(&members);
+ for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
+ r_result.insert(option.display, option);
+ }
+ }
+ Map<StringName, Variant> constants;
+ scr->get_constants(&constants);
+ for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) {
+ ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT);
+ r_result.insert(option.display, option);
+ }
+
+ List<MethodInfo> signals;
+ scr->get_script_signal_list(&signals);
+ for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL);
+ r_result.insert(option.display, option);
+ }
+ }
+
+ List<MethodInfo> methods;
+ scr->get_script_method_list(&methods);
+ for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ if (E->get().name.begins_with("@")) {
+ continue;
+ }
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
+ if (E->get().arguments.size()) {
+ option.insert_text += "(";
+ } else {
+ option.insert_text += "()";
+ }
+ r_result.insert(option.display, option);
+ }
+
+ Ref<Script> base_script = scr->get_base_script();
+ if (base_script.is_valid()) {
+ base_type.script_type = base_script;
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.native_type = scr->get_instance_base_type();
+ }
+ } else {
+ return;
+ }
+ } break;
+ case GDScriptParser::DataType::NATIVE: {
+ StringName type = _get_real_class_name(base_type.native_type);
+ if (!ClassDB::class_exists(type)) {
+ return;
+ }
+
+ if (!p_only_functions) {
+ List<String> constants;
+ ClassDB::get_integer_constant_list(type, &constants);
+ for (List<String>::Element *E = constants.front(); E; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT);
+ r_result.insert(option.display, option);
+ }
+
+ if (!_static || Engine::get_singleton()->has_singleton(type)) {
+ List<PropertyInfo> pinfo;
+ ClassDB::get_property_list(type, &pinfo);
+ for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
+ if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) {
+ continue;
+ }
+ if (E->get().name.find("/") != -1) {
+ continue;
+ }
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
+ r_result.insert(option.display, option);
+ }
+ }
+ }
+
+ if (!_static || Engine::get_singleton()->has_singleton(type)) {
+ List<MethodInfo> methods;
+ bool is_autocompleting_getters = GLOBAL_GET("debug/gdscript/completion/autocomplete_setters_and_getters").booleanize();
+ ClassDB::get_method_list(type, &methods, false, !is_autocompleting_getters);
+ for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ if (E->get().name.begins_with("_")) {
+ continue;
+ }
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
+ if (E->get().arguments.size()) {
+ option.insert_text += "(";
+ } else {
+ option.insert_text += "()";
+ }
+ r_result.insert(option.display, option);
+ }
+ }
+
+ return;
+ } break;
+ case GDScriptParser::DataType::BUILTIN: {
+ Callable::CallError err;
+ Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
+ if (err.error != Callable::CallError::CALL_OK) {
+ return;
+ }
+
+ if (!p_only_functions) {
+ List<PropertyInfo> members;
+ if (p_base.value.get_type() != Variant::NIL) {
+ p_base.value.get_property_list(&members);
+ } else {
+ tmp.get_property_list(&members);
+ }
+
+ for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
+ if (String(E->get().name).find("/") == -1) {
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
+ r_result.insert(option.display, option);
+ }
+ }
+ }
+
+ List<MethodInfo> methods;
+ tmp.get_method_list(&methods);
+ for (const List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
+ if (E->get().arguments.size()) {
+ option.insert_text += "(";
+ } else {
+ option.insert_text += "()";
+ }
+ r_result.insert(option.display, option);
+ }
+
+ return;
+ } break;
+ default: {
+ return;
+ } break;
+ }
+ }
+}
+
+static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
+ if (!p_only_functions && p_context.current_suite) {
+ // This includes function parameters, since they are also locals.
+ _find_identifiers_in_suite(p_context.current_suite, r_result);
+ }
+
+ if (p_context.current_class) {
+ _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result);
+ }
+
+ for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
+ MethodInfo function = GDScriptFunctions::get_info(GDScriptFunctions::Function(i));
+ ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION);
+ if (function.arguments.size() || (function.flags & METHOD_FLAG_VARARG)) {
+ option.insert_text += "(";
+ } else {
+ option.insert_text += "()";
+ }
+ r_result.insert(option.display, option);
+ }
+
+ if (p_only_functions) {
+ return;
+ }
+
+ static const char *_type_names[Variant::VARIANT_MAX] = {
+ "null", "bool", "int", "float", "String", "StringName", "Vector2", "Vector2i", "Rect2", "Rect2i", "Vector3", "Vector3i", "Transform2D", "Plane", "Quat", "AABB", "Basis", "Transform",
+ "Color", "NodePath", "RID", "Signal", "Callable", "Object", "Dictionary", "Array", "PackedByteArray", "PackedInt32Array", "PackedInt64Array", "PackedFloat32Array", "PackedFloat64Array", "PackedStringArray",
+ "PackedVector2Array", "PackedVector3Array", "PackedColorArray"
+ };
+ static_assert((sizeof(_type_names) / sizeof(*_type_names)) == Variant::VARIANT_MAX, "Completion for builtin types is incomplete");
+
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+ ScriptCodeCompletionOption option(_type_names[i], ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
+ }
+
+ static const char *_keywords[] = {
+ "and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert",
+ "breakpoint", "class", "extends", "is", "func", "preload", "signal", "tool", "await",
+ "const", "enum", "static", "super", "var", "break", "continue", "if", "elif",
+ "else", "for", "pass", "return", "match", "while",
+ 0
+ };
+
+ const char **kw = _keywords;
+ while (*kw) {
+ ScriptCodeCompletionOption option(*kw, ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ r_result.insert(option.display, option);
+ kw++;
+ }
+
+ Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) {
+ if (!E->value().is_singleton) {
+ continue;
+ }
+ ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT);
+ r_result.insert(option.display, option);
+ }
+
+ // Native classes and global constants.
+ for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) {
+ ScriptCodeCompletionOption option;
+ if (ClassDB::class_exists(E->key()) || Engine::get_singleton()->has_singleton(E->key())) {
+ option = ScriptCodeCompletionOption(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
+ } else {
+ option = ScriptCodeCompletionOption(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT);
+ }
+ r_result.insert(option.display, option);
+ }
+}
+
static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) {
GDScriptCompletionIdentifier ci;
ci.value = p_value;
ci.type.is_constant = true;
- ci.type.has_type = true;
+ ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
ci.type.kind = GDScriptParser::DataType::BUILTIN;
ci.type.builtin_type = p_value.get_type();
@@ -560,12 +1103,7 @@ static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) {
}
if (scr.is_valid()) {
ci.type.script_type = scr;
- Ref<GDScript> gds = scr;
- if (gds.is_valid()) {
- ci.type.kind = GDScriptParser::DataType::GDSCRIPT;
- } else {
- ci.type.kind = GDScriptParser::DataType::SCRIPT;
- }
+ ci.type.kind = GDScriptParser::DataType::SCRIPT;
ci.type.native_type = scr->get_instance_base_type();
} else {
ci.type.kind = GDScriptParser::DataType::NATIVE;
@@ -587,7 +1125,7 @@ static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_pr
ci.enumeration = p_property.class_name;
}
- ci.type.has_type = true;
+ ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
ci.type.builtin_type = p_property.type;
if (p_property.type == Variant::OBJECT) {
ci.type.kind = GDScriptParser::DataType::NATIVE;
@@ -598,344 +1136,290 @@ static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_pr
return ci;
}
-static GDScriptCompletionIdentifier _type_from_gdtype(const GDScriptDataType &p_gdtype) {
- GDScriptCompletionIdentifier ci;
- if (!p_gdtype.has_type) {
- return ci;
- }
+static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type);
+static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type);
+static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type);
- ci.type.has_type = true;
- ci.type.builtin_type = p_gdtype.builtin_type;
- ci.type.native_type = p_gdtype.native_type;
- ci.type.script_type = p_gdtype.script_type;
-
- switch (p_gdtype.kind) {
- case GDScriptDataType::UNINITIALIZED: {
- ERR_PRINT("Uninitialized completion. Please report a bug.");
- } break;
- case GDScriptDataType::BUILTIN: {
- ci.type.kind = GDScriptParser::DataType::BUILTIN;
- } break;
- case GDScriptDataType::NATIVE: {
- ci.type.kind = GDScriptParser::DataType::NATIVE;
- } break;
- case GDScriptDataType::GDSCRIPT: {
- ci.type.kind = GDScriptParser::DataType::GDSCRIPT;
- } break;
- case GDScriptDataType::SCRIPT: {
- ci.type.kind = GDScriptParser::DataType::SCRIPT;
- } break;
- }
- return ci;
-}
-
-static bool _guess_identifier_type(GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type);
-static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type);
-static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type);
-
-static bool _guess_expression_type(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_expression, GDScriptCompletionIdentifier &r_type) {
+static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::ExpressionNode *p_expression, GDScriptCompletionIdentifier &r_type) {
bool found = false;
- if (++p_context.depth > 100) {
- print_error("Maximum _guess_expression_type depth limit reached. Please file a bugreport.");
- return false;
- }
-
- switch (p_expression->type) {
- case GDScriptParser::Node::TYPE_CONSTANT: {
- const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_expression);
- r_type = _type_from_variant(cn->value);
- found = true;
- } break;
- case GDScriptParser::Node::TYPE_SELF: {
- if (p_context._class) {
- r_type.type.has_type = true;
- r_type.type.kind = GDScriptParser::DataType::CLASS;
- r_type.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
- r_type.type.is_constant = true;
- r_type.value = p_context.base;
+ if (p_expression->is_constant) {
+ // Already has a value, so just use that.
+ r_type = _type_from_variant(p_expression->reduced_value);
+ found = true;
+ } else {
+ switch (p_expression->type) {
+ case GDScriptParser::Node::LITERAL: {
+ const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(p_expression);
+ r_type = _type_from_variant(literal->value);
found = true;
- }
- } break;
- case GDScriptParser::Node::TYPE_IDENTIFIER: {
- const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression);
- found = _guess_identifier_type(p_context, id->name, r_type);
- } break;
- case GDScriptParser::Node::TYPE_DICTIONARY: {
- // Try to recreate the dictionary
- const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression);
- Dictionary d;
- bool full = true;
- for (int i = 0; i < dn->elements.size(); i++) {
- GDScriptCompletionIdentifier key;
- if (_guess_expression_type(p_context, dn->elements[i].key, key)) {
- GDScriptCompletionIdentifier value;
- if (_guess_expression_type(p_context, dn->elements[i].value, value)) {
- if (!value.type.is_constant) {
+ } break;
+ case GDScriptParser::Node::SELF: {
+ if (p_context.current_class) {
+ r_type.type.kind = GDScriptParser::DataType::CLASS;
+ r_type.type.type_source = GDScriptParser::DataType::INFERRED;
+ r_type.type.is_constant = true;
+ r_type.type.class_type = p_context.current_class;
+ r_type.value = p_context.base;
+ found = true;
+ }
+ } break;
+ case GDScriptParser::Node::IDENTIFIER: {
+ const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression);
+ found = _guess_identifier_type(p_context, id->name, r_type);
+ } break;
+ case GDScriptParser::Node::DICTIONARY: {
+ // Try to recreate the dictionary.
+ const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression);
+ Dictionary d;
+ bool full = true;
+ for (int i = 0; i < dn->elements.size(); i++) {
+ GDScriptCompletionIdentifier key;
+ if (_guess_expression_type(p_context, dn->elements[i].key, key)) {
+ if (!key.type.is_constant) {
+ full = false;
+ break;
+ }
+ GDScriptCompletionIdentifier value;
+ if (_guess_expression_type(p_context, dn->elements[i].value, value)) {
+ if (!value.type.is_constant) {
+ full = false;
+ break;
+ }
+ d[key.value] = value.value;
+ } else {
full = false;
break;
}
- d[key.value] = value.value;
} else {
full = false;
break;
}
- } else {
- full = false;
- break;
}
- }
- if (full) {
- // If not fully constant, setting this value is detrimental to the inference
- r_type.value = d;
- r_type.type.is_constant = true;
- }
- r_type.type.has_type = true;
- r_type.type.kind = GDScriptParser::DataType::BUILTIN;
- r_type.type.builtin_type = Variant::DICTIONARY;
- } break;
- case GDScriptParser::Node::TYPE_ARRAY: {
- // Try to recreate the array
- const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression);
- Array a;
- bool full = true;
- a.resize(an->elements.size());
- for (int i = 0; i < an->elements.size(); i++) {
- GDScriptCompletionIdentifier value;
- if (_guess_expression_type(p_context, an->elements[i], value)) {
- a[i] = value.value;
- } else {
- full = false;
- break;
+ if (full) {
+ r_type.value = d;
+ r_type.type.is_constant = true;
}
- }
- if (full) {
- // If not fully constant, setting this value is detrimental to the inference
- r_type.value = a;
- }
- r_type.type.has_type = true;
- r_type.type.kind = GDScriptParser::DataType::BUILTIN;
- r_type.type.builtin_type = Variant::ARRAY;
- } break;
- case GDScriptParser::Node::TYPE_CAST: {
- const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
- GDScriptCompletionIdentifier value;
- if (_guess_expression_type(p_context, cn->source_node, r_type)) {
- r_type.type = cn->get_datatype();
+ r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ r_type.type.kind = GDScriptParser::DataType::BUILTIN;
+ r_type.type.builtin_type = Variant::DICTIONARY;
found = true;
- }
- } break;
- case GDScriptParser::Node::TYPE_OPERATOR: {
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_expression);
- switch (op->op) {
- case GDScriptParser::OperatorNode::OP_CALL: {
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) {
- const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]);
- r_type.type.has_type = true;
- r_type.type.kind = GDScriptParser::DataType::BUILTIN;
- r_type.type.builtin_type = tn->vtype;
- found = true;
- break;
- } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
- const GDScriptParser::BuiltInFunctionNode *bin = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]);
- MethodInfo mi = GDScriptFunctions::get_info(bin->function);
- r_type = _type_from_property(mi.return_val);
- found = true;
+ } break;
+ case GDScriptParser::Node::ARRAY: {
+ // Try to recreate the array
+ const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression);
+ Array a;
+ bool full = true;
+ a.resize(an->elements.size());
+ for (int i = 0; i < an->elements.size(); i++) {
+ GDScriptCompletionIdentifier value;
+ if (_guess_expression_type(p_context, an->elements[i], value)) {
+ if (value.type.is_constant) {
+ a[i] = value.value;
+ } else {
+ full = false;
+ break;
+ }
+ } else {
+ full = false;
break;
- } else if (op->arguments.size() >= 2 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
- StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name;
-
- GDScriptCompletionContext c = p_context;
- c.line = op->line;
+ }
+ }
+ if (full) {
+ // If not fully constant, setting this value is detrimental to the inference.
+ r_type.value = a;
+ }
+ r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ r_type.type.kind = GDScriptParser::DataType::BUILTIN;
+ r_type.type.builtin_type = Variant::ARRAY;
+ found = true;
+ } break;
+ case GDScriptParser::Node::CAST: {
+ const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
+ GDScriptCompletionIdentifier value;
+ if (_guess_expression_type(p_context, cn->operand, r_type)) {
+ r_type.type = cn->get_datatype();
+ found = true;
+ }
+ } break;
+ case GDScriptParser::Node::CALL: {
+ const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression);
+ if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) {
+ r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ r_type.type.kind = GDScriptParser::DataType::BUILTIN;
+ r_type.type.builtin_type = GDScriptParser::get_builtin_type(call->function_name);
+ found = true;
+ break;
+ } else if (GDScriptParser::get_builtin_function(call->function_name) < GDScriptFunctions::FUNC_MAX) {
+ MethodInfo mi = GDScriptFunctions::get_info(GDScriptParser::get_builtin_function(call->function_name));
+ r_type = _type_from_property(mi.return_val);
+ found = true;
+ break;
+ } else {
+ GDScriptParser::CompletionContext c = p_context;
+ c.current_line = call->start_line;
- GDScriptCompletionIdentifier base;
- if (!_guess_expression_type(c, op->arguments[0], base)) {
+ GDScriptCompletionIdentifier base;
+ if (call->callee->type == GDScriptParser::Node::IDENTIFIER || call->is_super) {
+ // Simple call, so base is 'self'.
+ if (p_context.current_class) {
+ base.type.kind = GDScriptParser::DataType::CLASS;
+ base.type.type_source = GDScriptParser::DataType::INFERRED;
+ base.type.is_constant = true;
+ base.type.class_type = p_context.current_class;
+ base.value = p_context.base;
+ } else {
+ break;
+ }
+ } else if (call->callee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) {
+ if (!_guess_expression_type(c, static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->base, base)) {
found = false;
break;
}
+ } else {
+ break;
+ }
- // Try call if constant methods with constant arguments
- if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) {
- GDScriptParser::DataType native_type = base.type;
+ // Try call if constant methods with constant arguments
+ if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) {
+ GDScriptParser::DataType native_type = base.type;
- while (native_type.kind == GDScriptParser::DataType::CLASS) {
- native_type = native_type.class_type->base_type;
- }
+ while (native_type.kind == GDScriptParser::DataType::CLASS) {
+ native_type = native_type.class_type->base_type;
+ }
- while (native_type.kind == GDScriptParser::DataType::GDSCRIPT || native_type.kind == GDScriptParser::DataType::SCRIPT) {
- if (native_type.script_type.is_valid()) {
- Ref<Script> parent = native_type.script_type->get_base_script();
- if (parent.is_valid()) {
- native_type.script_type = parent;
- } else {
- native_type.kind = GDScriptParser::DataType::NATIVE;
- native_type.native_type = native_type.script_type->get_instance_base_type();
+ while (native_type.kind == GDScriptParser::DataType::SCRIPT) {
+ if (native_type.script_type.is_valid()) {
+ Ref<Script> parent = native_type.script_type->get_base_script();
+ if (parent.is_valid()) {
+ native_type.script_type = parent;
+ } else {
+ native_type.kind = GDScriptParser::DataType::NATIVE;
+ native_type.native_type = native_type.script_type->get_instance_base_type();
+ if (!ClassDB::class_exists(native_type.native_type)) {
+ native_type.native_type = String("_") + native_type.native_type;
if (!ClassDB::class_exists(native_type.native_type)) {
- native_type.native_type = String("_") + native_type.native_type;
- if (!ClassDB::class_exists(native_type.native_type)) {
- native_type.has_type = false;
- }
+ native_type.kind = GDScriptParser::DataType::UNRESOLVED;
}
}
}
}
+ }
- if (native_type.has_type && native_type.kind == GDScriptParser::DataType::NATIVE) {
- MethodBind *mb = ClassDB::get_method(native_type.native_type, id);
- if (mb && mb->is_const()) {
- bool all_is_const = true;
- Vector<Variant> args;
- GDScriptCompletionContext c2 = p_context;
- c2.line = op->line;
- for (int i = 2; all_is_const && i < op->arguments.size(); i++) {
- GDScriptCompletionIdentifier arg;
-
- if (_guess_expression_type(c2, op->arguments[i], arg)) {
- if (arg.type.has_type && arg.type.is_constant && arg.value.get_type() != Variant::OBJECT) {
- args.push_back(arg.value);
- } else {
- all_is_const = false;
- }
- } else {
- all_is_const = false;
- }
+ if (native_type.kind == GDScriptParser::DataType::NATIVE) {
+ MethodBind *mb = ClassDB::get_method(native_type.native_type, call->function_name);
+ if (mb && mb->is_const()) {
+ bool all_is_const = true;
+ Vector<Variant> args;
+ GDScriptParser::CompletionContext c2 = p_context;
+ c2.current_line = call->start_line;
+ for (int i = 0; all_is_const && i < call->arguments.size(); i++) {
+ GDScriptCompletionIdentifier arg;
+
+ if (!call->arguments[i]->is_constant) {
+ all_is_const = false;
}
+ }
- Object *baseptr = base.value;
-
- if (all_is_const && String(id) == "get_node" && ClassDB::is_parent_class(native_type.native_type, "Node") && args.size()) {
- String arg1 = args[0];
- if (arg1.begins_with("/root/")) {
- String which = arg1.get_slice("/", 2);
- if (which != "") {
- // Try singletons first
- if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) {
- r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]);
- found = true;
- } else {
- List<PropertyInfo> props;
- ProjectSettings::get_singleton()->get_property_list(&props);
-
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- String s = E->get().name;
- if (!s.begins_with("autoload/")) {
- continue;
- }
- String name = s.get_slice("/", 1);
- if (name == which) {
- String script = ProjectSettings::get_singleton()->get(s);
+ Object *baseptr = base.value;
- if (script.begins_with("*")) {
- script = script.right(1);
- }
+ if (all_is_const && String(call->function_name) == "get_node" && ClassDB::is_parent_class(native_type.native_type, "Node") && args.size()) {
+ String arg1 = args[0];
+ if (arg1.begins_with("/root/")) {
+ String which = arg1.get_slice("/", 2);
+ if (which != "") {
+ // Try singletons first
+ if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) {
+ r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]);
+ found = true;
+ } else {
+ Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
- if (!script.begins_with("res://")) {
- script = "res://" + script;
- }
+ for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
+ String name = E->key();
+ if (name == which) {
+ String script = E->value().path;
- if (!script.ends_with(".gd")) {
- //not a script, try find the script anyway,
- //may have some success
- script = script.get_basename() + ".gd";
- }
+ if (!script.begins_with("res://")) {
+ script = "res://" + script;
+ }
- if (FileAccess::exists(script)) {
- Ref<Script> scr;
- if (ScriptCodeCompletionCache::get_singleton()) {
- scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script);
- } else {
- scr = ResourceLoader::load(script);
- }
- if (scr.is_valid()) {
- r_type.type.has_type = true;
- r_type.type.script_type = scr;
- r_type.type.is_constant = false;
- Ref<GDScript> gds = scr;
- if (gds.is_valid()) {
- r_type.type.kind = GDScriptParser::DataType::GDSCRIPT;
- } else {
- r_type.type.kind = GDScriptParser::DataType::SCRIPT;
- }
- r_type.value = Variant();
- found = true;
- }
+ if (!script.ends_with(".gd")) {
+ //not a script, try find the script anyway,
+ //may have some success
+ script = script.get_basename() + ".gd";
+ }
+
+ if (FileAccess::exists(script)) {
+ Error err = OK;
+ Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err);
+ if (err == OK) {
+ r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ r_type.type.script_path = script;
+ r_type.type.class_type = parser->get_parser()->get_tree();
+ r_type.type.is_constant = false;
+ r_type.type.kind = GDScriptParser::DataType::CLASS;
+ r_type.value = Variant();
+ p_context.dependent_parsers.push_back(parser);
+ found = true;
}
- break;
}
+ break;
}
}
}
}
}
+ }
- if (!found && all_is_const && baseptr) {
- Vector<const Variant *> argptr;
- for (int i = 0; i < args.size(); i++) {
- argptr.push_back(&args[i]);
- }
+ if (!found && all_is_const && baseptr) {
+ Vector<const Variant *> argptr;
+ for (int i = 0; i < args.size(); i++) {
+ argptr.push_back(&args[i]);
+ }
- Callable::CallError ce;
- Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce);
+ Callable::CallError ce;
+ Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce);
- if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
- if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) {
- r_type = _type_from_variant(ret);
- found = true;
- }
+ if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
+ if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) {
+ r_type = _type_from_variant(ret);
+ found = true;
}
}
}
}
}
-
- if (!found) {
- found = _guess_method_return_type_from_base(c, base, id, r_type);
- }
- }
- } break;
- case GDScriptParser::OperatorNode::OP_PARENT_CALL: {
- if (!p_context._class || !op->arguments.size() || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
- break;
}
- StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name;
-
- GDScriptCompletionIdentifier base;
- base.value = p_context.base;
- base.type = p_context._class->base_type;
-
- GDScriptCompletionContext c = p_context;
- c.line = op->line;
-
- found = _guess_method_return_type_from_base(c, base, id, r_type);
- } break;
- case GDScriptParser::OperatorNode::OP_INDEX_NAMED: {
- if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
- found = false;
- break;
+ if (!found) {
+ found = _guess_method_return_type_from_base(c, base, call->function_name, r_type);
}
- const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]);
-
- GDScriptCompletionContext c = p_context;
- c.line = op->line;
+ }
+ } break;
+ case GDScriptParser::Node::SUBSCRIPT: {
+ const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression);
+ if (subscript->is_attribute) {
+ GDScriptParser::CompletionContext c = p_context;
+ c.current_line = subscript->start_line;
GDScriptCompletionIdentifier base;
- if (!_guess_expression_type(c, op->arguments[0], base)) {
+ if (!_guess_expression_type(c, subscript->base, base)) {
found = false;
break;
}
- if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(id->name))) {
- Variant value = base.value.operator Dictionary()[String(id->name)];
+ if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(subscript->attribute->name))) {
+ Variant value = base.value.operator Dictionary()[String(subscript->attribute->name)];
r_type = _type_from_variant(value);
found = true;
break;
}
const GDScriptParser::DictionaryNode *dn = nullptr;
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) {
- dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]);
- } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) {
+ if (subscript->base->type == GDScriptParser::Node::DICTIONARY) {
+ dn = static_cast<const GDScriptParser::DictionaryNode *>(subscript->base);
+ } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::DICTIONARY) {
dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression);
}
@@ -945,7 +1429,7 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G
if (!_guess_expression_type(c, dn->elements[i].key, key)) {
continue;
}
- if (key.value == String(id->name)) {
+ if (key.value == String(subscript->attribute->name)) {
r_type.assigned_expression = dn->elements[i].value;
found = _guess_expression_type(c, dn->elements[i].value, r_type);
break;
@@ -954,26 +1438,25 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G
}
if (!found) {
- found = _guess_identifier_type_from_base(c, base, id->name, r_type);
+ found = _guess_identifier_type_from_base(c, base, subscript->attribute->name, r_type);
}
- } break;
- case GDScriptParser::OperatorNode::OP_INDEX: {
- if (op->arguments.size() < 2) {
+ } else {
+ if (subscript->index == nullptr) {
found = false;
break;
}
- GDScriptCompletionContext c = p_context;
- c.line = op->line;
+ GDScriptParser::CompletionContext c = p_context;
+ c.current_line = subscript->start_line;
GDScriptCompletionIdentifier base;
- if (!_guess_expression_type(c, op->arguments[0], base)) {
+ if (!_guess_expression_type(c, subscript->base, base)) {
found = false;
break;
}
GDScriptCompletionIdentifier index;
- if (!_guess_expression_type(c, op->arguments[1], index)) {
+ if (!_guess_expression_type(c, subscript->index, index)) {
found = false;
break;
}
@@ -985,11 +1468,11 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G
break;
}
- // Look if it is a dictionary node
+ // Look if it is a dictionary node.
const GDScriptParser::DictionaryNode *dn = nullptr;
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) {
- dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]);
- } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) {
+ if (subscript->base->type == GDScriptParser::Node::DICTIONARY) {
+ dn = static_cast<const GDScriptParser::DictionaryNode *>(subscript->base);
+ } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::DICTIONARY) {
dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression);
}
@@ -1007,13 +1490,13 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G
}
}
- // Look if it is an array node
+ // Look if it is an array node.
if (!found && index.value.is_num()) {
int idx = index.value;
const GDScriptParser::ArrayNode *an = nullptr;
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_ARRAY) {
- an = static_cast<const GDScriptParser::ArrayNode *>(op->arguments[0]);
- } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_ARRAY) {
+ if (subscript->base->type == GDScriptParser::Node::ARRAY) {
+ an = static_cast<const GDScriptParser::ArrayNode *>(subscript->base);
+ } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::ARRAY) {
an = static_cast<const GDScriptParser::ArrayNode *>(base.assigned_expression);
}
@@ -1040,113 +1523,74 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G
found = true;
}
}
- } break;
- default: {
- if (op->arguments.size() < 2) {
- found = false;
- break;
- }
-
- Variant::Operator vop = Variant::OP_MAX;
- switch (op->op) {
- case GDScriptParser::OperatorNode::OP_ADD:
- vop = Variant::OP_ADD;
- break;
- case GDScriptParser::OperatorNode::OP_SUB:
- vop = Variant::OP_SUBTRACT;
- break;
- case GDScriptParser::OperatorNode::OP_MUL:
- vop = Variant::OP_MULTIPLY;
- break;
- case GDScriptParser::OperatorNode::OP_DIV:
- vop = Variant::OP_DIVIDE;
- break;
- case GDScriptParser::OperatorNode::OP_MOD:
- vop = Variant::OP_MODULE;
- break;
- case GDScriptParser::OperatorNode::OP_SHIFT_LEFT:
- vop = Variant::OP_SHIFT_LEFT;
- break;
- case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT:
- vop = Variant::OP_SHIFT_RIGHT;
- break;
- case GDScriptParser::OperatorNode::OP_BIT_AND:
- vop = Variant::OP_BIT_AND;
- break;
- case GDScriptParser::OperatorNode::OP_BIT_OR:
- vop = Variant::OP_BIT_OR;
- break;
- case GDScriptParser::OperatorNode::OP_BIT_XOR:
- vop = Variant::OP_BIT_XOR;
- break;
- default: {
- }
- }
+ }
+ } break;
+ case GDScriptParser::Node::BINARY_OPERATOR: {
+ const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression);
- if (vop == Variant::OP_MAX) {
- break;
- }
+ if (op->variant_op == Variant::OP_MAX) {
+ break;
+ }
- GDScriptCompletionContext context = p_context;
- context.line = op->line;
+ GDScriptParser::CompletionContext context = p_context;
+ context.current_line = op->start_line;
- GDScriptCompletionIdentifier p1;
- GDScriptCompletionIdentifier p2;
+ GDScriptCompletionIdentifier p1;
+ GDScriptCompletionIdentifier p2;
- if (!_guess_expression_type(context, op->arguments[0], p1)) {
- found = false;
- break;
- }
+ if (!_guess_expression_type(context, op->left_operand, p1)) {
+ found = false;
+ break;
+ }
- if (!_guess_expression_type(context, op->arguments[1], p2)) {
- found = false;
- break;
- }
+ if (!_guess_expression_type(context, op->right_operand, p2)) {
+ found = false;
+ break;
+ }
- Callable::CallError ce;
- bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT;
- Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type.builtin_type, nullptr, 0, ce);
- bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT;
- Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type.builtin_type, nullptr, 0, ce);
- // avoid potential invalid ops
- if ((vop == Variant::OP_DIVIDE || vop == Variant::OP_MODULE) && v2.get_type() == Variant::INT) {
- v2 = 1;
- v2_use_value = false;
- }
- if (vop == Variant::OP_DIVIDE && v2.get_type() == Variant::FLOAT) {
- v2 = 1.0;
- v2_use_value = false;
- }
+ Callable::CallError ce;
+ bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT;
+ Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type.builtin_type, nullptr, 0, ce);
+ bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT;
+ Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type.builtin_type, nullptr, 0, ce);
+ // avoid potential invalid ops
+ if ((op->variant_op == Variant::OP_DIVIDE || op->variant_op == Variant::OP_MODULE) && v2.get_type() == Variant::INT) {
+ v2 = 1;
+ v2_use_value = false;
+ }
+ if (op->variant_op == Variant::OP_DIVIDE && v2.get_type() == Variant::FLOAT) {
+ v2 = 1.0;
+ v2_use_value = false;
+ }
- Variant res;
- bool valid;
- Variant::evaluate(vop, v1, v2, res, valid);
- if (!valid) {
- found = false;
- break;
- }
- r_type = _type_from_variant(res);
- if (!v1_use_value || !v2_use_value) {
- r_type.value = Variant();
- r_type.type.is_constant = false;
- }
+ Variant res;
+ bool valid;
+ Variant::evaluate(op->variant_op, v1, v2, res, valid);
+ if (!valid) {
+ found = false;
+ break;
+ }
+ r_type = _type_from_variant(res);
+ if (!v1_use_value || !v2_use_value) {
+ r_type.value = Variant();
+ r_type.type.is_constant = false;
+ }
- found = true;
- } break;
- }
- } break;
- default: {
+ found = true;
+ } break;
+ default:
+ break;
}
}
// It may have found a null, but that's never useful
- if (found && r_type.type.has_type && r_type.type.kind == GDScriptParser::DataType::BUILTIN && r_type.type.builtin_type == Variant::NIL) {
+ if (found && r_type.type.kind == GDScriptParser::DataType::BUILTIN && r_type.type.builtin_type == Variant::NIL) {
found = false;
}
// Check type hint last. For collections we want chance to get the actual value first
// This way we can detect types from the content of dictionaries and arrays
- if (!found && p_expression->get_datatype().has_type) {
+ if (!found && p_expression->get_datatype().is_hard_type()) {
r_type.type = p_expression->get_datatype();
if (!r_type.assigned_expression) {
r_type.assigned_expression = p_expression;
@@ -1157,306 +1601,289 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G
return found;
}
-static bool _guess_identifier_type(GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) {
- // Look in blocks first
- const GDScriptParser::BlockNode *blk = p_context.block;
+static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) {
+ // Look in blocks first.
int last_assign_line = -1;
- const GDScriptParser::Node *last_assigned_expression = nullptr;
- GDScriptParser::DataType var_type;
- while (blk) {
- if (blk->variables.has(p_identifier)) {
- if (blk->variables[p_identifier]->line > p_context.line) {
- return false;
- }
+ const GDScriptParser::ExpressionNode *last_assigned_expression = nullptr;
+ GDScriptParser::DataType id_type;
+ GDScriptParser::SuiteNode *suite = p_context.current_suite;
+ bool is_function_parameter = false;
- var_type = blk->variables[p_identifier]->datatype;
+ if (suite) {
+ if (suite->has_local(p_identifier)) {
+ const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier);
- if (!last_assigned_expression && blk->variables[p_identifier]->assign && blk->variables[p_identifier]->assign->type == GDScriptParser::Node::TYPE_OPERATOR) {
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->variables[p_identifier]->assign);
- if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN && op->arguments.size() >= 2) {
- last_assign_line = op->line;
- last_assigned_expression = op->arguments[1];
- }
- }
- }
+ id_type = local.get_datatype();
- for (const List<GDScriptParser::Node *>::Element *E = blk->statements.front(); E; E = E->next()) {
- const GDScriptParser::Node *expr = E->get();
- if (expr->line > p_context.line || expr->type != GDScriptParser::Node::TYPE_OPERATOR) {
- continue;
+ // Check initializer as the first assignment.
+ switch (local.type) {
+ case GDScriptParser::SuiteNode::Local::VARIABLE:
+ if (local.variable->initializer) {
+ last_assign_line = local.variable->initializer->end_line;
+ last_assigned_expression = local.variable->initializer;
+ }
+ break;
+ case GDScriptParser::SuiteNode::Local::CONSTANT:
+ if (local.constant->initializer) {
+ last_assign_line = local.constant->initializer->end_line;
+ last_assigned_expression = local.constant->initializer;
+ }
+ break;
+ case GDScriptParser::SuiteNode::Local::PARAMETER:
+ if (local.parameter->default_value) {
+ last_assign_line = local.parameter->default_value->end_line;
+ last_assigned_expression = local.parameter->default_value;
+ }
+ is_function_parameter = true;
+ break;
+ default:
+ break;
}
+ }
+ }
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(expr);
- if (op->op != GDScriptParser::OperatorNode::OP_ASSIGN || op->line < last_assign_line) {
- continue;
+ while (suite) {
+ for (int i = 0; i < suite->statements.size(); i++) {
+ if (suite->statements[i]->start_line > p_context.current_line) {
+ break;
}
- if (op->arguments.size() >= 2 && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
- const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]);
- if (id->name == p_identifier) {
- last_assign_line = op->line;
- last_assigned_expression = op->arguments[1];
- }
+ switch (suite->statements[i]->type) {
+ case GDScriptParser::Node::ASSIGNMENT: {
+ const GDScriptParser::AssignmentNode *assign = static_cast<const GDScriptParser::AssignmentNode *>(suite->statements[i]);
+ if (assign->end_line > last_assign_line && assign->assignee && assign->assigned_value && assign->assignee->type == GDScriptParser::Node::IDENTIFIER) {
+ const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->assignee);
+ if (id->name == p_identifier) {
+ last_assign_line = assign->assigned_value->end_line;
+ last_assigned_expression = assign->assigned_value;
+ }
+ }
+ } break;
+ default:
+ // TODO: Check sub blocks (control flow statements) as they might also reassign stuff.
+ break;
}
}
- if (blk->if_condition && blk->if_condition->type == GDScriptParser::Node::TYPE_OPERATOR && static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition)->op == GDScriptParser::OperatorNode::OP_IS) {
- //is used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common..
- //super dirty hack, but very useful
- //credit: Zylann
- //TODO: this could be hacked to detect ANDed conditions too..
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition);
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name == p_identifier) {
- //bingo
- GDScriptCompletionContext c = p_context;
- c.line = op->line;
- c.block = blk;
- if (_guess_expression_type(p_context, op->arguments[1], r_type)) {
- r_type.type.is_meta_type = false; // Right-hand of `is` will be a meta type, but the left-hand value is not
- // Not an assignment, it shouldn't carry any value
- r_type.value = Variant();
- r_type.assigned_expression = nullptr;
-
- return true;
+ if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::BINARY_OPERATOR && static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition)->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
+ // Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common..
+ // Super dirty hack, but very useful.
+ // Credit: Zylann.
+ // TODO: this could be hacked to detect ANDed conditions too...
+ const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition);
+ if (op->left_operand && op->right_operand && op->left_operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->left_operand)->name == p_identifier) {
+ // Bingo.
+ GDScriptParser::CompletionContext c = p_context;
+ c.current_line = op->left_operand->start_line;
+ c.current_suite = suite;
+ GDScriptCompletionIdentifier is_type;
+ if (_guess_expression_type(c, op->right_operand, is_type)) {
+ id_type = is_type.type;
+ id_type.is_meta_type = false;
+ if (last_assign_line < c.current_line) {
+ // Override last assignment.
+ last_assign_line = c.current_line;
+ last_assigned_expression = nullptr;
+ }
}
}
}
- blk = blk->parent_block;
+ suite = suite->parent_block;
}
- if (last_assigned_expression && last_assign_line != p_context.line) {
- GDScriptCompletionContext c = p_context;
- c.line = last_assign_line;
+ if (last_assigned_expression && last_assign_line != p_context.current_line) {
+ GDScriptParser::CompletionContext c = p_context;
+ c.current_line = last_assign_line;
r_type.assigned_expression = last_assigned_expression;
if (_guess_expression_type(c, last_assigned_expression, r_type)) {
- if (var_type.has_type) {
- r_type.type = var_type;
- }
return true;
}
}
- if (var_type.has_type) {
- r_type.type = var_type;
- return true;
- }
-
- if (p_context.function) {
- for (int i = 0; i < p_context.function->arguments.size(); i++) {
- if (p_context.function->arguments[i] == p_identifier) {
- if (p_context.function->argument_types[i].has_type) {
- r_type.type = p_context.function->argument_types[i];
- return true;
- }
-
- int def_from = p_context.function->arguments.size() - p_context.function->default_values.size();
- if (i >= def_from) {
- int def_idx = i - def_from;
- if (p_context.function->default_values[def_idx]->type == GDScriptParser::Node::TYPE_OPERATOR) {
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_context.function->default_values[def_idx]);
- if (op->arguments.size() < 2) {
- return false;
- }
- GDScriptCompletionContext c = p_context;
- c.function = nullptr;
- c.block = nullptr;
- return _guess_expression_type(c, op->arguments[1], r_type);
- }
- }
- break;
- }
- }
-
- GDScriptParser::DataType base_type = p_context._class->base_type;
- while (base_type.has_type) {
+ if (is_function_parameter && p_context.current_function && p_context.current_class) {
+ // Check if it's override of native function, then we can assume the type from the signature.
+ GDScriptParser::DataType base_type = p_context.current_class->base_type;
+ while (base_type.is_set()) {
switch (base_type.kind) {
- case GDScriptParser::DataType::GDSCRIPT: {
- Ref<GDScript> gds = base_type.script_type;
- if (gds.is_valid() && gds->has_method(p_context.function->name)) {
- GDScriptFunction *func = gds->get_member_functions()[p_context.function->name];
- if (func) {
- for (int i = 0; i < func->get_argument_count(); i++) {
- if (func->get_argument_name(i) == p_identifier) {
- r_type = _type_from_gdtype(func->get_argument_type(i));
- return true;
- }
- }
+ case GDScriptParser::DataType::CLASS:
+ if (base_type.class_type->has_function(p_context.current_function->identifier->name)) {
+ GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function;
+ const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier]];
+ if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) {
+ id_type = parameter->get_datatype();
}
- Ref<GDScript> base_gds = gds->get_base_script();
- if (base_gds.is_valid()) {
- base_type.kind = GDScriptParser::DataType::GDSCRIPT;
- base_type.script_type = base_gds;
- } else {
- base_type.kind = GDScriptParser::DataType::NATIVE;
- base_type.native_type = gds->get_instance_base_type();
+ if (parameter->default_value) {
+ GDScriptParser::CompletionContext c = p_context;
+ c.current_function = parent_function;
+ c.current_class = base_type.class_type;
+ c.base = nullptr;
+ if (_guess_expression_type(c, parameter->default_value, r_type)) {
+ return true;
+ }
}
- } else {
- base_type.kind = GDScriptParser::DataType::NATIVE;
- base_type.native_type = gds->get_instance_base_type();
+ base_type = base_type.class_type->base_type;
}
- } break;
+ break;
case GDScriptParser::DataType::NATIVE: {
- List<MethodInfo> methods;
- ClassDB::get_method_list(base_type.native_type, &methods);
- ClassDB::get_virtual_methods(base_type.native_type, &methods);
-
- for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
- if (E->get().name == p_context.function->name) {
- MethodInfo &mi = E->get();
- for (List<PropertyInfo>::Element *F = mi.arguments.front(); F; F = F->next()) {
- if (F->get().name == p_identifier) {
- r_type = _type_from_property(F->get());
- return true;
- }
+ if (id_type.is_set() && !id_type.is_variant()) {
+ base_type = GDScriptParser::DataType();
+ break;
+ }
+ StringName real_native = _get_real_class_name(base_type.native_type);
+ MethodInfo info;
+ if (ClassDB::get_method_info(real_native, p_context.current_function->identifier->name, &info)) {
+ for (const List<PropertyInfo>::Element *E = info.arguments.front(); E; E = E->next()) {
+ if (E->get().name == p_identifier) {
+ r_type = _type_from_property(E->get());
+ return true;
}
}
}
- base_type.has_type = false;
- } break;
- default: {
- base_type.has_type = false;
+ base_type = GDScriptParser::DataType();
} break;
+ default:
+ break;
}
}
}
- // Check current class (including inheritance)
- if (p_context._class) {
- GDScriptCompletionIdentifier context_base;
- context_base.value = p_context.base;
- context_base.type.has_type = true;
- context_base.type.kind = GDScriptParser::DataType::CLASS;
- context_base.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
- context_base.type.is_meta_type = p_context.function && p_context.function->_static;
-
- if (_guess_identifier_type_from_base(p_context, context_base, p_identifier, r_type)) {
- return true;
- }
+ if (id_type.is_set() && !id_type.is_variant()) {
+ r_type.type = id_type;
+ return true;
}
- // Check named scripts
- if (ScriptServer::is_global_class(p_identifier)) {
- Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier));
- if (scr.is_valid()) {
- r_type = _type_from_variant(scr);
- r_type.type.is_meta_type = true;
+ // Check current class (including inheritance).
+ if (p_context.current_class) {
+ GDScriptCompletionIdentifier base;
+ base.value = p_context.base;
+ base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ base.type.kind = GDScriptParser::DataType::CLASS;
+ base.type.class_type = p_context.current_class;
+ base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static;
+
+ if (_guess_identifier_type_from_base(p_context, base, p_identifier, r_type)) {
return true;
}
- return false;
}
- for (int i = 0; i < 2; i++) {
- StringName target_id;
- switch (i) {
- case 0:
- // Check ClassDB
- target_id = p_identifier;
- break;
- case 1:
- // ClassDB again for underscore-prefixed classes
- target_id = String("_") + p_identifier;
- break;
- }
-
- if (ClassDB::class_exists(target_id)) {
- r_type.type.has_type = true;
- r_type.type.kind = GDScriptParser::DataType::NATIVE;
- r_type.type.native_type = target_id;
- if (Engine::get_singleton()->has_singleton(target_id)) {
- r_type.type.is_meta_type = false;
- r_type.value = Engine::get_singleton()->get_singleton_object(target_id);
- } else {
+ // Check global scripts.
+ if (ScriptServer::is_global_class(p_identifier)) {
+ String script = ScriptServer::get_global_class_path(p_identifier);
+ if (script.to_lower().ends_with(".gd")) {
+ Error err = OK;
+ Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err);
+ if (err == OK) {
+ r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ r_type.type.script_path = script;
+ r_type.type.class_type = parser->get_parser()->get_tree();
+ r_type.type.is_constant = false;
+ r_type.type.kind = GDScriptParser::DataType::CLASS;
+ r_type.value = Variant();
+ p_context.dependent_parsers.push_back(parser);
+ return true;
+ }
+ } else {
+ Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier));
+ if (scr.is_valid()) {
+ r_type = _type_from_variant(scr);
r_type.type.is_meta_type = true;
- const Map<StringName, int>::Element *target_elem = GDScriptLanguage::get_singleton()->get_global_map().find(target_id);
- // Check because classes like EditorNode are in ClassDB by now, but unknown to GDScript
- if (!target_elem) {
- return false;
- }
- int idx = target_elem->get();
- r_type.value = GDScriptLanguage::get_singleton()->get_global_array()[idx];
+ return true;
}
- return true;
}
+ return false;
}
- // Check autoload singletons
- if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) {
+ // Check autoloads.
+ if (ProjectSettings::get_singleton()->has_autoload(p_identifier)) {
r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]);
return true;
}
+ // Check ClassDB.
+ StringName class_name = _get_real_class_name(p_identifier);
+ if (ClassDB::class_exists(class_name) && ClassDB::is_class_exposed(class_name)) {
+ r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ r_type.type.kind = GDScriptParser::DataType::NATIVE;
+ r_type.type.native_type = p_identifier;
+ r_type.type.is_constant = true;
+ r_type.type.is_meta_type = !Engine::get_singleton()->has_singleton(class_name);
+ r_type.value = Variant();
+ }
+
return false;
}
-static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) {
+static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) {
GDScriptParser::DataType base_type = p_base.type;
- bool _static = base_type.is_meta_type;
- while (base_type.has_type) {
+ bool is_static = base_type.is_meta_type;
+ while (base_type.is_set()) {
switch (base_type.kind) {
- case GDScriptParser::DataType::CLASS: {
- if (base_type.class_type->constant_expressions.has(p_identifier)) {
- GDScriptParser::ClassNode::Constant c = base_type.class_type->constant_expressions[p_identifier];
- r_type.type = c.type;
- if (c.expression->type == GDScriptParser::Node::TYPE_CONSTANT) {
- r_type.value = static_cast<const GDScriptParser::ConstantNode *>(c.expression)->value;
- }
- return true;
- }
-
- if (!_static) {
- for (int i = 0; i < base_type.class_type->variables.size(); i++) {
- GDScriptParser::ClassNode::Member m = base_type.class_type->variables[i];
- if (m.identifier == p_identifier) {
- if (m.expression) {
- if (p_context.line == m.expression->line) {
- // Variable used in the same expression
- return false;
- }
-
- if (_guess_expression_type(p_context, m.expression, r_type)) {
- return true;
- }
- if (m.expression->get_datatype().has_type) {
- r_type.type = m.expression->get_datatype();
+ case GDScriptParser::DataType::CLASS:
+ if (base_type.class_type->has_member(p_identifier)) {
+ const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_identifier);
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::CONSTANT:
+ r_type.type = member.constant->get_datatype();
+ if (member.constant->initializer && member.constant->initializer->is_constant) {
+ r_type.value = member.constant->initializer->reduced_value;
+ }
+ return true;
+ case GDScriptParser::ClassNode::Member::VARIABLE:
+ if (!is_static) {
+ if (member.variable->initializer) {
+ const GDScriptParser::ExpressionNode *init = member.variable->initializer;
+ if (init->is_constant) {
+ r_type.value = init->reduced_value;
+ r_type = _type_from_variant(init->reduced_value);
+ return true;
+ } else if (init->start_line == p_context.current_line) {
+ return false;
+ } else if (_guess_expression_type(p_context, init, r_type)) {
+ return true;
+ } else if (init->get_datatype().is_set() && !init->get_datatype().is_variant()) {
+ r_type.type = init->get_datatype();
+ return true;
+ }
+ } else if (member.variable->get_datatype().is_set() && !member.variable->get_datatype().is_variant()) {
+ r_type.type = member.variable->get_datatype();
return true;
}
}
- if (m.data_type.has_type) {
- r_type.type = m.data_type;
- return true;
- }
+ // TODO: Check assignments in constructor.
return false;
- }
- }
- }
- base_type = base_type.class_type->base_type;
- } break;
- case GDScriptParser::DataType::GDSCRIPT: {
- Ref<GDScript> gds = base_type.script_type;
- if (gds.is_valid()) {
- if (gds->get_constants().has(p_identifier)) {
- r_type = _type_from_variant(gds->get_constants()[p_identifier]);
- return true;
- }
- if (!_static) {
- const Set<StringName>::Element *m = gds->get_members().find(p_identifier);
- if (m) {
- r_type = _type_from_gdtype(gds->get_member_type(p_identifier));
+ case GDScriptParser::ClassNode::Member::ENUM:
+ r_type.type = member.m_enum->get_datatype();
+ r_type.enumeration = member.m_enum->identifier->name;
return true;
- }
- }
- Ref<GDScript> parent = gds->get_base_script();
- if (parent.is_valid()) {
- base_type.script_type = parent;
- } else {
- base_type.kind = GDScriptParser::DataType::NATIVE;
- base_type.native_type = gds->get_instance_base_type();
+ case GDScriptParser::ClassNode::Member::ENUM_VALUE:
+ r_type = _type_from_variant(member.enum_value.value);
+ return true;
+ case GDScriptParser::ClassNode::Member::SIGNAL:
+ r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ r_type.type.kind = GDScriptParser::DataType::BUILTIN;
+ r_type.type.builtin_type = Variant::SIGNAL;
+ return true;
+ case GDScriptParser::ClassNode::Member::FUNCTION:
+ if (is_static && !member.function->is_static) {
+ return false;
+ }
+ r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ r_type.type.kind = GDScriptParser::DataType::BUILTIN;
+ r_type.type.builtin_type = Variant::CALLABLE;
+ return true;
+ case GDScriptParser::ClassNode::Member::CLASS:
+ r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ r_type.type.kind = GDScriptParser::DataType::CLASS;
+ r_type.type.class_type = member.m_class;
+ return true;
+ case GDScriptParser::ClassNode::Member::UNDEFINED:
+ return false; // Unreachable.
}
- } else {
return false;
}
- } break;
+ base_type = base_type.class_type->base_type;
+ break;
case GDScriptParser::DataType::SCRIPT: {
Ref<Script> scr = base_type.script_type;
if (scr.is_valid()) {
@@ -1467,7 +1894,7 @@ static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_contex
return true;
}
- if (!_static) {
+ if (!is_static) {
List<PropertyInfo> members;
scr->get_script_property_list(&members);
for (const List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
@@ -1490,33 +1917,25 @@ static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_contex
}
} break;
case GDScriptParser::DataType::NATIVE: {
- StringName class_name = base_type.native_type;
+ StringName class_name = _get_real_class_name(base_type.native_type);
if (!ClassDB::class_exists(class_name)) {
- class_name = String("_") + class_name;
- if (!ClassDB::class_exists(class_name)) {
- return false;
- }
+ return false;
}
- // Skip constants since they're all integers. Type does not matter because int has no members
+ // Skip constants since they're all integers. Type does not matter because int has no members.
- List<PropertyInfo> props;
- ClassDB::get_property_list(class_name, &props);
- for (const List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- const PropertyInfo &prop = E->get();
- if (prop.name == p_identifier) {
- StringName getter = ClassDB::get_property_getter(class_name, p_identifier);
- if (getter != StringName()) {
- MethodBind *g = ClassDB::get_method(class_name, getter);
- if (g) {
- r_type = _type_from_property(g->get_return_info());
- return true;
- }
- } else {
- r_type = _type_from_property(prop);
+ PropertyInfo prop;
+ if (ClassDB::get_property_info(class_name, p_identifier, &prop)) {
+ StringName getter = ClassDB::get_property_getter(class_name, p_identifier);
+ if (getter != StringName()) {
+ MethodBind *g = ClassDB::get_method(class_name, getter);
+ if (g) {
+ r_type = _type_from_property(g->get_return_info());
return true;
}
- break;
+ } else {
+ r_type = _type_from_property(prop);
+ return true;
}
}
return false;
@@ -1543,116 +1962,99 @@ static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_contex
} break;
}
}
-
return false;
}
-static bool _find_last_return_in_block(const GDScriptCompletionContext &p_context, int &r_last_return_line, const GDScriptParser::Node **r_last_returned_value) {
- if (!p_context.block) {
- return false;
+static void _find_last_return_in_block(GDScriptParser::CompletionContext &p_context, int &r_last_return_line, const GDScriptParser::ExpressionNode **r_last_returned_value) {
+ if (!p_context.current_suite) {
+ return;
}
- for (int i = 0; i < p_context.block->statements.size(); i++) {
- if (p_context.block->statements[i]->line < r_last_return_line) {
- continue;
- }
- if (p_context.block->statements[i]->type != GDScriptParser::Node::TYPE_CONTROL_FLOW) {
- continue;
+ for (int i = 0; i < p_context.current_suite->statements.size(); i++) {
+ if (p_context.current_suite->statements[i]->start_line < r_last_return_line) {
+ break;
}
- const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(p_context.block->statements[i]);
- if (cf->cf_type == GDScriptParser::ControlFlowNode::CF_RETURN && cf->arguments.size() > 0) {
- if (cf->line > r_last_return_line) {
- r_last_return_line = cf->line;
- *r_last_returned_value = cf->arguments[0];
- }
+ GDScriptParser::CompletionContext c = p_context;
+ switch (p_context.current_suite->statements[i]->type) {
+ case GDScriptParser::Node::FOR:
+ c.current_suite = static_cast<const GDScriptParser::ForNode *>(p_context.current_suite->statements[i])->loop;
+ _find_last_return_in_block(c, r_last_return_line, r_last_returned_value);
+ break;
+ case GDScriptParser::Node::WHILE:
+ c.current_suite = static_cast<const GDScriptParser::WhileNode *>(p_context.current_suite->statements[i])->loop;
+ _find_last_return_in_block(c, r_last_return_line, r_last_returned_value);
+ break;
+ case GDScriptParser::Node::IF: {
+ const GDScriptParser::IfNode *_if = static_cast<const GDScriptParser::IfNode *>(p_context.current_suite->statements[i]);
+ c.current_suite = _if->true_block;
+ _find_last_return_in_block(c, r_last_return_line, r_last_returned_value);
+ if (_if->false_block) {
+ c.current_suite = _if->false_block;
+ _find_last_return_in_block(c, r_last_return_line, r_last_returned_value);
+ }
+ } break;
+ case GDScriptParser::Node::MATCH: {
+ const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(p_context.current_suite->statements[i]);
+ for (int j = 0; j < match->branches.size(); j++) {
+ c.current_suite = match->branches[j]->block;
+ _find_last_return_in_block(c, r_last_return_line, r_last_returned_value);
+ }
+ } break;
+ case GDScriptParser::Node::RETURN: {
+ const GDScriptParser::ReturnNode *ret = static_cast<const GDScriptParser::ReturnNode *>(p_context.current_suite->statements[i]);
+ if (ret->return_value) {
+ if (ret->start_line > r_last_return_line) {
+ r_last_return_line = ret->start_line;
+ *r_last_returned_value = ret->return_value;
+ }
+ }
+ } break;
+ default:
+ break;
}
}
-
- // Recurse into subblocks
- for (int i = 0; i < p_context.block->sub_blocks.size(); i++) {
- GDScriptCompletionContext c = p_context;
- c.block = p_context.block->sub_blocks[i];
- _find_last_return_in_block(c, r_last_return_line, r_last_returned_value);
- }
-
- return false;
}
-static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) {
+static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) {
GDScriptParser::DataType base_type = p_base.type;
- bool _static = base_type.is_meta_type;
+ bool is_static = base_type.is_meta_type;
- if (_static && p_method == "new") {
+ if (is_static && p_method == "new") {
r_type.type = base_type;
r_type.type.is_meta_type = false;
r_type.type.is_constant = false;
return true;
}
- while (base_type.has_type) {
+ while (base_type.is_set() && !base_type.is_variant()) {
switch (base_type.kind) {
- case GDScriptParser::DataType::CLASS: {
- if (!base_type.class_type) {
- base_type.has_type = false;
- break;
- }
-
- for (int i = 0; i < base_type.class_type->static_functions.size(); i++) {
- if (base_type.class_type->static_functions[i]->name == p_method) {
+ case GDScriptParser::DataType::CLASS:
+ if (base_type.class_type->has_function(p_method)) {
+ const GDScriptParser::FunctionNode *method = base_type.class_type->get_member(p_method).function;
+ if (!is_static || method->is_static) {
int last_return_line = -1;
- const GDScriptParser::Node *last_returned_value = nullptr;
- GDScriptCompletionContext c = p_context;
- c._class = base_type.class_type;
- c.function = base_type.class_type->static_functions[i];
- c.block = c.function->body;
+ const GDScriptParser::ExpressionNode *last_returned_value = nullptr;
+ GDScriptParser::CompletionContext c = p_context;
+ c.current_class = base_type.class_type;
+ c.current_function = const_cast<GDScriptParser::FunctionNode *>(method);
+ c.current_suite = method->body;
_find_last_return_in_block(c, last_return_line, &last_returned_value);
if (last_returned_value) {
- c.line = c.block->end_line;
- return _guess_expression_type(c, last_returned_value, r_type);
- }
- }
- }
- if (!_static) {
- for (int i = 0; i < base_type.class_type->functions.size(); i++) {
- if (base_type.class_type->functions[i]->name == p_method) {
- int last_return_line = -1;
- const GDScriptParser::Node *last_returned_value = nullptr;
- GDScriptCompletionContext c = p_context;
- c._class = base_type.class_type;
- c.function = base_type.class_type->functions[i];
- c.block = c.function->body;
-
- _find_last_return_in_block(c, last_return_line, &last_returned_value);
- if (last_returned_value) {
- c.line = c.block->end_line;
- return _guess_expression_type(c, last_returned_value, r_type);
+ c.current_line = c.current_suite->end_line;
+ if (_guess_expression_type(c, last_returned_value, r_type)) {
+ return true;
+ }
+ if (method->get_datatype().is_set() && !method->get_datatype().is_variant()) {
+ r_type.type = method->get_datatype();
+ return true;
}
}
}
}
-
base_type = base_type.class_type->base_type;
- } break;
- case GDScriptParser::DataType::GDSCRIPT: {
- Ref<GDScript> gds = base_type.script_type;
- if (gds.is_valid()) {
- if (gds->get_member_functions().has(p_method)) {
- r_type = _type_from_gdtype(gds->get_member_functions()[p_method]->get_return_type());
- return true;
- }
- Ref<GDScript> base_script = gds->get_base_script();
- if (base_script.is_valid()) {
- base_type.script_type = base_script;
- } else {
- base_type.kind = GDScriptParser::DataType::NATIVE;
- base_type.native_type = gds->get_instance_base_type();
- }
- } else {
- return false;
- }
- } break;
+ break;
case GDScriptParser::DataType::SCRIPT: {
Ref<Script> scr = base_type.script_type;
if (scr.is_valid()) {
@@ -1677,12 +2079,9 @@ static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_con
}
} break;
case GDScriptParser::DataType::NATIVE: {
- StringName native = base_type.native_type;
+ StringName native = _get_real_class_name(base_type.native_type);
if (!ClassDB::class_exists(native)) {
- native = String("_") + native;
- if (!ClassDB::class_exists(native)) {
- return false;
- }
+ return false;
}
MethodBind *mb = ClassDB::get_method(native, p_method);
if (mb) {
@@ -1715,103 +2114,27 @@ static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_con
}
}
}
- return false;
-}
-
-static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx) {
- String arghint = _get_visual_datatype(p_info.return_val, false) + " " + p_info.name + "(";
-
- int def_args = p_info.arguments.size() - p_info.default_arguments.size();
- int i = 0;
- for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E; E = E->next()) {
- if (i > 0) {
- arghint += ", ";
- }
-
- if (i == p_arg_idx) {
- arghint += String::chr(0xFFFF);
- }
- arghint += E->get().name + ": " + _get_visual_datatype(E->get(), true);
-
- if (i - def_args >= 0) {
- arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string();
- }
-
- if (i == p_arg_idx) {
- arghint += String::chr(0xFFFF);
- }
-
- i++;
- }
-
- if (p_info.flags & METHOD_FLAG_VARARG) {
- if (p_info.arguments.size() > 0) {
- arghint += ", ";
- }
- if (p_arg_idx >= p_info.arguments.size()) {
- arghint += String::chr(0xFFFF);
- }
- arghint += "...";
- if (p_arg_idx >= p_info.arguments.size()) {
- arghint += String::chr(0xFFFF);
- }
- }
-
- arghint += ")";
-
- return arghint;
-}
-
-static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) {
- String arghint = p_function->return_type.to_string() + " " + p_function->name.operator String() + "(";
-
- int def_args = p_function->arguments.size() - p_function->default_values.size();
- for (int i = 0; i < p_function->arguments.size(); i++) {
- if (i > 0) {
- arghint += ", ";
- }
-
- if (i == p_arg_idx) {
- arghint += String::chr(0xFFFF);
- }
- arghint += p_function->arguments[i].operator String() + ": " + p_function->argument_types[i].to_string();
-
- if (i - def_args >= 0) {
- String def_val = "<unknown>";
- if (p_function->default_values[i - def_args] && p_function->default_values[i - def_args]->type == GDScriptParser::Node::TYPE_OPERATOR) {
- const GDScriptParser::OperatorNode *assign = static_cast<const GDScriptParser::OperatorNode *>(p_function->default_values[i - def_args]);
-
- if (assign->arguments.size() >= 2) {
- if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) {
- const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(assign->arguments[1]);
- def_val = cn->value.get_construct_string();
- } else if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
- const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->arguments[1]);
- def_val = id->name.operator String();
- }
- }
- }
- arghint += " = " + def_val;
- }
- if (i == p_arg_idx) {
- arghint += String::chr(0xFFFF);
- }
- }
- arghint += ")";
-
- return arghint;
+ return false;
}
-static void _find_enumeration_candidates(const String p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) {
+static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_context, const String &p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) {
if (p_enum_hint.find(".") == -1) {
- // Global constant
+ // Global constant or in the current class.
StringName current_enum = p_enum_hint;
- for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
- if (GlobalConstants::get_global_constant_enum(i) == current_enum) {
- ScriptCodeCompletionOption option(GlobalConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM);
+ if (p_context.current_class && p_context.current_class->has_member(current_enum) && p_context.current_class->get_member(current_enum).type == GDScriptParser::ClassNode::Member::ENUM) {
+ const GDScriptParser::EnumNode *_enum = p_context.current_class->get_member(current_enum).m_enum;
+ for (int i = 0; i < _enum->values.size(); i++) {
+ ScriptCodeCompletionOption option(_enum->values[i].identifier->name, ScriptCodeCompletionOption::KIND_ENUM);
r_result.insert(option.display, option);
}
+ } else {
+ for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
+ if (GlobalConstants::get_global_constant_enum(i) == current_enum) {
+ ScriptCodeCompletionOption option(GlobalConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM);
+ r_result.insert(option.display, option);
+ }
+ }
}
} else {
String class_name = p_enum_hint.get_slice(".", 0);
@@ -1831,500 +2154,60 @@ static void _find_enumeration_candidates(const String p_enum_hint, Map<String, S
}
}
-static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Map<String, ScriptCodeCompletionOption> &r_result) {
- for (Map<StringName, GDScriptParser::LocalVarNode *>::Element *E = p_context.block->variables.front(); E; E = E->next()) {
- if (E->get()->line < p_context.line) {
- ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_VARIABLE);
- r_result.insert(option.display, option);
- }
- }
- if (p_context.block->parent_block) {
- GDScriptCompletionContext c = p_context;
- c.block = p_context.block->parent_block;
- _find_identifiers_in_block(c, r_result);
- }
-}
-
-static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result);
-
-static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) {
- if (!p_parent_only) {
- if (!p_static && !p_only_functions) {
- for (int i = 0; i < p_context._class->variables.size(); i++) {
- ScriptCodeCompletionOption option(p_context._class->variables[i].identifier, ScriptCodeCompletionOption::KIND_MEMBER);
- r_result.insert(option.display, option);
- }
- }
-
- if (!p_only_functions) {
- for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_context._class->constant_expressions.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT);
- r_result.insert(option.display, option);
- }
- for (int i = 0; i < p_context._class->subclasses.size(); i++) {
- ScriptCodeCompletionOption option(p_context._class->subclasses[i]->name, ScriptCodeCompletionOption::KIND_CLASS);
- r_result.insert(option.display, option);
- }
- }
-
- for (int i = 0; i < p_context._class->static_functions.size(); i++) {
- ScriptCodeCompletionOption option(p_context._class->static_functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION);
- if (p_context._class->static_functions[i]->arguments.size()) {
- option.insert_text += "(";
- } else {
- option.insert_text += "()";
- }
- r_result.insert(option.display, option);
- }
-
- if (!p_static) {
- for (int i = 0; i < p_context._class->functions.size(); i++) {
- ScriptCodeCompletionOption option(p_context._class->functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION);
- if (p_context._class->functions[i]->arguments.size()) {
- option.insert_text += "(";
- } else {
- option.insert_text += "()";
- }
- r_result.insert(option.display, option);
- }
- }
- }
-
- // Parents
- GDScriptCompletionIdentifier base_type;
- base_type.type = p_context._class->base_type;
- base_type.type.is_meta_type = p_static;
- base_type.value = p_context.base;
-
- GDScriptCompletionContext c = p_context;
- c.block = nullptr;
- c.function = nullptr;
-
- _find_identifiers_in_base(c, base_type, p_only_functions, r_result);
-}
-
-static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
- GDScriptParser::DataType base_type = p_base.type;
- bool _static = base_type.is_meta_type;
-
- if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) {
- ScriptCodeCompletionOption option("new", ScriptCodeCompletionOption::KIND_FUNCTION);
- option.insert_text += "(";
- r_result.insert(option.display, option);
- }
-
- while (base_type.has_type) {
- switch (base_type.kind) {
- case GDScriptParser::DataType::CLASS: {
- GDScriptCompletionContext c = p_context;
- c._class = base_type.class_type;
- c.block = nullptr;
- c.function = nullptr;
- _find_identifiers_in_class(c, _static, p_only_functions, false, r_result);
- base_type = base_type.class_type->base_type;
- } break;
- case GDScriptParser::DataType::GDSCRIPT: {
- Ref<GDScript> script = base_type.script_type;
- if (script.is_valid()) {
- if (!_static && !p_only_functions) {
- if (p_context.base && p_context.base->get_script_instance()) {
- List<PropertyInfo> members;
- p_context.base->get_script_instance()->get_property_list(&members);
- for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
- r_result.insert(option.display, option);
- }
- }
- for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER);
- r_result.insert(option.display, option);
- }
- }
- if (!p_only_functions) {
- for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT);
- r_result.insert(option.display, option);
- }
- }
- for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) {
- if (!_static || E->get()->is_static()) {
- ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_FUNCTION);
- if (E->get()->get_argument_count()) {
- option.insert_text += "(";
- } else {
- option.insert_text += "()";
- }
- r_result.insert(option.display, option);
- }
- }
- if (!p_only_functions) {
- for (const Map<StringName, Ref<GDScript>>::Element *E = script->get_subclasses().front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
- r_result.insert(option.display, option);
- }
- }
- base_type = GDScriptParser::DataType();
- if (script->get_base().is_valid()) {
- base_type.has_type = true;
- base_type.kind = GDScriptParser::DataType::GDSCRIPT;
- base_type.script_type = script->get_base();
- } else {
- base_type.has_type = script->get_instance_base_type() != StringName();
- base_type.kind = GDScriptParser::DataType::NATIVE;
- base_type.native_type = script->get_instance_base_type();
- }
- } else {
- return;
- }
- } break;
- case GDScriptParser::DataType::SCRIPT: {
- Ref<Script> scr = base_type.script_type;
- if (scr.is_valid()) {
- if (!_static && !p_only_functions) {
- List<PropertyInfo> members;
- scr->get_script_property_list(&members);
- for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
- r_result.insert(option.display, option);
- }
- }
- if (!p_only_functions) {
- Map<StringName, Variant> constants;
- scr->get_constants(&constants);
- for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT);
- r_result.insert(option.display, option);
- }
- }
-
- List<MethodInfo> methods;
- scr->get_script_method_list(&methods);
- for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
- if (E->get().arguments.size()) {
- option.insert_text += "(";
- } else {
- option.insert_text += "()";
- }
- r_result.insert(option.display, option);
- }
-
- Ref<Script> base_script = scr->get_base_script();
- if (base_script.is_valid()) {
- base_type.script_type = base_script;
- } else {
- base_type.kind = GDScriptParser::DataType::NATIVE;
- base_type.native_type = scr->get_instance_base_type();
- }
- } else {
- return;
- }
- } break;
- case GDScriptParser::DataType::NATIVE: {
- StringName type = base_type.native_type;
- if (!ClassDB::class_exists(type)) {
- type = String("_") + type;
- if (!ClassDB::class_exists(type)) {
- return;
- }
- }
-
- if (!p_only_functions) {
- List<String> constants;
- ClassDB::get_integer_constant_list(type, &constants);
- for (List<String>::Element *E = constants.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT);
- r_result.insert(option.display, option);
- }
-
- if (!_static) {
- List<PropertyInfo> pinfo;
- ClassDB::get_property_list(type, &pinfo);
- for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
- if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_CATEGORY)) {
- continue;
- }
- if (E->get().name.find("/") != -1) {
- continue;
- }
- ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
- r_result.insert(option.display, option);
- }
- }
- }
-
- if (!_static) {
- List<MethodInfo> methods;
- bool is_autocompleting_getters = GLOBAL_GET("debug/gdscript/completion/autocomplete_setters_and_getters").booleanize();
- ClassDB::get_method_list(type, &methods, false, !is_autocompleting_getters);
- for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
- if (E->get().name.begins_with("_")) {
- continue;
- }
- ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
- if (E->get().arguments.size()) {
- option.insert_text += "(";
- } else {
- option.insert_text += "()";
- }
- r_result.insert(option.display, option);
- }
- }
-
- return;
- } break;
- case GDScriptParser::DataType::BUILTIN: {
- Callable::CallError err;
- Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
- if (err.error != Callable::CallError::CALL_OK) {
- return;
- }
-
- if (!p_only_functions) {
- List<PropertyInfo> members;
- if (p_base.value.get_type() != Variant::NIL) {
- p_base.value.get_property_list(&members);
- } else {
- tmp.get_property_list(&members);
- }
-
- for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
- if (String(E->get().name).find("/") == -1) {
- ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
- r_result.insert(option.display, option);
- }
- }
- }
-
- List<MethodInfo> methods;
- tmp.get_method_list(&methods);
- for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
- if (E->get().arguments.size()) {
- option.insert_text += "(";
- } else {
- option.insert_text += "()";
- }
- r_result.insert(option.display, option);
- }
-
- return;
- } break;
- default: {
- return;
- } break;
- }
- }
-}
-
-static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
- const GDScriptParser::BlockNode *block = p_context.block;
-
- if (p_context.function) {
- const GDScriptParser::FunctionNode *f = p_context.function;
-
- for (int i = 0; i < f->arguments.size(); i++) {
- ScriptCodeCompletionOption option(f->arguments[i].operator String(), ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
- r_result.insert(option.display, option);
- }
- }
-
- if (!p_only_functions && block) {
- GDScriptCompletionContext c = p_context;
- c.block = block;
- _find_identifiers_in_block(c, r_result);
- }
-
- const GDScriptParser::ClassNode *clss = p_context._class;
- bool _static = p_context.function && p_context.function->_static;
-
- while (clss) {
- GDScriptCompletionContext c = p_context;
- c._class = clss;
- c.block = nullptr;
- c.function = nullptr;
- _find_identifiers_in_class(c, _static, p_only_functions, false, r_result);
- _static = true;
- clss = clss->owner;
- }
-
- for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
- MethodInfo mi = GDScriptFunctions::get_info(GDScriptFunctions::Function(i));
- ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION);
- if (mi.arguments.size() || (mi.flags & METHOD_FLAG_VARARG)) {
- option.insert_text += "(";
- } else {
- option.insert_text += "()";
- }
- r_result.insert(option.display, option);
- }
-
- static const char *_type_names[Variant::VARIANT_MAX] = {
- "null", "bool", "int", "float", "String", "Vector2", "Vector2i", "Rect2", "Rect2i", "Vector3", "Vector3i", "Transform2D", "Plane", "Quat", "AABB", "Basis", "Transform",
- "Color", "StringName", "NodePath", "RID", "Object", "Callable", "Signal", "Dictionary", "Array", "PackedByteArray", "PackedInt32Array", "PackedInt64Array", "PackedFloat32Array", "PackedFloat64Array", "PackedStringArray",
- "PackedVector2Array", "PackedVector3Array", "PackedColorArray"
- };
-
- for (int i = 0; i < Variant::VARIANT_MAX; i++) {
- ScriptCodeCompletionOption option(_type_names[i], ScriptCodeCompletionOption::KIND_CLASS);
- r_result.insert(option.display, option);
- }
-
- static const char *_keywords[] = {
- "and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert",
- "breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield",
- "const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif",
- "else", "for", "pass", "return", "match", "while", "remote", "master", "puppet",
- "remotesync", "mastersync", "puppetsync",
- nullptr
- };
-
- const char **kw = _keywords;
- while (*kw) {
- ScriptCodeCompletionOption option(*kw, ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
- r_result.insert(option.display, option);
- kw++;
- }
-
- // Autoload singletons
- List<PropertyInfo> props;
- ProjectSettings::get_singleton()->get_property_list(&props);
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- String s = E->get().name;
- if (!s.begins_with("autoload/")) {
- continue;
- }
- String path = ProjectSettings::get_singleton()->get(s);
- if (path.begins_with("*")) {
- ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CONSTANT);
- r_result.insert(option.display, option);
- }
- }
-
- // Named scripts
- List<StringName> named_scripts;
- ScriptServer::get_global_class_list(&named_scripts);
- for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
- r_result.insert(option.display, option);
- }
-
- // Native classes
- for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
- r_result.insert(option.display, option);
- }
-}
-
-static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Map<String, ScriptCodeCompletionOption> &r_result, String &r_arghint) {
+static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Map<String, ScriptCodeCompletionOption> &r_result, String &r_arghint) {
Variant base = p_base.value;
GDScriptParser::DataType base_type = p_base.type;
const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
-#define IS_METHOD_SIGNAL(m_method) (m_method == "connect" || m_method == "disconnect" || m_method == "is_connected" || m_method == "emit_signal")
-
- while (base_type.has_type) {
+ while (base_type.is_set() && !base_type.is_variant()) {
switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: {
- for (int i = 0; i < base_type.class_type->static_functions.size(); i++) {
- if (base_type.class_type->static_functions[i]->name == p_method) {
- r_arghint = _make_arguments_hint(base_type.class_type->static_functions[i], p_argidx);
- return;
- }
- }
- for (int i = 0; i < base_type.class_type->functions.size(); i++) {
- if (base_type.class_type->functions[i]->name == p_method) {
- r_arghint = _make_arguments_hint(base_type.class_type->functions[i], p_argidx);
- return;
- }
- }
+ if (base_type.class_type->has_member(p_method)) {
+ const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_method);
- if (IS_METHOD_SIGNAL(p_method) && p_argidx == 0) {
- for (int i = 0; i < base_type.class_type->_signals.size(); i++) {
- ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL);
- option.insert_text = quote_style + option.display + quote_style;
- r_result.insert(option.display, option);
+ if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
+ r_arghint = _make_arguments_hint(member.function, p_argidx);
+ return;
}
}
base_type = base_type.class_type->base_type;
} break;
- case GDScriptParser::DataType::GDSCRIPT: {
- Ref<GDScript> gds = base_type.script_type;
- if (gds.is_valid()) {
- if (IS_METHOD_SIGNAL(p_method) && p_argidx == 0) {
- List<MethodInfo> signals;
- gds->get_script_signal_list(&signals);
- for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL);
- option.insert_text = quote_style + option.display + quote_style;
- r_result.insert(option.display, option);
- }
- }
- Ref<GDScript> base_script = gds->get_base_script();
- if (base_script.is_valid()) {
- base_type.script_type = base_script;
- } else {
- base_type.kind = GDScriptParser::DataType::NATIVE;
- base_type.native_type = gds->get_instance_base_type();
- }
- } else {
- return;
- }
- } break;
case GDScriptParser::DataType::NATIVE: {
- StringName class_name = base_type.native_type;
+ StringName class_name = _get_real_class_name(base_type.native_type);
if (!ClassDB::class_exists(class_name)) {
- class_name = String("_") + class_name;
- if (!ClassDB::class_exists(class_name)) {
- base_type.has_type = false;
- break;
- }
+ base_type.kind = GDScriptParser::DataType::UNRESOLVED;
+ break;
}
- List<MethodInfo> methods;
- ClassDB::get_method_list(class_name, &methods);
- ClassDB::get_virtual_methods(class_name, &methods);
+ MethodInfo info;
int method_args = 0;
- for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
- if (E->get().name == p_method) {
- method_args = E->get().arguments.size();
- if (base.get_type() == Variant::OBJECT) {
- Object *obj = base.operator Object *();
- if (obj) {
- List<String> options;
- obj->get_argument_options(p_method, p_argidx, &options);
- for (List<String>::Element *F = options.front(); F; F = F->next()) {
- ScriptCodeCompletionOption option(F->get(), ScriptCodeCompletionOption::KIND_FUNCTION);
- r_result.insert(option.display, option);
- }
+ if (ClassDB::get_method_info(class_name, p_method, &info)) {
+ method_args = info.arguments.size();
+ if (base.get_type() == Variant::OBJECT) {
+ Object *obj = base.operator Object *();
+ if (obj) {
+ List<String> options;
+ obj->get_argument_options(p_method, p_argidx, &options);
+ for (List<String>::Element *F = options.front(); F; F = F->next()) {
+ ScriptCodeCompletionOption option(F->get(), ScriptCodeCompletionOption::KIND_FUNCTION);
+ r_result.insert(option.display, option);
}
}
+ }
- if (p_argidx < method_args) {
- PropertyInfo arg_info = E->get().arguments[p_argidx];
- if (arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
- _find_enumeration_candidates(arg_info.class_name, r_result);
- }
+ if (p_argidx < method_args) {
+ PropertyInfo arg_info = info.arguments[p_argidx];
+ if (arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
+ _find_enumeration_candidates(p_context, arg_info.class_name, r_result);
}
-
- r_arghint = _make_arguments_hint(E->get(), p_argidx);
- break;
}
- }
- if (IS_METHOD_SIGNAL(p_method) && p_argidx == 0) {
- List<MethodInfo> signals;
- ClassDB::get_signal_list(class_name, &signals);
- for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL);
- option.insert_text = quote_style + option.display + quote_style;
- r_result.insert(option.display, option);
- }
+ r_arghint = _make_arguments_hint(info, p_argidx);
+ return;
}
-#undef IS_METHOD_SIGNAL
if (ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node") && p_argidx == 0) {
// Get autoloads
@@ -2359,7 +2242,7 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con
}
}
- base_type.has_type = false;
+ base_type.kind = GDScriptParser::DataType::UNRESOLVED;
} break;
case GDScriptParser::DataType::BUILTIN: {
if (base.get_type() == Variant::NIL) {
@@ -2379,139 +2262,92 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con
}
}
- base_type.has_type = false;
+ base_type.kind = GDScriptParser::DataType::UNRESOLVED;
} break;
default: {
- base_type.has_type = false;
+ base_type.kind = GDScriptParser::DataType::UNRESOLVED;
} break;
}
}
}
-static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Map<String, ScriptCodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) {
+static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptParser::Node *p_call, int p_argidx, Map<String, ScriptCodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) {
const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
- if (!p_node || p_node->type != GDScriptParser::Node::TYPE_OPERATOR) {
+ if (p_call->type == GDScriptParser::Node::PRELOAD) {
+ if (p_argidx == 0 && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
+ _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result);
+ }
+
+ MethodInfo mi(PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), "preload", PropertyInfo(Variant::STRING, "path"));
+ r_arghint = _make_arguments_hint(mi, p_argidx);
+ return;
+ } else if (p_call->type != GDScriptParser::Node::CALL) {
return;
}
Variant base;
GDScriptParser::DataType base_type;
- StringName function;
bool _static = false;
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_node);
+ const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_call);
GDScriptCompletionIdentifier connect_base;
- if (op->op != GDScriptParser::OperatorNode::OP_CALL && op->op != GDScriptParser::OperatorNode::OP_PARENT_CALL) {
- return;
- }
+ if (GDScriptParser::get_builtin_function(call->function_name) < GDScriptFunctions::FUNC_MAX) {
+ MethodInfo info = GDScriptFunctions::get_info(GDScriptParser::get_builtin_function(call->function_name));
- if (!op->arguments.size()) {
- return;
- }
-
- if (op->op == GDScriptParser::OperatorNode::OP_CALL) {
- if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
- // Complete built-in function
- const GDScriptParser::BuiltInFunctionNode *fn = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]);
- MethodInfo mi = GDScriptFunctions::get_info(fn->function);
-
- if ((mi.name == "load" || mi.name == "preload") && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
- _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result);
- }
-
- r_arghint = _make_arguments_hint(mi, p_argidx);
- return;
-
- } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) {
- // Complete constructor
- const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]);
-
- List<MethodInfo> constructors;
- Variant::get_constructor_list(tn->vtype, &constructors);
+ if ((info.name == "load" || info.name == "preload") && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
+ _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result);
+ }
- int i = 0;
- for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) {
- if (p_argidx >= E->get().arguments.size()) {
- continue;
- }
- if (i > 0) {
- r_arghint += "\n";
- }
- r_arghint += _make_arguments_hint(E->get(), p_argidx);
- i++;
- }
- return;
- } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_SELF) {
- if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
- return;
+ r_arghint = _make_arguments_hint(info, p_argidx);
+ return;
+ } else if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) {
+ // Complete constructor
+ List<MethodInfo> constructors;
+ Variant::get_constructor_list(GDScriptParser::get_builtin_type(call->function_name), &constructors);
+
+ int i = 0;
+ for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) {
+ if (p_argidx >= E->get().arguments.size()) {
+ continue;
}
-
- base = p_context.base;
-
- const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]);
- function = id->name;
- base_type.has_type = true;
- base_type.kind = GDScriptParser::DataType::CLASS;
- base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
- _static = p_context.function && p_context.function->_static;
-
- if (function == "connect" && op->arguments.size() >= 4) {
- _guess_expression_type(p_context, op->arguments[3], connect_base);
+ if (i > 0) {
+ r_arghint += "\n";
}
+ r_arghint += _make_arguments_hint(E->get(), p_argidx);
+ i++;
+ }
+ return;
+ } else if (call->is_super || call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ base = p_context.base;
- } else {
- if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
- return;
- }
- const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]);
- function = id->name;
+ if (p_context.current_class) {
+ base_type = p_context.current_class->get_datatype();
+ _static = !p_context.current_function || p_context.current_function->is_static;
+ }
+ } else if (call->callee->type == GDScriptParser::Node::SUBSCRIPT) {
+ const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee);
+ if (subscript->is_attribute) {
GDScriptCompletionIdentifier ci;
- if (_guess_expression_type(p_context, op->arguments[0], ci)) {
+ if (_guess_expression_type(p_context, subscript->base, ci)) {
base_type = ci.type;
base = ci.value;
} else {
return;
}
- _static = ci.type.is_meta_type;
- if (function == "connect" && op->arguments.size() >= 4) {
- _guess_expression_type(p_context, op->arguments[3], connect_base);
- }
+ _static = base_type.is_meta_type;
}
} else {
- if (!p_context._class || op->arguments.size() < 1 || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) {
- return;
- }
- base_type.has_type = true;
- base_type.kind = GDScriptParser::DataType::CLASS;
- base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class);
- base_type.is_meta_type = p_context.function && p_context.function->_static;
- base = p_context.base;
-
- function = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name;
-
- if (function == "connect" && op->arguments.size() >= 4) {
- _guess_expression_type(p_context, op->arguments[3], connect_base);
- }
+ return;
}
GDScriptCompletionIdentifier ci;
ci.type = base_type;
ci.value = base;
- _find_call_arguments(p_context, ci, function, p_argidx, _static, r_result, r_arghint);
-
- if (function == "connect" && p_argidx == 2) {
- Map<String, ScriptCodeCompletionOption> methods;
- _find_identifiers_in_base(p_context, connect_base, true, methods);
- for (Map<String, ScriptCodeCompletionOption>::Element *E = methods.front(); E; E = E->next()) {
- ScriptCodeCompletionOption &option = E->value();
- option.insert_text = quote_style + option.display + quote_style;
- r_result.insert(option.display, option);
- }
- }
+ _find_call_arguments(p_context, ci, call->function_name, p_argidx, _static, r_result, r_arghint);
r_forced = r_result.size() > 0;
}
@@ -2520,150 +2356,213 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
GDScriptParser parser;
+ GDScriptAnalyzer analyzer(&parser);
+
+ parser.parse(p_code, p_path, true);
+ analyzer.analyze();
- parser.parse(p_code, p_path.get_base_dir(), false, p_path, true);
r_forced = false;
Map<String, ScriptCodeCompletionOption> options;
- GDScriptCompletionContext context;
- context._class = parser.get_completion_class();
- context.block = parser.get_completion_block();
- context.function = parser.get_completion_function();
- context.line = parser.get_completion_line();
-
- if (!context._class || context._class->owner == nullptr) {
- context.base = p_owner;
- context.base_path = p_path.get_base_dir();
- }
+ GDScriptParser::CompletionContext completion_context = parser.get_completion_context();
+ completion_context.base = p_owner;
bool is_function = false;
- switch (parser.get_completion_type()) {
- case GDScriptParser::COMPLETION_NONE: {
+ switch (completion_context.type) {
+ case GDScriptParser::COMPLETION_NONE:
+ break;
+ case GDScriptParser::COMPLETION_ANNOTATION: {
+ List<MethodInfo> annotations;
+ parser.get_annotation_list(&annotations);
+ for (const List<MethodInfo>::Element *E = annotations.front(); E != nullptr; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get().name.substr(1), ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ if (E->get().arguments.size() > 0) {
+ option.insert_text += "(";
+ }
+ options.insert(option.display, option);
+ }
+ r_forced = true;
+ } break;
+ case GDScriptParser::COMPLETION_ANNOTATION_ARGUMENTS: {
+ if (completion_context.node == nullptr || completion_context.node->type != GDScriptParser::Node::ANNOTATION) {
+ break;
+ }
+ const GDScriptParser::AnnotationNode *annotation = static_cast<const GDScriptParser::AnnotationNode *>(completion_context.node);
+ _find_annotation_arguments(annotation, completion_context.current_argument, quote_style, options);
+ r_forced = true;
} break;
case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: {
List<StringName> constants;
- Variant::get_constants_for_type(parser.get_completion_built_in_constant(), &constants);
- for (List<StringName>::Element *E = constants.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT);
+ Variant::get_constants_for_type(completion_context.builtin_type, &constants);
+ for (const List<StringName>::Element *E = constants.front(); E != nullptr; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT);
options.insert(option.display, option);
}
} break;
- case GDScriptParser::COMPLETION_PARENT_FUNCTION: {
- _find_identifiers_in_class(context, !context.function || context.function->_static, true, true, options);
+ case GDScriptParser::COMPLETION_INHERIT_TYPE: {
+ _list_available_types(true, completion_context, options);
+ r_forced = true;
} break;
- case GDScriptParser::COMPLETION_FUNCTION: {
- is_function = true;
- [[fallthrough]];
+ case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID: {
+ ScriptCodeCompletionOption option("void", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ options.insert(option.display, option);
}
- case GDScriptParser::COMPLETION_IDENTIFIER: {
- _find_identifiers(context, is_function, options);
+ [[fallthrough]];
+ case GDScriptParser::COMPLETION_TYPE_NAME: {
+ _list_available_types(false, completion_context, options);
+ r_forced = true;
} break;
- case GDScriptParser::COMPLETION_GET_NODE: {
- if (p_owner) {
- List<String> opts;
- p_owner->get_argument_options("get_node", 0, &opts);
-
- for (List<String>::Element *E = opts.front(); E; E = E->next()) {
- String opt = E->get().strip_edges();
- if (opt.is_quoted()) {
- r_forced = true;
- String idopt = opt.unquote();
- if (idopt.replace("/", "_").is_valid_identifier()) {
- ScriptCodeCompletionOption option(idopt, ScriptCodeCompletionOption::KIND_NODE_PATH);
- options.insert(option.display, option);
- } else {
- ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH);
- options.insert(option.display, option);
- }
- }
+ case GDScriptParser::COMPLETION_PROPERTY_DECLARATION_OR_TYPE: {
+ _list_available_types(false, completion_context, options);
+ ScriptCodeCompletionOption get("get", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ options.insert(get.display, get);
+ ScriptCodeCompletionOption set("set", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ options.insert(set.display, set);
+ r_forced = true;
+ } break;
+ case GDScriptParser::COMPLETION_PROPERTY_DECLARATION: {
+ ScriptCodeCompletionOption get("get", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ options.insert(get.display, get);
+ ScriptCodeCompletionOption set("set", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ options.insert(set.display, set);
+ r_forced = true;
+ } break;
+ case GDScriptParser::COMPLETION_PROPERTY_METHOD: {
+ if (!completion_context.current_class) {
+ break;
+ }
+ for (int i = 0; i < completion_context.current_class->members.size(); i++) {
+ const GDScriptParser::ClassNode::Member &member = completion_context.current_class->members[i];
+ if (member.type != GDScriptParser::ClassNode::Member::FUNCTION) {
+ continue;
}
-
- // Get autoloads
- List<PropertyInfo> props;
- ProjectSettings::get_singleton()->get_property_list(&props);
-
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- String s = E->get().name;
- if (!s.begins_with("autoload/")) {
- continue;
- }
- String name = s.get_slice("/", 1);
- ScriptCodeCompletionOption option(quote_style + "/root/" + name + quote_style, ScriptCodeCompletionOption::KIND_NODE_PATH);
- options.insert(option.display, option);
+ if (member.function->is_static) {
+ continue;
}
+ ScriptCodeCompletionOption option(member.function->identifier->name, ScriptCodeCompletionOption::KIND_FUNCTION);
+ options.insert(option.display, option);
}
+ r_forced = true;
} break;
- case GDScriptParser::COMPLETION_METHOD: {
- is_function = true;
- [[fallthrough]];
- }
- case GDScriptParser::COMPLETION_INDEX: {
- const GDScriptParser::Node *node = parser.get_completion_node();
- if (node->type != GDScriptParser::Node::TYPE_OPERATOR) {
+ case GDScriptParser::COMPLETION_ASSIGN: {
+ GDScriptCompletionIdentifier type;
+ if (!completion_context.node || completion_context.node->type != GDScriptParser::Node::ASSIGNMENT) {
break;
}
- const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(node);
- if (op->arguments.size() < 1) {
+ if (!_guess_expression_type(completion_context, static_cast<const GDScriptParser::AssignmentNode *>(completion_context.node)->assignee, type)) {
+ _find_identifiers(completion_context, false, options);
+ r_forced = true;
break;
}
+ if (!type.enumeration.empty()) {
+ _find_enumeration_candidates(completion_context, type.enumeration, options);
+ r_forced = options.size() > 0;
+ } else {
+ _find_identifiers(completion_context, false, options);
+ r_forced = true;
+ }
+ } break;
+ case GDScriptParser::COMPLETION_METHOD:
+ is_function = true;
+ [[fallthrough]];
+ case GDScriptParser::COMPLETION_IDENTIFIER: {
+ _find_identifiers(completion_context, is_function, options);
+ } break;
+ case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD:
+ is_function = true;
+ [[fallthrough]];
+ case GDScriptParser::COMPLETION_ATTRIBUTE: {
+ r_forced = true;
+ const GDScriptParser::SubscriptNode *attr = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node);
+ if (attr->base) {
+ GDScriptCompletionIdentifier base;
+ if (!_guess_expression_type(completion_context, attr->base, base)) {
+ break;
+ }
+
+ _find_identifiers_in_base(base, is_function, options);
+ }
+ } break;
+ case GDScriptParser::COMPLETION_SUBSCRIPT: {
+ const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node);
GDScriptCompletionIdentifier base;
- if (!_guess_expression_type(context, op->arguments[0], base)) {
+ if (!_guess_expression_type(completion_context, subscript->base, base)) {
break;
}
- GDScriptCompletionContext c = context;
- c.function = nullptr;
- c.block = nullptr;
+ GDScriptParser::CompletionContext c = completion_context;
+ c.current_function = nullptr;
+ c.current_suite = nullptr;
c.base = base.value.get_type() == Variant::OBJECT ? base.value.operator Object *() : nullptr;
if (base.type.kind == GDScriptParser::DataType::CLASS) {
- c._class = base.type.class_type;
+ c.current_class = base.type.class_type;
} else {
- c._class = nullptr;
+ c.current_class = nullptr;
}
- _find_identifiers_in_base(c, base, is_function, options);
+ _find_identifiers_in_base(base, false, options);
+ } break;
+ case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: {
+ if (!completion_context.current_class) {
+ break;
+ }
+ const GDScriptParser::TypeNode *type = static_cast<const GDScriptParser::TypeNode *>(completion_context.node);
+ bool found = true;
+ GDScriptCompletionIdentifier base;
+ base.type.kind = GDScriptParser::DataType::CLASS;
+ base.type.type_source = GDScriptParser::DataType::INFERRED;
+ base.type.is_constant = true;
+ base.type.class_type = completion_context.current_class;
+ base.value = completion_context.base;
+
+ for (int i = 0; i < completion_context.current_argument; i++) {
+ GDScriptCompletionIdentifier ci;
+ if (!_guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci)) {
+ found = false;
+ break;
+ }
+ base = ci;
+ }
+
+ // TODO: Improve this to only list types.
+ if (found) {
+ _find_identifiers_in_base(base, false, options);
+ }
+ r_forced = true;
+ } break;
+ case GDScriptParser::COMPLETION_RESOURCE_PATH: {
+ if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) {
+ _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options);
+ r_forced = true;
+ }
} break;
case GDScriptParser::COMPLETION_CALL_ARGUMENTS: {
- _find_call_arguments(context, parser.get_completion_node(), parser.get_completion_argument_index(), options, r_forced, r_call_hint);
+ if (!completion_context.node) {
+ break;
+ }
+ _find_call_arguments(completion_context, completion_context.node, completion_context.current_argument, options, r_forced, r_call_hint);
} break;
- case GDScriptParser::COMPLETION_VIRTUAL_FUNC: {
- GDScriptParser::DataType native_type = context._class->base_type;
- while (native_type.has_type && native_type.kind != GDScriptParser::DataType::NATIVE) {
+ case GDScriptParser::COMPLETION_OVERRIDE_METHOD: {
+ GDScriptParser::DataType native_type = completion_context.current_class->base_type;
+ while (native_type.is_set() && native_type.kind != GDScriptParser::DataType::NATIVE) {
switch (native_type.kind) {
case GDScriptParser::DataType::CLASS: {
native_type = native_type.class_type->base_type;
} break;
- case GDScriptParser::DataType::GDSCRIPT: {
- Ref<GDScript> gds = native_type.script_type;
- if (gds.is_valid()) {
- Ref<GDScript> base = gds->get_base_script();
- if (base.is_valid()) {
- native_type.script_type = base;
- } else {
- native_type.native_type = gds->get_instance_base_type();
- native_type.kind = GDScriptParser::DataType::NATIVE;
- }
- } else {
- native_type.has_type = false;
- }
- } break;
default: {
- native_type.has_type = false;
+ native_type.kind = GDScriptParser::DataType::UNRESOLVED;
} break;
}
}
- if (!native_type.has_type) {
+ if (!native_type.is_set()) {
break;
}
- StringName class_name = native_type.native_type;
+ StringName class_name = _get_real_class_name(native_type.native_type);
if (!ClassDB::class_exists(class_name)) {
- class_name = String("_") + class_name;
- if (!ClassDB::class_exists(class_name)) {
- break;
- }
+ break;
}
bool use_type_hint = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints").operator bool();
@@ -2715,245 +2614,41 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
options.insert(option.display, option);
}
} break;
- case GDScriptParser::COMPLETION_YIELD: {
- const GDScriptParser::Node *node = parser.get_completion_node();
-
- GDScriptCompletionContext c = context;
- c.line = node->line;
- GDScriptCompletionIdentifier type;
- if (!_guess_expression_type(c, node, type)) {
- break;
- }
+ case GDScriptParser::COMPLETION_GET_NODE: {
+ if (p_owner) {
+ List<String> opts;
+ p_owner->get_argument_options("get_node", 0, &opts);
- GDScriptParser::DataType base_type = type.type;
- while (base_type.has_type) {
- switch (base_type.kind) {
- case GDScriptParser::DataType::CLASS: {
- for (int i = 0; i < base_type.class_type->_signals.size(); i++) {
- ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL);
- option.insert_text = quote_style + option.display + quote_style;
+ for (List<String>::Element *E = opts.front(); E; E = E->next()) {
+ String opt = E->get().strip_edges();
+ if (opt.is_quoted()) {
+ r_forced = true;
+ String idopt = opt.unquote();
+ if (idopt.replace("/", "_").is_valid_identifier()) {
+ ScriptCodeCompletionOption option(idopt, ScriptCodeCompletionOption::KIND_NODE_PATH);
options.insert(option.display, option);
- }
- base_type = base_type.class_type->base_type;
- } break;
- case GDScriptParser::DataType::SCRIPT:
- case GDScriptParser::DataType::GDSCRIPT: {
- Ref<Script> scr = base_type.script_type;
- if (scr.is_valid()) {
- List<MethodInfo> signals;
- scr->get_script_signal_list(&signals);
- for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL);
- options.insert(option.display, option);
- }
- Ref<Script> base_script = scr->get_base_script();
- if (base_script.is_valid()) {
- base_type.script_type = base_script;
- } else {
- base_type.kind = GDScriptParser::DataType::NATIVE;
- base_type.native_type = scr->get_instance_base_type();
- }
} else {
- base_type.has_type = false;
- }
- } break;
- case GDScriptParser::DataType::NATIVE: {
- base_type.has_type = false;
-
- StringName class_name = base_type.native_type;
- if (!ClassDB::class_exists(class_name)) {
- class_name = String("_") + class_name;
- if (!ClassDB::class_exists(class_name)) {
- break;
- }
- }
-
- List<MethodInfo> signals;
- ClassDB::get_signal_list(class_name, &signals);
- for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL);
+ ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH);
options.insert(option.display, option);
}
- } break;
- default: {
- base_type.has_type = false;
}
}
- }
- } break;
- case GDScriptParser::COMPLETION_RESOURCE_PATH: {
- if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) {
- _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options);
- r_forced = true;
- }
- } break;
- case GDScriptParser::COMPLETION_ASSIGN: {
- GDScriptCompletionIdentifier type;
- if (!_guess_expression_type(context, parser.get_completion_node(), type)) {
- break;
- }
- if (!type.enumeration.empty()) {
- _find_enumeration_candidates(type.enumeration, options);
- r_forced = options.size() > 0;
- }
- } break;
- case GDScriptParser::COMPLETION_TYPE_HINT: {
- const GDScriptParser::ClassNode *clss = context._class;
- while (clss) {
- for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = clss->constant_expressions.front(); E; E = E->next()) {
- GDScriptCompletionIdentifier constant;
- GDScriptCompletionContext c = context;
- c.function = nullptr;
- c.block = nullptr;
- c.line = E->value().expression->line;
- if (_guess_expression_type(c, E->value().expression, constant)) {
- if (constant.type.has_type && constant.type.is_meta_type) {
- ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
- options.insert(option.display, option);
- }
- }
- }
- for (int i = 0; i < clss->subclasses.size(); i++) {
- if (clss->subclasses[i]->name != StringName()) {
- ScriptCodeCompletionOption option(clss->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS);
- options.insert(option.display, option);
- }
- }
- clss = clss->owner;
- for (int i = 0; i < Variant::VARIANT_MAX; i++) {
- ScriptCodeCompletionOption option(Variant::get_type_name((Variant::Type)i), ScriptCodeCompletionOption::KIND_CLASS);
- options.insert(option.display, option);
- }
- List<PropertyInfo> props;
- ProjectSettings::get_singleton()->get_property_list(&props);
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- String s = E->get().name;
- if (!s.begins_with("autoload/")) {
- continue;
- }
- ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CLASS);
- options.insert(option.display, option);
- }
- }
+ // Get autoloads.
+ Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
- List<StringName> native_classes;
- ClassDB::get_class_list(&native_classes);
- for (List<StringName>::Element *E = native_classes.front(); E; E = E->next()) {
- String class_name = E->get().operator String();
- if (class_name.begins_with("_")) {
- class_name = class_name.right(1);
- }
- if (Engine::get_singleton()->has_singleton(class_name)) {
- continue;
+ for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
+ String name = E->key();
+ ScriptCodeCompletionOption option(quote_style + "/root/" + name + quote_style, ScriptCodeCompletionOption::KIND_NODE_PATH);
+ options.insert(option.display, option);
}
- ScriptCodeCompletionOption option(class_name, ScriptCodeCompletionOption::KIND_CLASS);
- options.insert(option.display, option);
- }
-
- // Named scripts
- List<StringName> named_scripts;
- ScriptServer::get_global_class_list(&named_scripts);
- for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
- options.insert(option.display, option);
- }
-
- if (parser.get_completion_identifier_is_function()) {
- ScriptCodeCompletionOption option("void", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
- options.insert(option.display, option);
}
- r_forced = true;
} break;
- case GDScriptParser::COMPLETION_TYPE_HINT_INDEX: {
- GDScriptCompletionIdentifier base;
- String index = parser.get_completion_cursor().operator String();
- if (!_guess_identifier_type(context, index.get_slice(".", 0), base)) {
+ case GDScriptParser::COMPLETION_SUPER_METHOD: {
+ if (!completion_context.current_class) {
break;
}
-
- GDScriptCompletionContext c = context;
- c._class = nullptr;
- c.function = nullptr;
- c.block = nullptr;
- bool finding = true;
- index = index.right(index.find(".") + 1);
- while (index.find(".") != -1) {
- String id = index.get_slice(".", 0);
-
- GDScriptCompletionIdentifier sub_base;
- if (!_guess_identifier_type_from_base(c, base, id, sub_base)) {
- finding = false;
- break;
- }
- index = index.right(index.find(".") + 1);
- base = sub_base;
- }
-
- if (!finding) {
- break;
- }
-
- GDScriptParser::DataType base_type = base.type;
- while (base_type.has_type) {
- switch (base_type.kind) {
- case GDScriptParser::DataType::CLASS: {
- if (base_type.class_type) {
- for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = base_type.class_type->constant_expressions.front(); E; E = E->next()) {
- GDScriptCompletionIdentifier constant;
- GDScriptCompletionContext c2 = context;
- c2._class = base_type.class_type;
- c2.function = nullptr;
- c2.block = nullptr;
- c2.line = E->value().expression->line;
- if (_guess_expression_type(c2, E->value().expression, constant)) {
- if (constant.type.has_type && constant.type.is_meta_type) {
- ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
- options.insert(option.display, option);
- }
- }
- }
- for (int i = 0; i < base_type.class_type->subclasses.size(); i++) {
- if (base_type.class_type->subclasses[i]->name != StringName()) {
- ScriptCodeCompletionOption option(base_type.class_type->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS);
- options.insert(option.display, option);
- }
- }
-
- base_type = base_type.class_type->base_type;
- } else {
- base_type.has_type = false;
- }
- } break;
- case GDScriptParser::DataType::SCRIPT:
- case GDScriptParser::DataType::GDSCRIPT: {
- Ref<Script> scr = base_type.script_type;
- if (scr.is_valid()) {
- Map<StringName, Variant> constants;
- scr->get_constants(&constants);
- for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) {
- Ref<Script> const_scr = E->value();
- if (const_scr.is_valid()) {
- ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
- options.insert(option.display, option);
- }
- }
- Ref<Script> base_script = scr->get_base_script();
- if (base_script.is_valid()) {
- base_type.script_type = base_script;
- } else {
- base_type.has_type = false;
- }
- } else {
- base_type.has_type = false;
- }
- } break;
- default: {
- base_type.has_type = false;
- } break;
- }
- }
- r_forced = options.size() > 0;
+ _find_identifiers_in_class(completion_context.current_class, true, false, true, options);
} break;
}
@@ -3060,53 +2755,18 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t
static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, const String &p_symbol, bool p_is_function, GDScriptLanguage::LookupResult &r_result) {
GDScriptParser::DataType base_type = p_base;
- while (base_type.has_type) {
+ while (base_type.is_set()) {
switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: {
if (base_type.class_type) {
- if (p_is_function) {
- for (int i = 0; i < base_type.class_type->functions.size(); i++) {
- if (base_type.class_type->functions[i]->name == p_symbol) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = base_type.class_type->functions[i]->line;
- return OK;
- }
- }
- for (int i = 0; i < base_type.class_type->static_functions.size(); i++) {
- if (base_type.class_type->static_functions[i]->name == p_symbol) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = base_type.class_type->static_functions[i]->line;
- return OK;
- }
- }
- } else {
- if (base_type.class_type->constant_expressions.has(p_symbol)) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = base_type.class_type->constant_expressions[p_symbol].expression->line;
- return OK;
- }
-
- for (int i = 0; i < base_type.class_type->variables.size(); i++) {
- if (base_type.class_type->variables[i].identifier == p_symbol) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = base_type.class_type->variables[i].line;
- return OK;
- }
- }
-
- for (int i = 0; i < base_type.class_type->subclasses.size(); i++) {
- if (base_type.class_type->subclasses[i]->name == p_symbol) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = base_type.class_type->subclasses[i]->line;
- return OK;
- }
- }
+ if (base_type.class_type->has_member(p_symbol)) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
+ r_result.location = base_type.class_type->get_member(p_symbol).get_line();
}
base_type = base_type.class_type->base_type;
}
} break;
- case GDScriptParser::DataType::SCRIPT:
- case GDScriptParser::DataType::GDSCRIPT: {
+ case GDScriptParser::DataType::SCRIPT: {
Ref<Script> scr = base_type.script_type;
if (scr.is_valid()) {
int line = scr->get_member_line(p_symbol);
@@ -3124,17 +2784,14 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
base_type.native_type = scr->get_instance_base_type();
}
} else {
- base_type.has_type = false;
+ base_type.kind = GDScriptParser::DataType::UNRESOLVED;
}
} break;
case GDScriptParser::DataType::NATIVE: {
- StringName class_name = base_type.native_type;
+ StringName class_name = _get_real_class_name(base_type.native_type);
if (!ClassDB::class_exists(class_name)) {
- class_name = String("_") + class_name;
- if (!ClassDB::class_exists(class_name)) {
- base_type.has_type = false;
- break;
- }
+ base_type.kind = GDScriptParser::DataType::UNRESOLVED;
+ break;
}
if (ClassDB::has_method(class_name, p_symbol, true)) {
@@ -3174,15 +2831,11 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
}
- List<PropertyInfo> properties;
- ClassDB::get_property_list(class_name, &properties, true);
- for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
- if (E->get().name == p_symbol) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
- r_result.class_name = base_type.native_type;
- r_result.class_member = p_symbol;
- return OK;
- }
+ if (ClassDB::has_property(class_name, p_symbol, true)) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
+ r_result.class_name = base_type.native_type;
+ r_result.class_member = p_symbol;
+ return OK;
}
StringName parent = ClassDB::get_parent_class(class_name);
@@ -3193,11 +2846,11 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
base_type.native_type = parent;
}
} else {
- base_type.has_type = false;
+ base_type.kind = GDScriptParser::DataType::UNRESOLVED;
}
} break;
case GDScriptParser::DataType::BUILTIN: {
- base_type.has_type = false;
+ base_type.kind = GDScriptParser::DataType::UNRESOLVED;
if (Variant::has_constant(base_type.builtin_type, p_symbol)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
@@ -3213,7 +2866,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
v = v_ref;
} else {
Callable::CallError err;
- v = Variant::construct(base_type.builtin_type, nullptr, 0, err);
+ v = Variant::construct(base_type.builtin_type, NULL, 0, err);
if (err.error != Callable::CallError::CALL_OK) {
break;
}
@@ -3236,7 +2889,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
} break;
default: {
- base_type.has_type = false;
+ base_type.kind = GDScriptParser::DataType::UNRESOLVED;
} break;
}
}
@@ -3285,26 +2938,18 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
}
GDScriptParser parser;
- parser.parse(p_code, p_path.get_base_dir(), false, p_path, true);
-
- if (parser.get_completion_type() == GDScriptParser::COMPLETION_NONE) {
- return ERR_CANT_RESOLVE;
- }
+ parser.parse(p_code, p_path, true);
+ GDScriptAnalyzer analyzer(&parser);
+ analyzer.analyze();
- GDScriptCompletionContext context;
- context._class = parser.get_completion_class();
- context.function = parser.get_completion_function();
- context.block = parser.get_completion_block();
- context.line = parser.get_completion_line();
- context.base = p_owner;
- context.base_path = p_path.get_base_dir();
+ GDScriptParser::CompletionContext context = parser.get_completion_context();
- if (context._class && context._class->extends_class.size() > 0) {
+ if (context.current_class && context.current_class->extends.size() > 0) {
bool success = false;
- ClassDB::get_integer_constant(context._class->extends_class[0], p_symbol, &success);
+ ClassDB::get_integer_constant(context.current_class->extends[0], p_symbol, &success);
if (success) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
- r_result.class_name = context._class->extends_class[0];
+ r_result.class_name = context.current_class->extends[0];
r_result.class_member = p_symbol;
return OK;
}
@@ -3312,57 +2957,40 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
bool is_function = false;
- switch (parser.get_completion_type()) {
+ switch (context.type) {
case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
- r_result.class_name = Variant::get_type_name(parser.get_completion_built_in_constant());
+ r_result.class_name = Variant::get_type_name(context.builtin_type);
r_result.class_member = p_symbol;
return OK;
} break;
- case GDScriptParser::COMPLETION_PARENT_FUNCTION:
- case GDScriptParser::COMPLETION_FUNCTION: {
+ case GDScriptParser::COMPLETION_SUPER_METHOD:
+ case GDScriptParser::COMPLETION_METHOD: {
is_function = true;
[[fallthrough]];
}
case GDScriptParser::COMPLETION_IDENTIFIER: {
- if (!is_function) {
- is_function = parser.get_completion_identifier_is_function();
- }
-
GDScriptParser::DataType base_type;
- if (context._class) {
- if (parser.get_completion_type() != GDScriptParser::COMPLETION_PARENT_FUNCTION) {
- base_type.has_type = true;
- base_type.kind = GDScriptParser::DataType::CLASS;
- base_type.class_type = const_cast<GDScriptParser::ClassNode *>(context._class);
+ if (context.current_class) {
+ if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) {
+ base_type = context.current_class->get_datatype();
} else {
- base_type = context._class->base_type;
+ base_type = context.current_class->base_type;
}
} else {
break;
}
- if (!is_function && context.block) {
- // Lookup local variables
- const GDScriptParser::BlockNode *block = context.block;
- while (block) {
- if (block->variables.has(p_symbol)) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = block->variables[p_symbol]->line;
- return OK;
- }
- block = block->parent_block;
- }
- }
-
- if (context.function && context.function->name != StringName()) {
- // Lookup function arguments
- for (int i = 0; i < context.function->arguments.size(); i++) {
- if (context.function->arguments[i] == p_symbol) {
+ if (!is_function && context.current_suite) {
+ // Lookup local variables.
+ const GDScriptParser::SuiteNode *suite = context.current_suite;
+ while (suite) {
+ if (suite->has_local(p_symbol)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = context.function->line;
+ r_result.location = suite->get_local(p_symbol).start_line;
return OK;
}
+ suite = suite->parent_block;
}
}
@@ -3371,38 +2999,27 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
}
if (!is_function) {
- // Guess in autoloads as singletons
- List<PropertyInfo> props;
- ProjectSettings::get_singleton()->get_property_list(&props);
-
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- String s = E->get().name;
- if (!s.begins_with("autoload/")) {
- continue;
- }
- String name = s.get_slice("/", 1);
- if (name == String(p_symbol)) {
- String path = ProjectSettings::get_singleton()->get(s);
- if (path.begins_with("*")) {
- String script = path.substr(1, path.length());
-
- if (!script.ends_with(".gd")) {
- // Not a script, try find the script anyway,
- // may have some success
- script = script.get_basename() + ".gd";
- }
+ // Guess in autoloads as singletons.
+ if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) {
+ const ProjectSettings::AutoloadInfo &singleton = ProjectSettings::get_singleton()->get_autoload(p_symbol);
+ if (singleton.is_singleton) {
+ String script = singleton.path;
+ if (!script.ends_with(".gd")) {
+ // Not a script, try find the script anyway,
+ // may have some success.
+ script = script.get_basename() + ".gd";
+ }
- if (FileAccess::exists(script)) {
- r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
- r_result.location = 0;
- r_result.script = ResourceLoader::load(script);
- return OK;
- }
+ if (FileAccess::exists(script)) {
+ r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
+ r_result.location = 0;
+ r_result.script = ResourceLoader::load(script);
+ return OK;
}
}
}
- // Global
+ // Global.
Map<StringName, int> classes = GDScriptLanguage::get_singleton()->get_global_map();
if (classes.has(p_symbol)) {
Variant value = GDScriptLanguage::get_singleton()->get_global_array()[classes[p_symbol]];
@@ -3429,7 +3046,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
// We cannot determine the exact nature of the identifier here
// Otherwise these codes would work
StringName enumName = ClassDB::get_integer_constant_enum("@GlobalScope", p_symbol, true);
- if (enumName != nullptr) {
+ if (enumName != NULL) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM;
r_result.class_name = "@GlobalScope";
r_result.class_member = enumName;
@@ -3449,17 +3066,20 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
}
}
} break;
- case GDScriptParser::COMPLETION_METHOD: {
+ case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD: {
is_function = true;
[[fallthrough]];
}
- case GDScriptParser::COMPLETION_INDEX: {
- const GDScriptParser::Node *node = parser.get_completion_node();
- if (node->type != GDScriptParser::Node::TYPE_OPERATOR) {
+ case GDScriptParser::COMPLETION_ATTRIBUTE: {
+ if (context.node->type != GDScriptParser::Node::SUBSCRIPT) {
+ break;
+ }
+ const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(context.node);
+ if (!subscript->is_attribute) {
break;
}
GDScriptCompletionIdentifier base;
- if (!_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], base)) {
+ if (!_guess_expression_type(context, subscript->base, base)) {
break;
}
@@ -3467,18 +3087,16 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
return OK;
}
} break;
- case GDScriptParser::COMPLETION_VIRTUAL_FUNC: {
- GDScriptParser::DataType base_type = context._class->base_type;
+ case GDScriptParser::COMPLETION_OVERRIDE_METHOD: {
+ GDScriptParser::DataType base_type = context.current_class->base_type;
if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) {
return OK;
}
} break;
- case GDScriptParser::COMPLETION_TYPE_HINT: {
- GDScriptParser::DataType base_type = context._class->base_type;
- base_type.has_type = true;
- base_type.kind = GDScriptParser::DataType::CLASS;
- base_type.class_type = const_cast<GDScriptParser::ClassNode *>(context._class);
+ case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID:
+ case GDScriptParser::COMPLETION_TYPE_NAME: {
+ GDScriptParser::DataType base_type = context.current_class->get_datatype();
if (_lookup_symbol_from_base(base_type, p_symbol, false, r_result) == OK) {
return OK;
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index 1aab71d161..a4e37a79f8 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -210,12 +210,12 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
&&OPCODE_CONSTRUCT_DICTIONARY, \
&&OPCODE_CALL, \
&&OPCODE_CALL_RETURN, \
+ &&OPCODE_CALL_ASYNC, \
&&OPCODE_CALL_BUILT_IN, \
&&OPCODE_CALL_SELF, \
&&OPCODE_CALL_SELF_BASE, \
- &&OPCODE_YIELD, \
- &&OPCODE_YIELD_SIGNAL, \
- &&OPCODE_YIELD_RESUME, \
+ &&OPCODE_AWAIT, \
+ &&OPCODE_AWAIT_RESUME, \
&&OPCODE_JUMP, \
&&OPCODE_JUMP_IF, \
&&OPCODE_JUMP_IF_NOT, \
@@ -227,7 +227,8 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
&&OPCODE_BREAKPOINT, \
&&OPCODE_LINE, \
&&OPCODE_END \
- };
+ }; \
+ static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum.");
#define OPCODE(m_op) \
m_op:
@@ -280,7 +281,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
int line = _initial_line;
if (p_state) {
- //use existing (supplied) state (yielded)
+ //use existing (supplied) state (awaited)
stack = (Variant *)p_state->stack.ptr();
call_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check
line = p_state->line;
@@ -411,7 +412,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
profile.frame_call_count++;
}
bool exit_ok = false;
- bool yielded = false;
+ bool awaited = false;
#endif
#ifdef DEBUG_ENABLED
@@ -1006,10 +1007,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_CALL_ASYNC)
OPCODE(OPCODE_CALL_RETURN)
OPCODE(OPCODE_CALL) {
CHECK_SPACE(4);
- bool call_ret = _code_ptr[ip] == OPCODE_CALL_RETURN;
+ bool call_ret = _code_ptr[ip] != OPCODE_CALL;
+#ifdef DEBUG_ENABLED
+ bool call_async = _code_ptr[ip] == OPCODE_CALL_ASYNC;
+#endif
int argc = _code_ptr[ip + 1];
GET_VARIANT_PTR(base, 2);
@@ -1040,6 +1045,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (call_ret) {
GET_VARIANT_PTR(ret, argc);
base->call_ptr(*methodname, (const Variant **)argptrs, argc, ret, err);
+#ifdef DEBUG_ENABLED
+ if (!call_async && ret->get_type() == Variant::OBJECT) {
+ // Check if getting a function state without await.
+ bool was_freed = false;
+ Object *obj = ret->get_validated_object_with_check(was_freed);
+
+ if (was_freed) {
+ err_text = "Got a freed object as a result of the call.";
+ OPCODE_BREAK;
+ }
+ if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) {
+ err_text = R"(Trying to call an async function without "await".)";
+ OPCODE_BREAK;
+ }
+ }
+#endif
} else {
base->call_ptr(*methodname, (const Variant **)argptrs, argc, nullptr, err);
}
@@ -1192,105 +1213,96 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
- OPCODE(OPCODE_YIELD)
- OPCODE(OPCODE_YIELD_SIGNAL) {
- int ipofs = 1;
- if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) {
- CHECK_SPACE(4);
- ipofs += 2;
- } else {
- CHECK_SPACE(2);
- }
+ OPCODE(OPCODE_AWAIT) {
+ int ipofs = 2;
+ CHECK_SPACE(3);
- Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState);
- gdfs->function = this;
+ //do the oneshot connect
+ GET_VARIANT_PTR(argobj, 1);
+
+ Signal sig;
+ bool is_signal = true;
- gdfs->state.stack.resize(alloca_size);
- //copy variant stack
- for (int i = 0; i < _stack_size; i++) {
- memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i]));
- }
- gdfs->state.stack_size = _stack_size;
- gdfs->state.self = self;
- gdfs->state.alloca_size = alloca_size;
- gdfs->state.ip = ip + ipofs;
- gdfs->state.line = line;
- gdfs->state.script = _script;
{
- MutexLock lock(GDScriptLanguage::get_singleton()->lock);
- _script->pending_func_states.add(&gdfs->scripts_list);
- if (p_instance) {
- gdfs->state.instance = p_instance;
- p_instance->pending_func_states.add(&gdfs->instances_list);
- } else {
- gdfs->state.instance = nullptr;
- }
- }
-#ifdef DEBUG_ENABLED
- gdfs->state.function_name = name;
- gdfs->state.script_path = _script->get_path();
-#endif
- gdfs->state.defarg = defarg;
- gdfs->function = this;
+ Variant result = *argobj;
- retvalue = gdfs;
+ if (argobj->get_type() == Variant::OBJECT) {
+ bool was_freed = false;
+ Object *obj = argobj->get_validated_object_with_check(was_freed);
- if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) {
- //do the oneshot connect
- GET_VARIANT_PTR(argobj, 1);
- GET_VARIANT_PTR(argname, 2);
+ if (was_freed) {
+ err_text = "Trying to await on a freed object.";
+ OPCODE_BREAK;
+ }
-#ifdef DEBUG_ENABLED
- if (argobj->get_type() != Variant::OBJECT) {
- err_text = "First argument of yield() not of type object.";
- OPCODE_BREAK;
- }
- if (argname->get_type() != Variant::STRING) {
- err_text = "Second argument of yield() not a string (for signal name).";
- OPCODE_BREAK;
+ // Is this even possible to be null at this point?
+ if (obj) {
+ if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) {
+ static StringName completed = _scs_create("completed");
+ result = Signal(obj, completed);
+ }
+ }
}
-#endif
-#ifdef DEBUG_ENABLED
- bool was_freed;
- Object *obj = argobj->get_validated_object_with_check(was_freed);
- String signal = argname->operator String();
-
- if (was_freed) {
- err_text = "First argument of yield() is a previously freed instance.";
- OPCODE_BREAK;
+ if (result.get_type() != Variant::SIGNAL) {
+ ip += 4; // Skip OPCODE_AWAIT_RESUME and its data.
+ // The stack pointer should be the same, so we don't need to set a return value.
+ is_signal = false;
+ } else {
+ sig = result;
}
+ }
- if (!obj) {
- err_text = "First argument of yield() is null.";
- OPCODE_BREAK;
+ if (is_signal) {
+ Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState);
+ gdfs->function = this;
+
+ gdfs->state.stack.resize(alloca_size);
+ //copy variant stack
+ for (int i = 0; i < _stack_size; i++) {
+ memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i]));
}
- if (signal.length() == 0) {
- err_text = "Second argument of yield() is an empty string (for signal name).";
- OPCODE_BREAK;
+ gdfs->state.stack_size = _stack_size;
+ gdfs->state.self = self;
+ gdfs->state.alloca_size = alloca_size;
+ gdfs->state.ip = ip + ipofs;
+ gdfs->state.line = line;
+ gdfs->state.script = _script;
+ {
+ MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+ _script->pending_func_states.add(&gdfs->scripts_list);
+ if (p_instance) {
+ gdfs->state.instance = p_instance;
+ p_instance->pending_func_states.add(&gdfs->instances_list);
+ } else {
+ gdfs->state.instance = nullptr;
+ }
}
+#ifdef DEBUG_ENABLED
+ gdfs->state.function_name = name;
+ gdfs->state.script_path = _script->get_path();
+#endif
+ gdfs->state.defarg = defarg;
+ gdfs->function = this;
- Error err = obj->connect_compat(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT);
+ retvalue = gdfs;
+
+ Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT);
if (err != OK) {
- err_text = "Error connecting to signal: " + signal + " during yield().";
+ err_text = "Error connecting to signal: " + sig.get_name() + " during await.";
OPCODE_BREAK;
}
-#else
- Object *obj = argobj->operator Object *();
- String signal = argname->operator String();
-
- obj->connect_compat(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT);
-#endif
- }
#ifdef DEBUG_ENABLED
- exit_ok = true;
- yielded = true;
+ exit_ok = true;
+ awaited = true;
#endif
- OPCODE_BREAK;
+ OPCODE_BREAK;
+ }
}
+ DISPATCH_OPCODE; // Needed for synchronous calls (when result is immediately available).
- OPCODE(OPCODE_YIELD_RESUME) {
+ OPCODE(OPCODE_AWAIT_RESUME) {
CHECK_SPACE(2);
#ifdef DEBUG_ENABLED
if (!p_state) {
@@ -1556,11 +1568,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
}
- // Check if this is the last time the function is resuming from yield
- // Will be true if never yielded as well
+ // Check if this is the last time the function is resuming from await
+ // Will be true if never awaited as well
// When it's the last resume it will postpone the exit from stack,
// so the debugger knows which function triggered the resume of the next function (if any)
- if (!p_state || yielded) {
+ if (!p_state || awaited) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->exit_function();
}
@@ -1786,14 +1798,14 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
if (!scripts_list.in_list()) {
#ifdef DEBUG_ENABLED
- ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after yield, but script is gone. At script: " + state.script_path + ":" + itos(state.line));
+ ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but script is gone. At script: " + state.script_path + ":" + itos(state.line));
#else
return Variant();
#endif
}
if (state.instance && !instances_list.in_list()) {
#ifdef DEBUG_ENABLED
- ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after yield, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line));
+ ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line));
#else
return Variant();
#endif
@@ -1810,7 +1822,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
bool completed = true;
// If the return value is a GDScriptFunctionState reference,
- // then the function did yield again after resuming.
+ // then the function did awaited again after resuming.
if (ret.is_ref()) {
GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret);
if (gdfs && gdfs->function == function) {
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 583eab744a..771baf6a08 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -180,12 +180,12 @@ public:
OPCODE_CONSTRUCT_DICTIONARY,
OPCODE_CALL,
OPCODE_CALL_RETURN,
+ OPCODE_CALL_ASYNC,
OPCODE_CALL_BUILT_IN,
OPCODE_CALL_SELF,
OPCODE_CALL_SELF_BASE,
- OPCODE_YIELD,
- OPCODE_YIELD_SIGNAL,
- OPCODE_YIELD_RESUME,
+ OPCODE_AWAIT,
+ OPCODE_AWAIT_RESUME,
OPCODE_JUMP,
OPCODE_JUMP_IF,
OPCODE_JUMP_IF_NOT,
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index d4258c385e..7f2a62a8e9 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -146,12 +146,14 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
if (p_arg_count < m_count) { \
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
r_error.argument = m_count; \
+ r_error.expected = m_count; \
r_ret = Variant(); \
return; \
} \
if (p_arg_count > m_count) { \
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
r_error.argument = m_count; \
+ r_error.expected = m_count; \
r_ret = Variant(); \
return; \
}
@@ -897,6 +899,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
case 0: {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = 1;
+ r_error.expected = 1;
r_ret = Variant();
} break;
@@ -1001,6 +1004,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
default: {
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
r_error.argument = 3;
+ r_error.expected = 3;
r_ret = Variant();
} break;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 63da849723..af07457750 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -30,8870 +30,3802 @@
#include "gdscript_parser.h"
-#include "core/core_string_names.h"
-#include "core/engine.h"
#include "core/io/resource_loader.h"
+#include "core/math/math_defs.h"
#include "core/os/file_access.h"
-#include "core/print_string.h"
#include "core/project_settings.h"
-#include "core/reference.h"
-#include "core/script_language.h"
#include "gdscript.h"
-template <class T>
-T *GDScriptParser::alloc_node() {
- T *t = memnew(T);
-
- t->next = list;
- list = t;
+#ifdef DEBUG_ENABLED
+#include "core/os/os.h"
+#include "core/string_builder.h"
+#endif // DEBUG_ENABLED
- if (!head) {
- head = t;
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif // TOOLS_ENABLED
+
+static HashMap<StringName, Variant::Type> builtin_types;
+Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
+ if (builtin_types.empty()) {
+ builtin_types["bool"] = Variant::BOOL;
+ builtin_types["int"] = Variant::INT;
+ builtin_types["float"] = Variant::FLOAT;
+ builtin_types["String"] = Variant::STRING;
+ builtin_types["Vector2"] = Variant::VECTOR2;
+ builtin_types["Vector2i"] = Variant::VECTOR2I;
+ builtin_types["Rect2"] = Variant::RECT2;
+ builtin_types["Rect2i"] = Variant::RECT2I;
+ builtin_types["Transform2D"] = Variant::TRANSFORM2D;
+ builtin_types["Vector3"] = Variant::VECTOR3;
+ builtin_types["Vector3i"] = Variant::VECTOR3I;
+ builtin_types["AABB"] = Variant::AABB;
+ builtin_types["Plane"] = Variant::PLANE;
+ builtin_types["Quat"] = Variant::QUAT;
+ builtin_types["Basis"] = Variant::BASIS;
+ builtin_types["Transform"] = Variant::TRANSFORM;
+ builtin_types["Color"] = Variant::COLOR;
+ builtin_types["RID"] = Variant::_RID;
+ builtin_types["Object"] = Variant::OBJECT;
+ builtin_types["StringName"] = Variant::STRING_NAME;
+ builtin_types["NodePath"] = Variant::NODE_PATH;
+ builtin_types["Dictionary"] = Variant::DICTIONARY;
+ builtin_types["Callable"] = Variant::CALLABLE;
+ builtin_types["Signal"] = Variant::SIGNAL;
+ builtin_types["Array"] = Variant::ARRAY;
+ builtin_types["PackedByteArray"] = Variant::PACKED_BYTE_ARRAY;
+ builtin_types["PackedInt32Array"] = Variant::PACKED_INT32_ARRAY;
+ builtin_types["PackedInt64Array"] = Variant::PACKED_INT64_ARRAY;
+ builtin_types["PackedFloat32Array"] = Variant::PACKED_FLOAT32_ARRAY;
+ builtin_types["PackedFloat64Array"] = Variant::PACKED_FLOAT64_ARRAY;
+ builtin_types["PackedStringArray"] = Variant::PACKED_STRING_ARRAY;
+ builtin_types["PackedVector2Array"] = Variant::PACKED_VECTOR2_ARRAY;
+ builtin_types["PackedVector3Array"] = Variant::PACKED_VECTOR3_ARRAY;
+ builtin_types["PackedColorArray"] = Variant::PACKED_COLOR_ARRAY;
+ // NIL is not here, hence the -1.
+ if (builtin_types.size() != Variant::VARIANT_MAX - 1) {
+ ERR_PRINT("Outdated parser: amount of built-in types don't match the amount of types in Variant.");
+ }
+ }
+
+ if (builtin_types.has(p_type)) {
+ return builtin_types[p_type];
+ }
+ return Variant::VARIANT_MAX;
+}
+
+GDScriptFunctions::Function GDScriptParser::get_builtin_function(const StringName &p_name) {
+ for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
+ if (p_name == GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) {
+ return GDScriptFunctions::Function(i);
+ }
+ }
+ return GDScriptFunctions::FUNC_MAX;
+}
+
+void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const {
+ List<StringName> keys;
+ valid_annotations.get_key_list(&keys);
+ for (const List<StringName>::Element *E = keys.front(); E != nullptr; E = E->next()) {
+ r_annotations->push_back(valid_annotations[E->get()].info);
}
+}
- t->line = tokenizer->get_token_line();
- t->column = tokenizer->get_token_column();
- return t;
+GDScriptParser::GDScriptParser() {
+ // Register valid annotations.
+ // TODO: Should this be static?
+ // TODO: Validate applicable types (e.g. a VARIABLE annotation that only applies to string variables).
+ register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
+ register_annotation(MethodInfo("@icon", { Variant::STRING, "icon_path" }), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
+ register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
+ // Export annotations.
+ register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_TYPE_STRING, Variant::NIL>);
+ register_annotation(MethodInfo("@export_enum", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, 0, true);
+ register_annotation(MethodInfo("@export_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, 1, true);
+ register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>);
+ register_annotation(MethodInfo("@export_global_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, 1, true);
+ register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>);
+ register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>);
+ register_annotation(MethodInfo("@export_placeholder"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>);
+ register_annotation(MethodInfo("@export_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, 3);
+ register_annotation(MethodInfo("@export_exp_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_RANGE, Variant::FLOAT>, 3);
+ register_annotation(MethodInfo("@export_exp_easing", { Variant::STRING, "hint1" }, { Variant::STRING, "hint2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, 2);
+ register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>);
+ register_annotation(MethodInfo("@export_node_path", { Variant::STRING, "type" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, 1, true);
+ register_annotation(MethodInfo("@export_flags", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, 0, true);
+ register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);
+ // Networking.
+ register_annotation(MethodInfo("@remote"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_REMOTE>);
+ register_annotation(MethodInfo("@master"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTER>);
+ register_annotation(MethodInfo("@puppet"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>);
+ register_annotation(MethodInfo("@remotesync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_REMOTESYNC>);
+ register_annotation(MethodInfo("@mastersync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTERSYNC>);
+ register_annotation(MethodInfo("@puppetsync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPETSYNC>);
+ // TODO: Warning annotations.
}
-#ifdef DEBUG_ENABLED
-static String _find_function_name(const GDScriptParser::OperatorNode *p_call);
-#endif // DEBUG_ENABLED
+GDScriptParser::~GDScriptParser() {
+ clear();
+}
-bool GDScriptParser::_end_statement() {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) {
- tokenizer->advance();
- return true; //handle next
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
- return true; //will be handled properly
+void GDScriptParser::clear() {
+ while (list != nullptr) {
+ Node *element = list;
+ list = list->next;
+ memdelete(element);
}
- return false;
+ head = nullptr;
+ list = nullptr;
+ _is_tool = false;
+ for_completion = false;
+ errors.clear();
+ multiline_stack.clear();
}
-void GDScriptParser::_set_end_statement_error(String p_name) {
- String error_msg;
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER) {
- error_msg = vformat("Expected end of statement (\"%s\"), got %s (\"%s\") instead.", p_name, tokenizer->get_token_name(tokenizer->get_token()), tokenizer->get_token_identifier());
+void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
+ // TODO: Improve error reporting by pointing at source code.
+ // TODO: Errors might point at more than one place at once (e.g. show previous declaration).
+ panic_mode = true;
+ // TODO: Improve positional information.
+ if (p_origin == nullptr) {
+ errors.push_back({ p_message, current.start_line, current.start_column });
} else {
- error_msg = vformat("Expected end of statement (\"%s\"), got %s instead.", p_name, tokenizer->get_token_name(tokenizer->get_token()));
+ errors.push_back({ p_message, p_origin->start_line, p_origin->leftmost_column });
}
- _set_error(error_msg);
}
-bool GDScriptParser::_enter_indent_block(BlockNode *p_block) {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_COLON) {
- // report location at the previous token (on the previous line)
- int error_line = tokenizer->get_token_line(-1);
- int error_column = tokenizer->get_token_column(-1);
- _set_error("':' expected at end of line.", error_line, error_column);
- return false;
+#ifdef DEBUG_ENABLED
+void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) {
+ Vector<String> symbols;
+ if (!p_symbol1.empty()) {
+ symbols.push_back(p_symbol1);
}
- tokenizer->advance();
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
- return false;
+ if (!p_symbol2.empty()) {
+ symbols.push_back(p_symbol2);
}
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) {
- // be more python-like
- IndentLevel current_level = indent_level.back()->get();
- indent_level.push_back(current_level);
- return true;
- //_set_error("newline expected after ':'.");
- //return false;
- }
-
- while (true) {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) {
- return false; //wtf
- } else if (tokenizer->get_token(1) == GDScriptTokenizer::TK_EOF) {
- return false;
- } else if (tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) {
- int indent = tokenizer->get_token_line_indent();
- int tabs = tokenizer->get_token_line_tab_indent();
- IndentLevel current_level = indent_level.back()->get();
- IndentLevel new_indent(indent, tabs);
- if (new_indent.is_mixed(current_level)) {
- _set_error("Mixed tabs and spaces in indentation.");
- return false;
- }
-
- if (indent <= current_level.indent) {
- return false;
- }
-
- indent_level.push_back(new_indent);
- tokenizer->advance();
- return true;
-
- } else if (p_block) {
- NewLineNode *nl = alloc_node<NewLineNode>();
- nl->line = tokenizer->get_token_line();
- p_block->statements.push_back(nl);
- }
-
- tokenizer->advance(); // go to next newline
+ if (!p_symbol3.empty()) {
+ symbols.push_back(p_symbol3);
}
+ if (!p_symbol4.empty()) {
+ symbols.push_back(p_symbol4);
+ }
+ push_warning(p_source, p_code, symbols);
}
-bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete, bool p_parsing_constant) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- tokenizer->advance();
- } else {
- parenthesis++;
- int argidx = 0;
-
- while (true) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
- _make_completable_call(argidx);
- completion_node = p_parent;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING && tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) {
- //completing a string argument..
- completion_cursor = tokenizer->get_token_constant();
-
- _make_completable_call(argidx);
- completion_node = p_parent;
- tokenizer->advance(1);
- return false;
- }
-
- Node *arg = _parse_expression(p_parent, p_static, false, p_parsing_constant);
- if (!arg) {
- return false;
- }
-
- p_args.push_back(arg);
+void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) {
+ if (is_ignoring_warnings) {
+ return;
+ }
+ if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && script_path.begins_with("res://addons/")) {
+ return;
+ }
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- tokenizer->advance();
- break;
+ String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower();
+ if (ignored_warnings.has(warn_name)) {
+ return;
+ }
+ if (!GLOBAL_GET("debug/gdscript/warnings/" + warn_name)) {
+ return;
+ }
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- if (tokenizer->get_token(1) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expression expected");
- return false;
- }
+ GDScriptWarning warning;
+ warning.code = p_code;
+ warning.symbols = p_symbols;
+ warning.start_line = p_source->start_line;
+ warning.end_line = p_source->end_line;
+ warning.leftmost_column = p_source->leftmost_column;
+ warning.rightmost_column = p_source->rightmost_column;
- tokenizer->advance();
- argidx++;
- } else {
- // something is broken
- _set_error("Expected ',' or ')'");
- return false;
- }
+ List<GDScriptWarning>::Element *before = nullptr;
+ for (List<GDScriptWarning>::Element *E = warnings.front(); E != nullptr; E = E->next()) {
+ if (E->get().start_line > warning.start_line) {
+ break;
}
- parenthesis--;
+ before = E;
+ }
+ if (before) {
+ warnings.insert_after(before, warning);
+ } else {
+ warnings.push_front(warning);
}
-
- return true;
}
+#endif
-void GDScriptParser::_make_completable_call(int p_arg) {
- completion_cursor = StringName();
- completion_type = COMPLETION_CALL_ARGUMENTS;
- completion_class = current_class;
- completion_function = current_function;
- completion_line = tokenizer->get_token_line();
- completion_argument = p_arg;
- completion_block = current_block;
- completion_found = true;
- tokenizer->advance();
-}
-
-bool GDScriptParser::_get_completable_identifier(CompletionType p_type, StringName &identifier) {
- identifier = StringName();
- if (tokenizer->is_token_literal()) {
- identifier = tokenizer->get_token_literal();
- tokenizer->advance();
- }
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
- completion_cursor = identifier;
- completion_type = p_type;
- completion_class = current_class;
- completion_function = current_function;
- completion_line = tokenizer->get_token_line();
- completion_block = current_block;
- completion_found = true;
- completion_ident_is_call = false;
- tokenizer->advance();
-
- if (tokenizer->is_token_literal()) {
- identifier = identifier.operator String() + tokenizer->get_token_literal().operator String();
- tokenizer->advance();
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- completion_ident_is_call = true;
- }
- return true;
+void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) {
+ if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
+ return;
}
+ if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
+ return;
+ }
+ CompletionContext context;
+ context.type = p_type;
+ context.current_class = current_class;
+ context.current_function = current_function;
+ context.current_suite = current_suite;
+ context.current_line = tokenizer.get_cursor_line();
+ context.current_argument = p_argument;
+ context.node = p_node;
+ completion_context = context;
+}
- return false;
+void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) {
+ if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
+ return;
+ }
+ if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
+ return;
+ }
+ CompletionContext context;
+ context.type = p_type;
+ context.current_class = current_class;
+ context.current_function = current_function;
+ context.current_suite = current_suite;
+ context.current_line = tokenizer.get_cursor_line();
+ context.builtin_type = p_builtin_type;
+ completion_context = context;
}
-GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_static, bool p_allow_assign, bool p_parsing_constant) {
- //Vector<Node*> expressions;
- //Vector<OperatorNode::Operator> operators;
+void GDScriptParser::push_completion_call(Node *p_call) {
+ if (!for_completion) {
+ return;
+ }
+ CompletionCall call;
+ call.call = p_call;
+ call.argument = 0;
+ completion_call_stack.push_back(call);
+ if (previous.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_BEGINNING) {
+ completion_call = call;
+ }
+}
- Vector<Expression> expression;
+void GDScriptParser::pop_completion_call() {
+ if (!for_completion) {
+ return;
+ }
+ ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to pop empty completion call stack");
+ completion_call_stack.pop_back();
+}
- Node *expr = nullptr;
+void GDScriptParser::set_last_completion_call_arg(int p_argument) {
+ if (!for_completion || passed_cursor) {
+ return;
+ }
+ ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to set argument on empty completion call stack");
+ completion_call_stack.back()->get().argument = p_argument;
+}
- int op_line = tokenizer->get_token_line(); // when operators are created at the bottom, the line might have been changed (\n found)
+Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) {
+ clear();
- while (true) {
- /*****************/
- /* Parse Operand */
- /*****************/
+ String source = p_source_code;
+ int cursor_line = -1;
+ int cursor_column = -1;
+ for_completion = p_for_completion;
- if (parenthesis > 0) {
- //remove empty space (only allowed if inside parenthesis
- while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
- tokenizer->advance();
+ int tab_size = 4;
+#ifdef TOOLS_ENABLED
+ if (EditorSettings::get_singleton()) {
+ tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size");
+ }
+#endif // TOOLS_ENABLED
+
+ if (p_for_completion) {
+ // Remove cursor sentinel char.
+ const Vector<String> lines = p_source_code.split("\n");
+ cursor_line = 1;
+ cursor_column = 1;
+ for (int i = 0; i < lines.size(); i++) {
+ bool found = false;
+ const String &line = lines[i];
+ for (int j = 0; j < line.size(); j++) {
+ if (line[j] == CharType(0xFFFF)) {
+ found = true;
+ break;
+ } else if (line[j] == '\t') {
+ cursor_column += tab_size - 1;
+ }
+ cursor_column++;
}
- }
-
- // Check that the next token is not TK_CURSOR and if it is, the offset should be incremented.
- int next_valid_offset = 1;
- if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_CURSOR) {
- next_valid_offset++;
- // There is a chunk of the identifier that also needs to be ignored (not always there!)
- if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_IDENTIFIER) {
- next_valid_offset++;
+ if (found) {
+ break;
}
+ cursor_line++;
+ cursor_column = 1;
}
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- //subexpression ()
- tokenizer->advance();
- parenthesis++;
- Node *subexpr = _parse_expression(p_parent, p_static, p_allow_assign, p_parsing_constant);
- parenthesis--;
- if (!subexpr) {
- return nullptr;
- }
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected ')' in expression");
- return nullptr;
- }
-
- tokenizer->advance();
- expr = subexpr;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_DOLLAR) {
- tokenizer->advance();
-
- String path;
-
- bool need_identifier = true;
- bool done = false;
- int line = tokenizer->get_token_line();
-
- while (!done) {
- switch (tokenizer->get_token()) {
- case GDScriptTokenizer::TK_CURSOR: {
- completion_type = COMPLETION_GET_NODE;
- completion_class = current_class;
- completion_function = current_function;
- completion_line = tokenizer->get_token_line();
- completion_cursor = path;
- completion_argument = 0;
- completion_block = current_block;
- completion_found = true;
- tokenizer->advance();
- } break;
- case GDScriptTokenizer::TK_CONSTANT: {
- if (!need_identifier) {
- done = true;
- break;
- }
-
- if (tokenizer->get_token_constant().get_type() != Variant::STRING) {
- _set_error("Expected string constant or identifier after '$' or '/'.");
- return nullptr;
- }
-
- path += String(tokenizer->get_token_constant());
- tokenizer->advance();
- need_identifier = false;
-
- } break;
- case GDScriptTokenizer::TK_OP_DIV: {
- if (need_identifier) {
- done = true;
- break;
- }
-
- path += "/";
- tokenizer->advance();
- need_identifier = true;
-
- } break;
- default: {
- // Instead of checking for TK_IDENTIFIER, we check with is_token_literal, as this allows us to use match/sync/etc. as a name
- if (need_identifier && tokenizer->is_token_literal()) {
- path += String(tokenizer->get_token_literal());
- tokenizer->advance();
- need_identifier = false;
- } else {
- done = true;
- }
+ source = source.replace_first(String::chr(0xFFFF), String());
+ }
- break;
- }
- }
- }
+ tokenizer.set_source_code(source);
+ tokenizer.set_cursor_position(cursor_line, cursor_column);
+ script_path = p_script_path;
+ current = tokenizer.scan();
+ // Avoid error as the first token.
+ while (current.type == GDScriptTokenizer::Token::ERROR) {
+ push_error(current.literal);
+ current = tokenizer.scan();
+ }
- if (path == "") {
- _set_error("Path expected after $.");
- return nullptr;
- }
+ push_multiline(false); // Keep one for the whole parsing.
+ parse_program();
+ pop_multiline();
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_CALL;
- op->line = line;
- op->arguments.push_back(alloc_node<SelfNode>());
- op->arguments[0]->line = line;
-
- IdentifierNode *funcname = alloc_node<IdentifierNode>();
- funcname->name = "get_node";
- funcname->line = line;
- op->arguments.push_back(funcname);
-
- ConstantNode *nodepath = alloc_node<ConstantNode>();
- nodepath->value = NodePath(StringName(path));
- nodepath->datatype = _type_from_variant(nodepath->value);
- nodepath->line = line;
- op->arguments.push_back(nodepath);
-
- expr = op;
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
- tokenizer->advance();
- continue; //no point in cursor in the middle of expression
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) {
- //constant defined by tokenizer
- ConstantNode *constant = alloc_node<ConstantNode>();
- constant->value = tokenizer->get_token_constant();
- constant->datatype = _type_from_variant(constant->value);
- tokenizer->advance();
- expr = constant;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_PI) {
- //constant defined by tokenizer
- ConstantNode *constant = alloc_node<ConstantNode>();
- constant->value = Math_PI;
- constant->datatype = _type_from_variant(constant->value);
- tokenizer->advance();
- expr = constant;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_TAU) {
- //constant defined by tokenizer
- ConstantNode *constant = alloc_node<ConstantNode>();
- constant->value = Math_TAU;
- constant->datatype = _type_from_variant(constant->value);
- tokenizer->advance();
- expr = constant;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_INF) {
- //constant defined by tokenizer
- ConstantNode *constant = alloc_node<ConstantNode>();
- constant->value = Math_INF;
- constant->datatype = _type_from_variant(constant->value);
- tokenizer->advance();
- expr = constant;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_NAN) {
- //constant defined by tokenizer
- ConstantNode *constant = alloc_node<ConstantNode>();
- constant->value = Math_NAN;
- constant->datatype = _type_from_variant(constant->value);
- tokenizer->advance();
- expr = constant;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_PRELOAD) {
- //constant defined by tokenizer
- tokenizer->advance();
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- _set_error("Expected '(' after 'preload'");
- return nullptr;
- }
- tokenizer->advance();
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
- completion_cursor = StringName();
- completion_node = p_parent;
- completion_type = COMPLETION_RESOURCE_PATH;
- completion_class = current_class;
- completion_function = current_function;
- completion_line = tokenizer->get_token_line();
- completion_block = current_block;
- completion_argument = 0;
- completion_found = true;
- tokenizer->advance();
- }
+#ifdef DEBUG_ENABLED
+ if (multiline_stack.size() > 0) {
+ ERR_PRINT("Parser bug: Imbalanced multiline stack.");
+ }
+#endif
- String path;
- bool found_constant = false;
- bool valid = false;
- ConstantNode *cn;
+ if (errors.empty()) {
+ return OK;
+ } else {
+ return ERR_PARSE_ERROR;
+ }
+}
- Node *subexpr = _parse_and_reduce_expression(p_parent, p_static);
- if (subexpr) {
- if (subexpr->type == Node::TYPE_CONSTANT) {
- cn = static_cast<ConstantNode *>(subexpr);
- found_constant = true;
- }
- if (subexpr->type == Node::TYPE_IDENTIFIER) {
- IdentifierNode *in = static_cast<IdentifierNode *>(subexpr);
-
- // Try to find the constant expression by the identifier
- if (current_class->constant_expressions.has(in->name)) {
- Node *cn_exp = current_class->constant_expressions[in->name].expression;
- if (cn_exp->type == Node::TYPE_CONSTANT) {
- cn = static_cast<ConstantNode *>(cn_exp);
- found_constant = true;
- }
- }
- }
+GDScriptTokenizer::Token GDScriptParser::advance() {
+ if (current.type == GDScriptTokenizer::Token::TK_EOF) {
+ ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream.");
+ }
+ if (for_completion && !completion_call_stack.empty()) {
+ if (completion_call.call == nullptr && tokenizer.is_past_cursor()) {
+ completion_call = completion_call_stack.back()->get();
+ passed_cursor = true;
+ }
+ }
+ previous = current;
+ current = tokenizer.scan();
+ while (current.type == GDScriptTokenizer::Token::ERROR) {
+ push_error(current.literal);
+ current = tokenizer.scan();
+ }
+ return previous;
+}
- if (found_constant && cn->value.get_type() == Variant::STRING) {
- valid = true;
- path = (String)cn->value;
- }
- }
+bool GDScriptParser::match(GDScriptTokenizer::Token::Type p_token_type) {
+ if (!check(p_token_type)) {
+ return false;
+ }
+ advance();
+ return true;
+}
- if (!valid) {
- _set_error("expected string constant as 'preload' argument.");
- return nullptr;
- }
+bool GDScriptParser::check(GDScriptTokenizer::Token::Type p_token_type) {
+ if (p_token_type == GDScriptTokenizer::Token::IDENTIFIER) {
+ return current.is_identifier();
+ }
+ return current.type == p_token_type;
+}
- if (!path.is_abs_path() && base_path != "") {
- path = base_path.plus_file(path);
- }
- path = path.replace("///", "//").simplify_path();
- if (path == self_path) {
- _set_error("Can't preload itself (use 'get_script()').");
- return nullptr;
- }
+bool GDScriptParser::consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message) {
+ if (match(p_token_type)) {
+ return true;
+ }
+ push_error(p_error_message);
+ return false;
+}
- Ref<Resource> res;
- dependencies.push_back(path);
- if (!dependencies_only) {
- if (!validating) {
- //this can be too slow for just validating code
- if (for_completion && ScriptCodeCompletionCache::get_singleton() && FileAccess::exists(path)) {
- res = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path);
- } else if (!for_completion || FileAccess::exists(path)) {
- res = ResourceLoader::load(path);
- }
- } else {
- if (!FileAccess::exists(path)) {
- _set_error("Can't preload resource at path: " + path);
- return nullptr;
- } else if (ScriptCodeCompletionCache::get_singleton()) {
- res = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path);
- }
- }
- if (!res.is_valid()) {
- _set_error("Can't preload resource at path: " + path);
- return nullptr;
- }
- }
+bool GDScriptParser::is_at_end() {
+ return check(GDScriptTokenizer::Token::TK_EOF);
+}
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected ')' after 'preload' path");
- return nullptr;
- }
+void GDScriptParser::synchronize() {
+ panic_mode = false;
+ while (!is_at_end()) {
+ if (previous.type == GDScriptTokenizer::Token::NEWLINE || previous.type == GDScriptTokenizer::Token::SEMICOLON) {
+ return;
+ }
- Ref<GDScript> gds = res;
- if (gds.is_valid() && !gds->is_valid()) {
- _set_error("Couldn't fully preload the script, possible cyclic reference or compilation error. Use \"load()\" instead if a cyclic reference is intended.");
- return nullptr;
- }
+ switch (current.type) {
+ case GDScriptTokenizer::Token::CLASS:
+ case GDScriptTokenizer::Token::FUNC:
+ case GDScriptTokenizer::Token::STATIC:
+ case GDScriptTokenizer::Token::VAR:
+ case GDScriptTokenizer::Token::CONST:
+ case GDScriptTokenizer::Token::SIGNAL:
+ //case GDScriptTokenizer::Token::IF: // Can also be inside expressions.
+ case GDScriptTokenizer::Token::FOR:
+ case GDScriptTokenizer::Token::WHILE:
+ case GDScriptTokenizer::Token::MATCH:
+ case GDScriptTokenizer::Token::RETURN:
+ case GDScriptTokenizer::Token::ANNOTATION:
+ return;
+ default:
+ // Do nothing.
+ break;
+ }
- tokenizer->advance();
+ advance();
+ }
+}
- ConstantNode *constant = alloc_node<ConstantNode>();
- constant->value = res;
- constant->datatype = _type_from_variant(constant->value);
+void GDScriptParser::push_multiline(bool p_state) {
+ multiline_stack.push_back(p_state);
+ tokenizer.set_multiline_mode(p_state);
+ if (p_state) {
+ // Consume potential whitespace tokens already waiting in line.
+ while (current.type == GDScriptTokenizer::Token::NEWLINE || current.type == GDScriptTokenizer::Token::INDENT || current.type == GDScriptTokenizer::Token::DEDENT) {
+ current = tokenizer.scan(); // Don't call advance() here, as we don't want to change the previous token.
+ }
+ }
+}
- expr = constant;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_YIELD) {
- if (!current_function) {
- _set_error("\"yield()\" can only be used inside function blocks.");
- return nullptr;
- }
+void GDScriptParser::pop_multiline() {
+ ERR_FAIL_COND_MSG(multiline_stack.size() == 0, "Parser bug: trying to pop from multiline stack without available value.");
+ multiline_stack.pop_back();
+ tokenizer.set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false);
+}
- current_function->has_yield = true;
+bool GDScriptParser::is_statement_end() {
+ return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON);
+}
- tokenizer->advance();
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- _set_error("Expected \"(\" after \"yield\".");
- return nullptr;
- }
+void GDScriptParser::end_statement(const String &p_context) {
+ bool found = false;
+ while (is_statement_end()) {
+ // Remove sequential newlines/semicolons.
+ found = true;
+ advance();
+ }
+ if (!found) {
+ push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name()));
+ }
+}
- tokenizer->advance();
+void GDScriptParser::parse_program() {
+ if (current.type == GDScriptTokenizer::Token::TK_EOF) {
+ // Empty file.
+ push_error("Source file is empty.");
+ return;
+ }
- OperatorNode *yield = alloc_node<OperatorNode>();
- yield->op = OperatorNode::OP_YIELD;
+ head = alloc_node<ClassNode>();
+ current_class = head;
- while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
- tokenizer->advance();
+ if (match(GDScriptTokenizer::Token::ANNOTATION)) {
+ // Check for @tool annotation.
+ AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL);
+ if (annotation->name == "@tool") {
+ // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?).
+ _is_tool = true;
+ if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
+ push_error(R"(Expected newline after "@tool" annotation.)");
}
+ // @tool annotation has no specific target.
+ annotation->apply(this, nullptr);
+ } else {
+ annotation_stack.push_back(annotation);
+ }
+ }
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- expr = yield;
- tokenizer->advance();
- } else {
- parenthesis++;
-
- Node *object = _parse_and_reduce_expression(p_parent, p_static);
- if (!object) {
- return nullptr;
- }
- yield->arguments.push_back(object);
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- _set_error("Expected \",\" after the first argument of \"yield\".");
- return nullptr;
- }
-
- tokenizer->advance();
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
- completion_cursor = StringName();
- completion_node = object;
- completion_type = COMPLETION_YIELD;
- completion_class = current_class;
- completion_function = current_function;
- completion_line = tokenizer->get_token_line();
- completion_argument = 0;
- completion_block = current_block;
- completion_found = true;
- tokenizer->advance();
- }
-
- Node *signal = _parse_and_reduce_expression(p_parent, p_static);
- if (!signal) {
- return nullptr;
- }
- yield->arguments.push_back(signal);
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \")\" after the second argument of \"yield\".");
- return nullptr;
+ for (bool should_break = false; !should_break;) {
+ // Order here doesn't matter, but there should be only one of each at most.
+ switch (current.type) {
+ case GDScriptTokenizer::Token::CLASS_NAME:
+ if (!annotation_stack.empty()) {
+ push_error(R"("class_name" should be used before annotations.)");
}
-
- parenthesis--;
-
- tokenizer->advance();
-
- expr = yield;
- }
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_SELF) {
- if (p_static) {
- _set_error("\"self\" isn't allowed in a static function or constant expression.");
- return nullptr;
- }
- //constant defined by tokenizer
- SelfNode *self = alloc_node<SelfNode>();
- tokenizer->advance();
- expr = self;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) {
- Variant::Type bi_type = tokenizer->get_token_type();
- tokenizer->advance(2);
-
- StringName identifier;
-
- if (_get_completable_identifier(COMPLETION_BUILT_IN_TYPE_CONSTANT, identifier)) {
- completion_built_in_constant = bi_type;
- }
-
- if (identifier == StringName()) {
- _set_error("Built-in type constant or static function expected after \".\".");
- return nullptr;
- }
- if (!Variant::has_constant(bi_type, identifier)) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN &&
- Variant::is_method_const(bi_type, identifier) &&
- Variant::get_method_return_type(bi_type, identifier) == bi_type) {
- tokenizer->advance();
-
- OperatorNode *construct = alloc_node<OperatorNode>();
- construct->op = OperatorNode::OP_CALL;
-
- TypeNode *tn = alloc_node<TypeNode>();
- tn->vtype = bi_type;
- construct->arguments.push_back(tn);
-
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_CALL;
- op->arguments.push_back(construct);
-
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = identifier;
- op->arguments.push_back(id);
-
- if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) {
- return nullptr;
- }
-
- expr = op;
+ advance();
+ if (head->identifier != nullptr) {
+ push_error(R"("class_name" can only be used once.)");
} else {
- // Object is a special case
- bool valid = false;
- if (bi_type == Variant::OBJECT) {
- int object_constant = ClassDB::get_integer_constant("Object", identifier, &valid);
- if (valid) {
- ConstantNode *cn = alloc_node<ConstantNode>();
- cn->value = object_constant;
- cn->datatype = _type_from_variant(cn->value);
- expr = cn;
- }
- }
- if (!valid) {
- _set_error("Static constant '" + identifier.operator String() + "' not present in built-in type " + Variant::get_type_name(bi_type) + ".");
- return nullptr;
- }
- }
- } else {
- ConstantNode *cn = alloc_node<ConstantNode>();
- cn->value = Variant::get_constant_value(bi_type, identifier);
- cn->datatype = _type_from_variant(cn->value);
- expr = cn;
- }
-
- } else if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) {
- // We check with is_token_literal, as this allows us to use match/sync/etc. as a name
- //function or constructor
-
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_CALL;
-
- //Do a quick Array and Dictionary Check. Replace if either require no arguments.
- bool replaced = false;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) {
- Variant::Type ct = tokenizer->get_token_type();
- if (!p_parsing_constant) {
- if (ct == Variant::ARRAY) {
- if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- ArrayNode *arr = alloc_node<ArrayNode>();
- expr = arr;
- replaced = true;
- tokenizer->advance(3);
- }
- }
- if (ct == Variant::DICTIONARY) {
- if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- DictionaryNode *dict = alloc_node<DictionaryNode>();
- expr = dict;
- replaced = true;
- tokenizer->advance(3);
- }
- }
- }
-
- if (!replaced) {
- TypeNode *tn = alloc_node<TypeNode>();
- tn->vtype = tokenizer->get_token_type();
- op->arguments.push_back(tn);
- tokenizer->advance(2);
+ parse_class_name();
}
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_FUNC) {
- BuiltInFunctionNode *bn = alloc_node<BuiltInFunctionNode>();
- bn->function = tokenizer->get_token_built_in_func();
- op->arguments.push_back(bn);
- tokenizer->advance(2);
- } else {
- SelfNode *self = alloc_node<SelfNode>();
- op->arguments.push_back(self);
-
- StringName identifier;
- if (_get_completable_identifier(COMPLETION_FUNCTION, identifier)) {
- }
-
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = identifier;
- op->arguments.push_back(id);
- tokenizer->advance(1);
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
- _make_completable_call(0);
- completion_node = op;
-
- if (op->arguments[0]->type == GDScriptParser::Node::Type::TYPE_BUILT_IN_FUNCTION) {
- BuiltInFunctionNode *bn = static_cast<BuiltInFunctionNode *>(op->arguments[0]);
- if (bn->function == GDScriptFunctions::Function::RESOURCE_LOAD) {
- completion_type = COMPLETION_RESOURCE_PATH;
- }
- }
- }
- if (!replaced) {
- if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) {
- return nullptr;
- }
- expr = op;
- }
- } else if (tokenizer->is_token_literal(0, true)) {
- // We check with is_token_literal, as this allows us to use match/sync/etc. as a name
- //identifier (reference)
-
- const ClassNode *cln = current_class;
- bool bfn = false;
- StringName identifier;
- int id_line = tokenizer->get_token_line();
- if (_get_completable_identifier(COMPLETION_IDENTIFIER, identifier)) {
- }
-
- BlockNode *b = current_block;
- while (!bfn && b) {
- if (b->variables.has(identifier)) {
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = identifier;
- id->declared_block = b;
- id->line = id_line;
- expr = id;
- bfn = true;
-
-#ifdef DEBUG_ENABLED
- LocalVarNode *lv = b->variables[identifier];
- switch (tokenizer->get_token()) {
- case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
- case GDScriptTokenizer::TK_OP_ASSIGN_DIV:
- case GDScriptTokenizer::TK_OP_ASSIGN_MOD:
- case GDScriptTokenizer::TK_OP_ASSIGN_MUL:
- case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
- case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
- case GDScriptTokenizer::TK_OP_ASSIGN_SUB: {
- if (lv->assignments == 0) {
- if (!lv->datatype.has_type) {
- _set_error("Using assignment with operation on a variable that was never assigned.");
- return nullptr;
- }
- _add_warning(GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, -1, identifier.operator String());
- }
- [[fallthrough]];
- }
- case GDScriptTokenizer::TK_OP_ASSIGN: {
- lv->assignments += 1;
- lv->usages--; // Assignment is not really usage
- } break;
- default: {
- lv->usages++;
- }
- }
-#endif // DEBUG_ENABLED
- break;
- }
- b = b->parent_block;
- }
-
- if (!bfn && p_parsing_constant) {
- if (cln->constant_expressions.has(identifier)) {
- expr = cln->constant_expressions[identifier].expression;
- bfn = true;
- } else if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
- //check from constants
- ConstantNode *constant = alloc_node<ConstantNode>();
- constant->value = GDScriptLanguage::get_singleton()->get_global_array()[GDScriptLanguage::get_singleton()->get_global_map()[identifier]];
- constant->datatype = _type_from_variant(constant->value);
- constant->line = id_line;
- expr = constant;
- bfn = true;
- }
-
- if (!bfn && GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) {
- //check from singletons
- ConstantNode *constant = alloc_node<ConstantNode>();
- constant->value = GDScriptLanguage::get_singleton()->get_named_globals_map()[identifier];
- constant->datatype = _type_from_variant(constant->value);
- expr = constant;
- bfn = true;
- }
-
- if (!dependencies_only) {
- if (!bfn && ScriptServer::is_global_class(identifier)) {
- Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(identifier));
- if (scr.is_valid() && scr->is_valid()) {
- ConstantNode *constant = alloc_node<ConstantNode>();
- constant->value = scr;
- constant->datatype = _type_from_variant(constant->value);
- expr = constant;
- bfn = true;
- }
- }
-
- // Check parents for the constant
- if (!bfn) {
- // Using current_class instead of cln here, since cln is const*
- _determine_inheritance(current_class, false);
- if (cln->base_type.has_type && cln->base_type.kind == DataType::GDSCRIPT && cln->base_type.script_type->is_valid()) {
- Map<StringName, Variant> parent_constants;
- current_class->base_type.script_type->get_constants(&parent_constants);
- if (parent_constants.has(identifier)) {
- ConstantNode *constant = alloc_node<ConstantNode>();
- constant->value = parent_constants[identifier];
- constant->datatype = _type_from_variant(constant->value);
- expr = constant;
- bfn = true;
- }
- }
- }
- }
- }
-
- if (!bfn) {
-#ifdef DEBUG_ENABLED
- if (current_function) {
- int arg_idx = current_function->arguments.find(identifier);
- if (arg_idx != -1) {
- switch (tokenizer->get_token()) {
- case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
- case GDScriptTokenizer::TK_OP_ASSIGN_DIV:
- case GDScriptTokenizer::TK_OP_ASSIGN_MOD:
- case GDScriptTokenizer::TK_OP_ASSIGN_MUL:
- case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
- case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
- case GDScriptTokenizer::TK_OP_ASSIGN_SUB:
- case GDScriptTokenizer::TK_OP_ASSIGN: {
- // Assignment is not really usage
- } break;
- default: {
- current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1;
- }
- }
- }
- }
-#endif // DEBUG_ENABLED
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = identifier;
- id->line = id_line;
- expr = id;
- }
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ADD || tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB || tokenizer->get_token() == GDScriptTokenizer::TK_OP_NOT || tokenizer->get_token() == GDScriptTokenizer::TK_OP_BIT_INVERT) {
- //single prefix operators like !expr +expr -expr ++expr --expr
- alloc_node<OperatorNode>();
- Expression e;
- e.is_op = true;
-
- switch (tokenizer->get_token()) {
- case GDScriptTokenizer::TK_OP_ADD:
- e.op = OperatorNode::OP_POS;
- break;
- case GDScriptTokenizer::TK_OP_SUB:
- e.op = OperatorNode::OP_NEG;
- break;
- case GDScriptTokenizer::TK_OP_NOT:
- e.op = OperatorNode::OP_NOT;
- break;
- case GDScriptTokenizer::TK_OP_BIT_INVERT:
- e.op = OperatorNode::OP_BIT_INVERT;
- break;
- default: {
+ break;
+ case GDScriptTokenizer::Token::EXTENDS:
+ if (!annotation_stack.empty()) {
+ push_error(R"("extends" should be used before annotations.)");
}
- }
-
- tokenizer->advance();
-
- if (e.op != OperatorNode::OP_NOT && tokenizer->get_token() == GDScriptTokenizer::TK_OP_NOT) {
- _set_error("Misplaced 'not'.");
- return nullptr;
- }
-
- expression.push_back(e);
- continue; //only exception, must continue...
-
- /*
- Node *subexpr=_parse_expression(op,p_static);
- if (!subexpr)
- return nullptr;
- op->arguments.push_back(subexpr);
- expr=op;*/
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_IS && tokenizer->get_token(1) == GDScriptTokenizer::TK_BUILT_IN_TYPE) {
- // 'is' operator with built-in type
- if (!expr) {
- _set_error("Expected identifier before 'is' operator");
- return nullptr;
- }
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_IS_BUILTIN;
- op->arguments.push_back(expr);
-
- tokenizer->advance();
-
- TypeNode *tn = alloc_node<TypeNode>();
- tn->vtype = tokenizer->get_token_type();
- op->arguments.push_back(tn);
- tokenizer->advance();
-
- expr = op;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_OPEN) {
- // array
- tokenizer->advance();
-
- ArrayNode *arr = alloc_node<ArrayNode>();
- bool expecting_comma = false;
-
- while (true) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
- _set_error("Unterminated array");
- return nullptr;
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) {
- tokenizer->advance();
- break;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
- tokenizer->advance(); //ignore newline
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- if (!expecting_comma) {
- _set_error("expression or ']' expected");
- return nullptr;
- }
-
- expecting_comma = false;
- tokenizer->advance(); //ignore newline
+ advance();
+ if (head->extends_used) {
+ push_error(R"("extends" can only be used once.)");
} else {
- //parse expression
- if (expecting_comma) {
- _set_error("',' or ']' expected");
- return nullptr;
- }
- Node *n = _parse_expression(arr, p_static, p_allow_assign, p_parsing_constant);
- if (!n) {
- return nullptr;
- }
- arr->elements.push_back(n);
- expecting_comma = true;
+ parse_extends();
+ end_statement("superclass");
}
- }
-
- expr = arr;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) {
- // array
- tokenizer->advance();
-
- DictionaryNode *dict = alloc_node<DictionaryNode>();
-
- enum DictExpect {
-
- DICT_EXPECT_KEY,
- DICT_EXPECT_COLON,
- DICT_EXPECT_VALUE,
- DICT_EXPECT_COMMA
-
- };
-
- Node *key = nullptr;
- Set<Variant> keys;
-
- DictExpect expecting = DICT_EXPECT_KEY;
-
- while (true) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
- _set_error("Unterminated dictionary");
- return nullptr;
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
- if (expecting == DICT_EXPECT_COLON) {
- _set_error("':' expected");
- return nullptr;
- }
- if (expecting == DICT_EXPECT_VALUE) {
- _set_error("value expected");
- return nullptr;
- }
- tokenizer->advance();
- break;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
- tokenizer->advance(); //ignore newline
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- if (expecting == DICT_EXPECT_KEY) {
- _set_error("key or '}' expected");
- return nullptr;
- }
- if (expecting == DICT_EXPECT_VALUE) {
- _set_error("value expected");
- return nullptr;
- }
- if (expecting == DICT_EXPECT_COLON) {
- _set_error("':' expected");
- return nullptr;
- }
-
- expecting = DICT_EXPECT_KEY;
- tokenizer->advance(); //ignore newline
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
- if (expecting == DICT_EXPECT_KEY) {
- _set_error("key or '}' expected");
- return nullptr;
- }
- if (expecting == DICT_EXPECT_VALUE) {
- _set_error("value expected");
- return nullptr;
- }
- if (expecting == DICT_EXPECT_COMMA) {
- _set_error("',' or '}' expected");
- return nullptr;
- }
-
- expecting = DICT_EXPECT_VALUE;
- tokenizer->advance(); //ignore newline
- } else {
- if (expecting == DICT_EXPECT_COMMA) {
- _set_error("',' or '}' expected");
- return nullptr;
- }
- if (expecting == DICT_EXPECT_COLON) {
- _set_error("':' expected");
- return nullptr;
- }
-
- if (expecting == DICT_EXPECT_KEY) {
- if (tokenizer->is_token_literal() && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
- // We check with is_token_literal, as this allows us to use match/sync/etc. as a name
- //lua style identifier, easier to write
- ConstantNode *cn = alloc_node<ConstantNode>();
- cn->value = tokenizer->get_token_literal();
- cn->datatype = _type_from_variant(cn->value);
- key = cn;
- tokenizer->advance(2);
- expecting = DICT_EXPECT_VALUE;
- } else {
- //python/js style more flexible
- key = _parse_expression(dict, p_static, p_allow_assign, p_parsing_constant);
- if (!key) {
- return nullptr;
- }
- expecting = DICT_EXPECT_COLON;
- }
- }
-
- if (expecting == DICT_EXPECT_VALUE) {
- Node *value = _parse_expression(dict, p_static, p_allow_assign, p_parsing_constant);
- if (!value) {
- return nullptr;
- }
- expecting = DICT_EXPECT_COMMA;
-
- if (key->type == GDScriptParser::Node::TYPE_CONSTANT) {
- Variant const &keyName = static_cast<const GDScriptParser::ConstantNode *>(key)->value;
-
- if (keys.has(keyName)) {
- _set_error("Duplicate key found in Dictionary literal");
- return nullptr;
- }
- keys.insert(keyName);
- }
-
- DictionaryNode::Pair pair;
- pair.key = key;
- pair.value = value;
- dict->elements.push_back(pair);
- key = nullptr;
- }
- }
- }
-
- expr = dict;
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR)) {
- // We check with is_token_literal, as this allows us to use match/sync/etc. as a name
- // parent call
-
- tokenizer->advance(); //goto identifier
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_PARENT_CALL;
-
- /*SelfNode *self = alloc_node<SelfNode>();
- op->arguments.push_back(self);
- forbidden for now */
- StringName identifier;
- bool is_completion = _get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier) && for_completion;
+ break;
+ default:
+ should_break = true;
+ break;
+ }
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = identifier;
- op->arguments.push_back(id);
+ if (panic_mode) {
+ synchronize();
+ }
+ }
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- if (!is_completion) {
- _set_error("Expected '(' for parent function call.");
- return nullptr;
+ if (match(GDScriptTokenizer::Token::ANNOTATION)) {
+ // Check for @icon annotation.
+ AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL);
+ if (annotation != nullptr) {
+ if (annotation->name == "@icon") {
+ if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
+ push_error(R"(Expected newline after "@icon" annotation.)");
}
+ annotation->apply(this, head);
} else {
- tokenizer->advance();
- if (!_parse_arguments(op, op->arguments, p_static, false, p_parsing_constant)) {
- return nullptr;
- }
+ annotation_stack.push_back(annotation);
}
-
- expr = op;
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && expression.size() > 0 && expression[expression.size() - 1].is_op && expression[expression.size() - 1].op == OperatorNode::OP_IS) {
- Expression e = expression[expression.size() - 1];
- e.op = OperatorNode::OP_IS_BUILTIN;
- expression.write[expression.size() - 1] = e;
-
- TypeNode *tn = alloc_node<TypeNode>();
- tn->vtype = tokenizer->get_token_type();
- expr = tn;
- tokenizer->advance();
- } else {
- //find list [ or find dictionary {
- _set_error("Error parsing expression, misplaced: " + String(tokenizer->get_token_name(tokenizer->get_token())));
- return nullptr; //nothing
}
+ }
- ERR_FAIL_COND_V_MSG(!expr, nullptr, "GDScriptParser bug, couldn't figure out what expression is.");
-
- /******************/
- /* Parse Indexing */
- /******************/
-
- while (true) {
- //expressions can be indexed any number of times
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) {
- //indexing using "."
-
- if (tokenizer->get_token(1) != GDScriptTokenizer::TK_CURSOR && !tokenizer->is_token_literal(1)) {
- // We check with is_token_literal, as this allows us to use match/sync/etc. as a name
- _set_error("Expected identifier as member");
- return nullptr;
- } else if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- //call!!
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_CALL;
-
- tokenizer->advance();
-
- IdentifierNode *id = alloc_node<IdentifierNode>();
- StringName identifier;
- if (_get_completable_identifier(COMPLETION_METHOD, identifier)) {
- completion_node = op;
- //indexing stuff
- }
-
- id->name = identifier;
-
- op->arguments.push_back(expr); // call what
- op->arguments.push_back(id); // call func
- //get arguments
- tokenizer->advance(1);
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
- _make_completable_call(0);
- completion_node = op;
- }
- if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) {
- return nullptr;
- }
- expr = op;
+ parse_class_body();
- } else {
- //simple indexing!
+ if (!check(GDScriptTokenizer::Token::TK_EOF)) {
+ push_error("Expected end of file.");
+ }
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_INDEX_NAMED;
- tokenizer->advance();
+ clear_unused_annotations();
+}
- StringName identifier;
- if (_get_completable_identifier(COMPLETION_INDEX, identifier)) {
- if (identifier == StringName()) {
- identifier = "@temp"; //so it parses alright
- }
- completion_node = op;
+GDScriptParser::ClassNode *GDScriptParser::parse_class() {
+ ClassNode *n_class = alloc_node<ClassNode>();
- //indexing stuff
- }
+ ClassNode *previous_class = current_class;
+ current_class = n_class;
+ n_class->outer = previous_class;
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = identifier;
+ if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) {
+ n_class->identifier = parse_identifier();
+ }
- op->arguments.push_back(expr);
- op->arguments.push_back(id);
+ if (match(GDScriptTokenizer::Token::EXTENDS)) {
+ parse_extends();
+ }
- expr = op;
- }
+ consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after class declaration.)");
+ consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected newline after class declaration.)");
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_OPEN) {
- //indexing using "[]"
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_INDEX;
+ if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) {
+ current_class = previous_class;
+ return n_class;
+ }
- tokenizer->advance(1);
+ parse_class_body();
- Node *subexpr = _parse_expression(op, p_static, p_allow_assign, p_parsing_constant);
- if (!subexpr) {
- return nullptr;
- }
+ consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)");
- if (tokenizer->get_token() != GDScriptTokenizer::TK_BRACKET_CLOSE) {
- _set_error("Expected ']'");
- return nullptr;
- }
+ current_class = previous_class;
+ return n_class;
+}
- op->arguments.push_back(expr);
- op->arguments.push_back(subexpr);
- tokenizer->advance(1);
- expr = op;
+void GDScriptParser::parse_class_name() {
+ if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the global class name after "class_name".)")) {
+ current_class->identifier = parse_identifier();
+ }
- } else {
- break;
+ // TODO: Move this to annotation
+ if (match(GDScriptTokenizer::Token::COMMA)) {
+ // Icon path.
+ if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected class icon path string after ",".)")) {
+ if (previous.literal.get_type() != Variant::STRING) {
+ push_error(vformat(R"(Only strings can be used for the class icon path, found "%s" instead.)", Variant::get_type_name(previous.literal.get_type())));
}
+ current_class->icon_path = previous.literal;
}
+ }
- /*****************/
- /* Parse Casting */
- /*****************/
+ if (match(GDScriptTokenizer::Token::EXTENDS)) {
+ // Allow extends on the same line.
+ parse_extends();
+ end_statement("superclass");
+ } else {
+ end_statement("class_name statement");
+ }
+}
- bool has_casting = expr->type == Node::TYPE_CAST;
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_AS) {
- if (has_casting) {
- _set_error("Unexpected 'as'.");
- return nullptr;
- }
- CastNode *cn = alloc_node<CastNode>();
- if (!_parse_type(cn->cast_type)) {
- _set_error("Expected type after 'as'.");
- return nullptr;
- }
- has_casting = true;
- cn->source_node = expr;
- expr = cn;
- }
+void GDScriptParser::parse_extends() {
+ current_class->extends_used = true;
- /******************/
- /* Parse Operator */
- /******************/
+ int chain_index = 0;
- if (parenthesis > 0) {
- //remove empty space (only allowed if inside parenthesis
- while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
- tokenizer->advance();
- }
+ if (match(GDScriptTokenizer::Token::LITERAL)) {
+ if (previous.literal.get_type() != Variant::STRING) {
+ push_error(vformat(R"(Only strings or identifiers can be used after "extends", found "%s" instead.)", Variant::get_type_name(previous.literal.get_type())));
}
+ current_class->extends_path = previous.literal;
- Expression e;
- e.is_op = false;
- e.node = expr;
- expression.push_back(e);
+ if (!match(GDScriptTokenizer::Token::PERIOD)) {
+ end_statement("superclass path");
+ return;
+ }
+ }
- // determine which operator is next
+ make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
- OperatorNode::Operator op;
- bool valid = true;
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) {
+ return;
+ }
+ current_class->extends.push_back(previous.literal);
-//assign, if allowed is only allowed on the first operator
-#define _VALIDATE_ASSIGN \
- if (!p_allow_assign || has_casting) { \
- _set_error("Unexpected assign."); \
- return nullptr; \
- } \
- p_allow_assign = false;
+ while (match(GDScriptTokenizer::Token::PERIOD)) {
+ make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) {
+ return;
+ }
+ current_class->extends.push_back(previous.literal);
+ }
+}
- switch (tokenizer->get_token()) { //see operator
+template <class T>
+void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) {
+ advance();
+ T *member = (this->*p_parse_function)();
+ if (member == nullptr) {
+ return;
+ }
+ // Consume annotations.
+ while (!annotation_stack.empty()) {
+ AnnotationNode *last_annotation = annotation_stack.back()->get();
+ if (last_annotation->applies_to(p_target)) {
+ last_annotation->apply(this, member);
+ member->annotations.push_front(last_annotation);
+ annotation_stack.pop_back();
+ } else {
+ push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind));
+ clear_unused_annotations();
+ return;
+ }
+ }
+ if (member->identifier != nullptr) {
+ // Enums may be unnamed.
+ // TODO: Consider names in outer scope too, for constants and classes (and static functions?)
+ if (current_class->members_indices.has(member->identifier->name)) {
+ push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier);
+ } else {
+ current_class->add_member(member);
+ }
+ }
+}
- case GDScriptTokenizer::TK_OP_IN:
- op = OperatorNode::OP_IN;
- break;
- case GDScriptTokenizer::TK_OP_EQUAL:
- op = OperatorNode::OP_EQUAL;
- break;
- case GDScriptTokenizer::TK_OP_NOT_EQUAL:
- op = OperatorNode::OP_NOT_EQUAL;
- break;
- case GDScriptTokenizer::TK_OP_LESS:
- op = OperatorNode::OP_LESS;
- break;
- case GDScriptTokenizer::TK_OP_LESS_EQUAL:
- op = OperatorNode::OP_LESS_EQUAL;
- break;
- case GDScriptTokenizer::TK_OP_GREATER:
- op = OperatorNode::OP_GREATER;
+void GDScriptParser::parse_class_body() {
+ bool class_end = false;
+ while (!class_end && !is_at_end()) {
+ switch (current.type) {
+ case GDScriptTokenizer::Token::VAR:
+ parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable");
break;
- case GDScriptTokenizer::TK_OP_GREATER_EQUAL:
- op = OperatorNode::OP_GREATER_EQUAL;
+ case GDScriptTokenizer::Token::CONST:
+ parse_class_member(&GDScriptParser::parse_constant, AnnotationInfo::CONSTANT, "constant");
break;
- case GDScriptTokenizer::TK_OP_AND:
- op = OperatorNode::OP_AND;
+ case GDScriptTokenizer::Token::SIGNAL:
+ parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
break;
- case GDScriptTokenizer::TK_OP_OR:
- op = OperatorNode::OP_OR;
+ case GDScriptTokenizer::Token::STATIC:
+ case GDScriptTokenizer::Token::FUNC:
+ parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function");
break;
- case GDScriptTokenizer::TK_OP_ADD:
- op = OperatorNode::OP_ADD;
+ case GDScriptTokenizer::Token::CLASS:
+ parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class");
break;
- case GDScriptTokenizer::TK_OP_SUB:
- op = OperatorNode::OP_SUB;
+ case GDScriptTokenizer::Token::ENUM:
+ parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
break;
- case GDScriptTokenizer::TK_OP_MUL:
- op = OperatorNode::OP_MUL;
- break;
- case GDScriptTokenizer::TK_OP_DIV:
- op = OperatorNode::OP_DIV;
- break;
- case GDScriptTokenizer::TK_OP_MOD:
- op = OperatorNode::OP_MOD;
- break;
- //case GDScriptTokenizer::TK_OP_NEG: op=OperatorNode::OP_NEG ; break;
- case GDScriptTokenizer::TK_OP_SHIFT_LEFT:
- op = OperatorNode::OP_SHIFT_LEFT;
- break;
- case GDScriptTokenizer::TK_OP_SHIFT_RIGHT:
- op = OperatorNode::OP_SHIFT_RIGHT;
- break;
- case GDScriptTokenizer::TK_OP_ASSIGN: {
- _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN;
-
- if (tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) {
- //code complete assignment
- completion_type = COMPLETION_ASSIGN;
- completion_node = expr;
- completion_class = current_class;
- completion_function = current_function;
- completion_line = tokenizer->get_token_line();
- completion_block = current_block;
- completion_found = true;
- tokenizer->advance();
+ case GDScriptTokenizer::Token::ANNOTATION: {
+ advance();
+ AnnotationNode *annotation = parse_annotation(AnnotationInfo::CLASS_LEVEL);
+ if (annotation != nullptr) {
+ annotation_stack.push_back(annotation);
}
-
- } break;
- case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
- _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_ADD;
- break;
- case GDScriptTokenizer::TK_OP_ASSIGN_SUB:
- _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SUB;
- break;
- case GDScriptTokenizer::TK_OP_ASSIGN_MUL:
- _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MUL;
- break;
- case GDScriptTokenizer::TK_OP_ASSIGN_DIV:
- _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_DIV;
- break;
- case GDScriptTokenizer::TK_OP_ASSIGN_MOD:
- _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MOD;
- break;
- case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
- _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_LEFT;
break;
- case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
- _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_RIGHT;
- break;
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
- _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_AND;
- break;
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
- _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_OR;
- break;
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
- _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_XOR;
- break;
- case GDScriptTokenizer::TK_OP_BIT_AND:
- op = OperatorNode::OP_BIT_AND;
- break;
- case GDScriptTokenizer::TK_OP_BIT_OR:
- op = OperatorNode::OP_BIT_OR;
- break;
- case GDScriptTokenizer::TK_OP_BIT_XOR:
- op = OperatorNode::OP_BIT_XOR;
- break;
- case GDScriptTokenizer::TK_PR_IS:
- op = OperatorNode::OP_IS;
- break;
- case GDScriptTokenizer::TK_CF_IF:
- op = OperatorNode::OP_TERNARY_IF;
+ }
+ case GDScriptTokenizer::Token::PASS:
+ advance();
+ end_statement(R"("pass")");
break;
- case GDScriptTokenizer::TK_CF_ELSE:
- op = OperatorNode::OP_TERNARY_ELSE;
+ case GDScriptTokenizer::Token::DEDENT:
+ class_end = true;
break;
default:
- valid = false;
+ push_error(vformat(R"(Unexpected "%s" in class body.)", current.get_name()));
+ advance();
break;
}
-
- if (valid) {
- e.is_op = true;
- e.op = op;
- expression.push_back(e);
- tokenizer->advance();
- } else {
- break;
+ if (panic_mode) {
+ synchronize();
}
}
+}
- /* Reduce the set set of expressions and place them in an operator tree, respecting precedence */
-
- while (expression.size() > 1) {
- int next_op = -1;
- int min_priority = 0xFFFFF;
- bool is_unary = false;
- bool is_ternary = false;
-
- for (int i = 0; i < expression.size(); i++) {
- if (!expression[i].is_op) {
- continue;
- }
-
- int priority;
-
- bool unary = false;
- bool ternary = false;
- bool error = false;
- bool right_to_left = false;
-
- switch (expression[i].op) {
- case OperatorNode::OP_IS:
- case OperatorNode::OP_IS_BUILTIN:
- priority = -1;
- break; //before anything
-
- case OperatorNode::OP_BIT_INVERT:
- priority = 0;
- unary = true;
- break;
- case OperatorNode::OP_NEG:
- case OperatorNode::OP_POS:
- priority = 1;
- unary = true;
- break;
-
- case OperatorNode::OP_MUL:
- priority = 2;
- break;
- case OperatorNode::OP_DIV:
- priority = 2;
- break;
- case OperatorNode::OP_MOD:
- priority = 2;
- break;
-
- case OperatorNode::OP_ADD:
- priority = 3;
- break;
- case OperatorNode::OP_SUB:
- priority = 3;
- break;
-
- case OperatorNode::OP_SHIFT_LEFT:
- priority = 4;
- break;
- case OperatorNode::OP_SHIFT_RIGHT:
- priority = 4;
- break;
-
- case OperatorNode::OP_BIT_AND:
- priority = 5;
- break;
- case OperatorNode::OP_BIT_XOR:
- priority = 6;
- break;
- case OperatorNode::OP_BIT_OR:
- priority = 7;
- break;
-
- case OperatorNode::OP_LESS:
- priority = 8;
- break;
- case OperatorNode::OP_LESS_EQUAL:
- priority = 8;
- break;
- case OperatorNode::OP_GREATER:
- priority = 8;
- break;
- case OperatorNode::OP_GREATER_EQUAL:
- priority = 8;
- break;
-
- case OperatorNode::OP_EQUAL:
- priority = 8;
- break;
- case OperatorNode::OP_NOT_EQUAL:
- priority = 8;
- break;
-
- case OperatorNode::OP_IN:
- priority = 10;
- break;
+GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
+ return parse_variable(true);
+}
- case OperatorNode::OP_NOT:
- priority = 11;
- unary = true;
- break;
- case OperatorNode::OP_AND:
- priority = 12;
- break;
- case OperatorNode::OP_OR:
- priority = 13;
- break;
+GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_property) {
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
+ return nullptr;
+ }
- case OperatorNode::OP_TERNARY_IF:
- priority = 14;
- ternary = true;
- right_to_left = true;
- break;
- case OperatorNode::OP_TERNARY_ELSE:
- priority = 14;
- error = true;
- // Rigth-to-left should be false in this case, otherwise it would always error.
- break;
+ VariableNode *variable = alloc_node<VariableNode>();
+ variable->identifier = parse_identifier();
- case OperatorNode::OP_ASSIGN:
- priority = 15;
- break;
- case OperatorNode::OP_ASSIGN_ADD:
- priority = 15;
- break;
- case OperatorNode::OP_ASSIGN_SUB:
- priority = 15;
- break;
- case OperatorNode::OP_ASSIGN_MUL:
- priority = 15;
- break;
- case OperatorNode::OP_ASSIGN_DIV:
- priority = 15;
- break;
- case OperatorNode::OP_ASSIGN_MOD:
- priority = 15;
- break;
- case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
- priority = 15;
- break;
- case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
- priority = 15;
- break;
- case OperatorNode::OP_ASSIGN_BIT_AND:
- priority = 15;
- break;
- case OperatorNode::OP_ASSIGN_BIT_OR:
- priority = 15;
- break;
- case OperatorNode::OP_ASSIGN_BIT_XOR:
- priority = 15;
- break;
+ if (match(GDScriptTokenizer::Token::COLON)) {
+ if (check(GDScriptTokenizer::Token::NEWLINE)) {
+ if (p_allow_property) {
+ advance();
- default: {
- _set_error("GDScriptParser bug, invalid operator in expression: " + itos(expression[i].op));
- return nullptr;
- }
+ return parse_property(variable, true);
+ } else {
+ push_error(R"(Expected type after ":")");
+ return nullptr;
}
-
- if (priority < min_priority || (right_to_left && priority == min_priority)) {
- // < is used for left to right (default)
- // <= is used for right to left
- if (error) {
- _set_error("Unexpected operator");
- return nullptr;
+ } else if (check((GDScriptTokenizer::Token::EQUAL))) {
+ // Infer type.
+ variable->infer_datatype = true;
+ } else {
+ if (p_allow_property) {
+ make_completion_context(COMPLETION_PROPERTY_DECLARATION_OR_TYPE, variable);
+ if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
+ // Check if get or set.
+ if (current.get_identifier() == "get" || current.get_identifier() == "set") {
+ return parse_property(variable, false);
+ }
}
- next_op = i;
- min_priority = priority;
- is_unary = unary;
- is_ternary = ternary;
}
- }
- if (next_op == -1) {
- _set_error("Yet another parser bug....");
- ERR_FAIL_V(nullptr);
+ // Parse type.
+ variable->datatype_specifier = parse_type();
}
+ }
- // OK! create operator..
- if (is_unary) {
- int expr_pos = next_op;
- while (expression[expr_pos].is_op) {
- expr_pos++;
- if (expr_pos == expression.size()) {
- //can happen..
- _set_error("Unexpected end of expression...");
- return nullptr;
- }
- }
-
- //consecutively do unary operators
- for (int i = expr_pos - 1; i >= next_op; i--) {
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = expression[i].op;
- op->arguments.push_back(expression[i + 1].node);
- op->line = op_line; //line might have been changed from a \n
- expression.write[i].is_op = false;
- expression.write[i].node = op;
- expression.remove(i + 1);
- }
-
- } else if (is_ternary) {
- if (next_op < 1 || next_op >= (expression.size() - 1)) {
- _set_error("Parser bug...");
- ERR_FAIL_V(nullptr);
- }
-
- if (next_op >= (expression.size() - 2) || expression[next_op + 2].op != OperatorNode::OP_TERNARY_ELSE) {
- _set_error("Expected else after ternary if.");
- return nullptr;
- }
- if (next_op >= (expression.size() - 3)) {
- _set_error("Expected value after ternary else.");
- return nullptr;
- }
-
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = expression[next_op].op;
- op->line = op_line; //line might have been changed from a \n
-
- if (expression[next_op - 1].is_op) {
- _set_error("Parser bug...");
- ERR_FAIL_V(nullptr);
- }
-
- if (expression[next_op + 1].is_op) {
- // this is not invalid and can really appear
- // but it becomes invalid anyway because no binary op
- // can be followed by a unary op in a valid combination,
- // due to how precedence works, unaries will always disappear first
-
- _set_error("Unexpected two consecutive operators after ternary if.");
- return nullptr;
- }
-
- if (expression[next_op + 3].is_op) {
- // this is not invalid and can really appear
- // but it becomes invalid anyway because no binary op
- // can be followed by a unary op in a valid combination,
- // due to how precedence works, unaries will always disappear first
-
- _set_error("Unexpected two consecutive operators after ternary else.");
- return nullptr;
- }
-
- op->arguments.push_back(expression[next_op + 1].node); //next expression goes as first
- op->arguments.push_back(expression[next_op - 1].node); //left expression goes as when-true
- op->arguments.push_back(expression[next_op + 3].node); //expression after next goes as when-false
+ if (match(GDScriptTokenizer::Token::EQUAL)) {
+ // Initializer.
+ variable->initializer = parse_expression(false);
+ variable->assignments++;
+ }
- //replace all 3 nodes by this operator and make it an expression
- expression.write[next_op - 1].node = op;
- expression.remove(next_op);
- expression.remove(next_op);
- expression.remove(next_op);
- expression.remove(next_op);
+ if (p_allow_property && match(GDScriptTokenizer::Token::COLON)) {
+ if (match(GDScriptTokenizer::Token::NEWLINE)) {
+ return parse_property(variable, true);
} else {
- if (next_op < 1 || next_op >= (expression.size() - 1)) {
- _set_error("Parser bug...");
- ERR_FAIL_V(nullptr);
- }
-
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = expression[next_op].op;
- op->line = op_line; //line might have been changed from a \n
-
- if (expression[next_op - 1].is_op) {
- _set_error("Parser bug...");
- ERR_FAIL_V(nullptr);
- }
-
- if (expression[next_op + 1].is_op) {
- // this is not invalid and can really appear
- // but it becomes invalid anyway because no binary op
- // can be followed by a unary op in a valid combination,
- // due to how precedence works, unaries will always disappear first
-
- _set_error("Unexpected two consecutive operators.");
- return nullptr;
- }
-
- op->arguments.push_back(expression[next_op - 1].node); //expression goes as left
- op->arguments.push_back(expression[next_op + 1].node); //next expression goes as right
-
- //replace all 3 nodes by this operator and make it an expression
- expression.write[next_op - 1].node = op;
- expression.remove(next_op);
- expression.remove(next_op);
+ return parse_property(variable, false);
}
}
- return expression[0].node;
-}
-
-GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to_const) {
- switch (p_node->type) {
- case Node::TYPE_BUILT_IN_FUNCTION: {
- //many may probably be optimizable
- return p_node;
- } break;
- case Node::TYPE_ARRAY: {
- ArrayNode *an = static_cast<ArrayNode *>(p_node);
- bool all_constants = true;
-
- for (int i = 0; i < an->elements.size(); i++) {
- an->elements.write[i] = _reduce_expression(an->elements[i], p_to_const);
- if (an->elements[i]->type != Node::TYPE_CONSTANT) {
- all_constants = false;
- }
- }
-
- if (all_constants && p_to_const) {
- //reduce constant array expression
-
- ConstantNode *cn = alloc_node<ConstantNode>();
- Array arr;
- arr.resize(an->elements.size());
- for (int i = 0; i < an->elements.size(); i++) {
- ConstantNode *acn = static_cast<ConstantNode *>(an->elements[i]);
- arr[i] = acn->value;
- }
- cn->value = arr;
- cn->datatype = _type_from_variant(cn->value);
- return cn;
- }
+ end_statement("variable declaration");
- return an;
+ variable->export_info.name = variable->identifier->name;
- } break;
- case Node::TYPE_DICTIONARY: {
- DictionaryNode *dn = static_cast<DictionaryNode *>(p_node);
- bool all_constants = true;
-
- for (int i = 0; i < dn->elements.size(); i++) {
- dn->elements.write[i].key = _reduce_expression(dn->elements[i].key, p_to_const);
- if (dn->elements[i].key->type != Node::TYPE_CONSTANT) {
- all_constants = false;
- }
- dn->elements.write[i].value = _reduce_expression(dn->elements[i].value, p_to_const);
- if (dn->elements[i].value->type != Node::TYPE_CONSTANT) {
- all_constants = false;
- }
- }
+ return variable;
+}
- if (all_constants && p_to_const) {
- //reduce constant array expression
+GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_variable, bool p_need_indent) {
+ if (p_need_indent) {
+ if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block for property after ":".)")) {
+ return nullptr;
+ }
+ }
- ConstantNode *cn = alloc_node<ConstantNode>();
- Dictionary dict;
- for (int i = 0; i < dn->elements.size(); i++) {
- ConstantNode *key_c = static_cast<ConstantNode *>(dn->elements[i].key);
- ConstantNode *value_c = static_cast<ConstantNode *>(dn->elements[i].value);
+ VariableNode *property = p_variable;
- dict[key_c->value] = value_c->value;
- }
- cn->value = dict;
- cn->datatype = _type_from_variant(cn->value);
- return cn;
- }
+ make_completion_context(COMPLETION_PROPERTY_DECLARATION, property);
- return dn;
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected "get" or "set" for property declaration.)")) {
+ return nullptr;
+ }
- } break;
- case Node::TYPE_OPERATOR: {
- OperatorNode *op = static_cast<OperatorNode *>(p_node);
+ IdentifierNode *function = parse_identifier();
- bool all_constants = true;
- int last_not_constant = -1;
+ if (check(GDScriptTokenizer::Token::EQUAL)) {
+ p_variable->property = VariableNode::PROP_SETGET;
+ } else {
+ p_variable->property = VariableNode::PROP_INLINE;
+ if (!p_need_indent) {
+ push_error("Property with inline code must go to an indented block.");
+ }
+ }
- for (int i = 0; i < op->arguments.size(); i++) {
- op->arguments.write[i] = _reduce_expression(op->arguments[i], p_to_const);
- if (op->arguments[i]->type != Node::TYPE_CONSTANT) {
- all_constants = false;
- last_not_constant = i;
- }
- }
+ bool getter_used = false;
+ bool setter_used = false;
- if (op->op == OperatorNode::OP_IS) {
- //nothing much
- return op;
+ // Run with a loop because order doesn't matter.
+ for (int i = 0; i < 2; i++) {
+ if (function->name == "set") {
+ if (setter_used) {
+ push_error(R"(Properties can only have one setter.)");
+ } else {
+ parse_property_setter(property);
+ setter_used = true;
}
- if (op->op == OperatorNode::OP_PARENT_CALL) {
- //nothing much
- return op;
-
- } else if (op->op == OperatorNode::OP_CALL) {
- //can reduce base type constructors
- if ((op->arguments[0]->type == Node::TYPE_TYPE || (op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && GDScriptFunctions::is_deterministic(static_cast<BuiltInFunctionNode *>(op->arguments[0])->function))) && last_not_constant == 0) {
- //native type constructor or intrinsic function
- const Variant **vptr = nullptr;
- Vector<Variant *> ptrs;
- if (op->arguments.size() > 1) {
- ptrs.resize(op->arguments.size() - 1);
- for (int i = 0; i < ptrs.size(); i++) {
- ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[i + 1]);
- ptrs.write[i] = &cn->value;
- }
-
- vptr = (const Variant **)&ptrs[0];
- }
-
- Callable::CallError ce;
- Variant v;
-
- if (op->arguments[0]->type == Node::TYPE_TYPE) {
- TypeNode *tn = static_cast<TypeNode *>(op->arguments[0]);
- v = Variant::construct(tn->vtype, vptr, ptrs.size(), ce);
-
- } else {
- GDScriptFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function;
- GDScriptFunctions::call(func, vptr, ptrs.size(), v, ce);
- }
-
- if (ce.error != Callable::CallError::CALL_OK) {
- String errwhere;
- if (op->arguments[0]->type == Node::TYPE_TYPE) {
- TypeNode *tn = static_cast<TypeNode *>(op->arguments[0]);
- errwhere = "'" + Variant::get_type_name(tn->vtype) + "' constructor";
-
- } else {
- GDScriptFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function;
- errwhere = String("'") + GDScriptFunctions::get_func_name(func) + "' intrinsic function";
- }
-
- switch (ce.error) {
- case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
- _set_error("Invalid argument (#" + itos(ce.argument + 1) + ") for " + errwhere + ".");
-
- } break;
- case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: {
- _set_error("Too many arguments for " + errwhere + ".");
- } break;
- case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: {
- _set_error("Too few arguments for " + errwhere + ".");
- } break;
- default: {
- _set_error("Invalid arguments for " + errwhere + ".");
-
- } break;
- }
-
- error_line = op->line;
-
- return p_node;
- }
-
- ConstantNode *cn = alloc_node<ConstantNode>();
- cn->value = v;
- cn->datatype = _type_from_variant(v);
- return cn;
- }
-
- return op; //don't reduce yet
-
- } else if (op->op == OperatorNode::OP_YIELD) {
- return op;
-
- } else if (op->op == OperatorNode::OP_INDEX) {
- //can reduce indices into constant arrays or dictionaries
-
- if (all_constants) {
- ConstantNode *ca = static_cast<ConstantNode *>(op->arguments[0]);
- ConstantNode *cb = static_cast<ConstantNode *>(op->arguments[1]);
-
- bool valid;
-
- Variant v = ca->value.get(cb->value, &valid);
- if (!valid) {
- _set_error("invalid index in constant expression");
- error_line = op->line;
- return op;
- }
-
- ConstantNode *cn = alloc_node<ConstantNode>();
- cn->value = v;
- cn->datatype = _type_from_variant(v);
- return cn;
- }
-
- return op;
-
- } else if (op->op == OperatorNode::OP_INDEX_NAMED) {
- if (op->arguments[0]->type == Node::TYPE_CONSTANT && op->arguments[1]->type == Node::TYPE_IDENTIFIER) {
- ConstantNode *ca = static_cast<ConstantNode *>(op->arguments[0]);
- IdentifierNode *ib = static_cast<IdentifierNode *>(op->arguments[1]);
-
- bool valid;
- Variant v = ca->value.get_named(ib->name, &valid);
- if (!valid) {
- _set_error("invalid index '" + String(ib->name) + "' in constant expression");
- error_line = op->line;
- return op;
- }
-
- ConstantNode *cn = alloc_node<ConstantNode>();
- cn->value = v;
- cn->datatype = _type_from_variant(v);
- return cn;
- }
-
- return op;
+ } else if (function->name == "get") {
+ if (getter_used) {
+ push_error(R"(Properties can only have one getter.)");
+ } else {
+ parse_property_getter(property);
+ getter_used = true;
}
+ } else {
+ // TODO: Update message to only have the missing one if it's the case.
+ push_error(R"(Expected "get" or "set" for property declaration.)");
+ }
- //validate assignment (don't assign to constant expression
- switch (op->op) {
- case OperatorNode::OP_ASSIGN:
- case OperatorNode::OP_ASSIGN_ADD:
- case OperatorNode::OP_ASSIGN_SUB:
- case OperatorNode::OP_ASSIGN_MUL:
- case OperatorNode::OP_ASSIGN_DIV:
- case OperatorNode::OP_ASSIGN_MOD:
- case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
- case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
- case OperatorNode::OP_ASSIGN_BIT_AND:
- case OperatorNode::OP_ASSIGN_BIT_OR:
- case OperatorNode::OP_ASSIGN_BIT_XOR: {
- if (op->arguments[0]->type == Node::TYPE_CONSTANT) {
- _set_error("Can't assign to constant", tokenizer->get_token_line() - 1);
- error_line = op->line;
- return op;
- } else if (op->arguments[0]->type == Node::TYPE_SELF) {
- _set_error("Can't assign to self.", op->line);
- error_line = op->line;
- return op;
- }
-
- if (op->arguments[0]->type == Node::TYPE_OPERATOR) {
- OperatorNode *on = static_cast<OperatorNode *>(op->arguments[0]);
- if (on->op != OperatorNode::OP_INDEX && on->op != OperatorNode::OP_INDEX_NAMED) {
- _set_error("Can't assign to an expression", tokenizer->get_token_line() - 1);
- error_line = op->line;
- return op;
- }
- }
-
- } break;
- default: {
- break;
- }
- }
- //now se if all are constants
- if (!all_constants) {
- return op; //nothing to reduce from here on
- }
-#define _REDUCE_UNARY(m_vop) \
- bool valid = false; \
- Variant res; \
- Variant::evaluate(m_vop, static_cast<ConstantNode *>(op->arguments[0])->value, Variant(), res, valid); \
- if (!valid) { \
- _set_error("Invalid operand for unary operator"); \
- error_line = op->line; \
- return p_node; \
- } \
- ConstantNode *cn = alloc_node<ConstantNode>(); \
- cn->value = res; \
- cn->datatype = _type_from_variant(res); \
- return cn;
-
-#define _REDUCE_BINARY(m_vop) \
- bool valid = false; \
- Variant res; \
- Variant::evaluate(m_vop, static_cast<ConstantNode *>(op->arguments[0])->value, static_cast<ConstantNode *>(op->arguments[1])->value, res, valid); \
- if (!valid) { \
- _set_error("Invalid operands for operator"); \
- error_line = op->line; \
- return p_node; \
- } \
- ConstantNode *cn = alloc_node<ConstantNode>(); \
- cn->value = res; \
- cn->datatype = _type_from_variant(res); \
- return cn;
-
- switch (op->op) {
- //unary operators
- case OperatorNode::OP_NEG: {
- _REDUCE_UNARY(Variant::OP_NEGATE);
- } break;
- case OperatorNode::OP_POS: {
- _REDUCE_UNARY(Variant::OP_POSITIVE);
- } break;
- case OperatorNode::OP_NOT: {
- _REDUCE_UNARY(Variant::OP_NOT);
- } break;
- case OperatorNode::OP_BIT_INVERT: {
- _REDUCE_UNARY(Variant::OP_BIT_NEGATE);
- } break;
- //binary operators (in precedence order)
- case OperatorNode::OP_IN: {
- _REDUCE_BINARY(Variant::OP_IN);
- } break;
- case OperatorNode::OP_EQUAL: {
- _REDUCE_BINARY(Variant::OP_EQUAL);
- } break;
- case OperatorNode::OP_NOT_EQUAL: {
- _REDUCE_BINARY(Variant::OP_NOT_EQUAL);
- } break;
- case OperatorNode::OP_LESS: {
- _REDUCE_BINARY(Variant::OP_LESS);
- } break;
- case OperatorNode::OP_LESS_EQUAL: {
- _REDUCE_BINARY(Variant::OP_LESS_EQUAL);
- } break;
- case OperatorNode::OP_GREATER: {
- _REDUCE_BINARY(Variant::OP_GREATER);
- } break;
- case OperatorNode::OP_GREATER_EQUAL: {
- _REDUCE_BINARY(Variant::OP_GREATER_EQUAL);
- } break;
- case OperatorNode::OP_AND: {
- _REDUCE_BINARY(Variant::OP_AND);
- } break;
- case OperatorNode::OP_OR: {
- _REDUCE_BINARY(Variant::OP_OR);
- } break;
- case OperatorNode::OP_ADD: {
- _REDUCE_BINARY(Variant::OP_ADD);
- } break;
- case OperatorNode::OP_SUB: {
- _REDUCE_BINARY(Variant::OP_SUBTRACT);
- } break;
- case OperatorNode::OP_MUL: {
- _REDUCE_BINARY(Variant::OP_MULTIPLY);
- } break;
- case OperatorNode::OP_DIV: {
- _REDUCE_BINARY(Variant::OP_DIVIDE);
- } break;
- case OperatorNode::OP_MOD: {
- _REDUCE_BINARY(Variant::OP_MODULE);
- } break;
- case OperatorNode::OP_SHIFT_LEFT: {
- _REDUCE_BINARY(Variant::OP_SHIFT_LEFT);
- } break;
- case OperatorNode::OP_SHIFT_RIGHT: {
- _REDUCE_BINARY(Variant::OP_SHIFT_RIGHT);
- } break;
- case OperatorNode::OP_BIT_AND: {
- _REDUCE_BINARY(Variant::OP_BIT_AND);
- } break;
- case OperatorNode::OP_BIT_OR: {
- _REDUCE_BINARY(Variant::OP_BIT_OR);
- } break;
- case OperatorNode::OP_BIT_XOR: {
- _REDUCE_BINARY(Variant::OP_BIT_XOR);
- } break;
- case OperatorNode::OP_TERNARY_IF: {
- if (static_cast<ConstantNode *>(op->arguments[0])->value.booleanize()) {
- return op->arguments[1];
- } else {
- return op->arguments[2];
+ if (i == 0 && p_variable->property == VariableNode::PROP_SETGET) {
+ if (match(GDScriptTokenizer::Token::COMMA)) {
+ // Consume potential newline.
+ if (match(GDScriptTokenizer::Token::NEWLINE)) {
+ if (!p_need_indent) {
+ push_error(R"(Inline setter/getter setting cannot span across multiple lines (use "\\"" if needed).)");
}
- } break;
- default: {
- ERR_FAIL_V(op);
}
- }
-
- } break;
- default: {
- return p_node;
- } break;
- }
-}
-
-GDScriptParser::Node *GDScriptParser::_parse_and_reduce_expression(Node *p_parent, bool p_static, bool p_reduce_const, bool p_allow_assign) {
- Node *expr = _parse_expression(p_parent, p_static, p_allow_assign, p_reduce_const);
- if (!expr || error_set) {
- return nullptr;
- }
- expr = _reduce_expression(expr, p_reduce_const);
- if (!expr || error_set) {
- return nullptr;
- }
- return expr;
-}
-
-bool GDScriptParser::_reduce_export_var_type(Variant &p_value, int p_line) {
- if (p_value.get_type() == Variant::ARRAY) {
- Array arr = p_value;
- for (int i = 0; i < arr.size(); i++) {
- if (!_reduce_export_var_type(arr[i], p_line)) {
- return false;
+ } else {
+ break;
}
}
- return true;
- }
- if (p_value.get_type() == Variant::DICTIONARY) {
- Dictionary dict = p_value;
- for (int i = 0; i < dict.size(); i++) {
- Variant value = dict.get_value_at_index(i);
- if (!_reduce_export_var_type(value, p_line)) {
- return false;
- }
+ if (!match(GDScriptTokenizer::Token::IDENTIFIER)) {
+ break;
}
- return true;
+ function = parse_identifier();
}
- // validate type
- DataType type = _type_from_variant(p_value);
- if (type.kind == DataType::BUILTIN) {
- return true;
- } else if (type.kind == DataType::NATIVE) {
- if (ClassDB::is_parent_class(type.native_type, "Resource")) {
- return true;
- }
+ if (p_variable->property == VariableNode::PROP_SETGET) {
+ end_statement("property declaration");
}
- _set_error("Invalid export type. Only built-in and native resource types can be exported.", p_line);
- return false;
-}
-bool GDScriptParser::_recover_from_completion() {
- if (!completion_found) {
- return false; //can't recover if no completion
- }
- //skip stuff until newline
- while (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE && tokenizer->get_token() != GDScriptTokenizer::TK_EOF && tokenizer->get_token() != GDScriptTokenizer::TK_ERROR) {
- tokenizer->advance();
+ if (p_need_indent) {
+ consume(GDScriptTokenizer::Token::DEDENT, R"(Expected end of indented block for property.)");
}
- completion_found = false;
- error_set = false;
- if (tokenizer->get_token() == GDScriptTokenizer::TK_ERROR) {
- error_set = true;
- }
-
- return true;
+ return property;
}
-GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) {
- PatternNode *pattern = alloc_node<PatternNode>();
-
- GDScriptTokenizer::Token token = tokenizer->get_token();
- if (error_set) {
- return nullptr;
- }
-
- if (token == GDScriptTokenizer::TK_EOF) {
- return nullptr;
- }
-
- switch (token) {
- // array
- case GDScriptTokenizer::TK_BRACKET_OPEN: {
- tokenizer->advance();
- pattern->pt_type = GDScriptParser::PatternNode::PT_ARRAY;
- while (true) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) {
- tokenizer->advance();
- break;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) {
- // match everything
- tokenizer->advance(2);
- PatternNode *sub_pattern = alloc_node<PatternNode>();
- sub_pattern->pt_type = GDScriptParser::PatternNode::PT_IGNORE_REST;
- pattern->array.push_back(sub_pattern);
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA && tokenizer->get_token(1) == GDScriptTokenizer::TK_BRACKET_CLOSE) {
- tokenizer->advance(2);
- break;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) {
- tokenizer->advance(1);
- break;
- } else {
- _set_error("'..' pattern only allowed at the end of an array pattern");
- return nullptr;
- }
- }
-
- PatternNode *sub_pattern = _parse_pattern(p_static);
- if (!sub_pattern) {
- return nullptr;
- }
-
- pattern->array.push_back(sub_pattern);
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- tokenizer->advance();
- continue;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) {
- tokenizer->advance();
- break;
- } else {
- _set_error("Not a valid pattern");
- return nullptr;
- }
+void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
+ switch (p_variable->property) {
+ case VariableNode::PROP_INLINE:
+ consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)");
+ if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name after "(".)")) {
+ p_variable->setter_parameter = parse_identifier();
}
- } break;
- // bind
- case GDScriptTokenizer::TK_PR_VAR: {
- tokenizer->advance();
- if (!tokenizer->is_token_literal()) {
- _set_error("Expected identifier for binding variable name.");
- return nullptr;
- }
- pattern->pt_type = GDScriptParser::PatternNode::PT_BIND;
- pattern->bind = tokenizer->get_token_literal();
- // Check if variable name is already used
- BlockNode *bl = current_block;
- while (bl) {
- if (bl->variables.has(pattern->bind)) {
- _set_error("Binding name of '" + pattern->bind.operator String() + "' is already declared in this scope.");
- return nullptr;
- }
- bl = bl->parent_block;
- }
- // Create local variable for proper identifier detection later
- LocalVarNode *lv = alloc_node<LocalVarNode>();
- lv->name = pattern->bind;
- current_block->variables.insert(lv->name, lv);
- tokenizer->advance();
- } break;
- // dictionary
- case GDScriptTokenizer::TK_CURLY_BRACKET_OPEN: {
- tokenizer->advance();
- pattern->pt_type = GDScriptParser::PatternNode::PT_DICTIONARY;
- while (true) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
- tokenizer->advance();
- break;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) {
- // match everything
- tokenizer->advance(2);
- PatternNode *sub_pattern = alloc_node<PatternNode>();
- sub_pattern->pt_type = PatternNode::PT_IGNORE_REST;
- pattern->array.push_back(sub_pattern);
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA && tokenizer->get_token(1) == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
- tokenizer->advance(2);
- break;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
- tokenizer->advance(1);
- break;
- } else {
- _set_error("'..' pattern only allowed at the end of a dictionary pattern");
- return nullptr;
- }
- }
-
- Node *key = _parse_and_reduce_expression(pattern, p_static);
- if (!key) {
- _set_error("Not a valid key in pattern");
- return nullptr;
- }
-
- if (key->type != GDScriptParser::Node::TYPE_CONSTANT) {
- _set_error("Not a constant expression as key");
- return nullptr;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
- tokenizer->advance();
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*");
+ consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*");
- PatternNode *value = _parse_pattern(p_static);
- if (!value) {
- _set_error("Expected pattern in dictionary value");
- return nullptr;
- }
-
- pattern->dictionary.insert(static_cast<ConstantNode *>(key), value);
- } else {
- pattern->dictionary.insert(static_cast<ConstantNode *>(key), nullptr);
- }
+ p_variable->setter = parse_suite("setter definition");
+ break;
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- tokenizer->advance();
- continue;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
- tokenizer->advance();
- break;
- } else {
- _set_error("Not a valid pattern");
- return nullptr;
- }
+ case VariableNode::PROP_SETGET:
+ consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")");
+ make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable);
+ if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected setter function name after "=".)")) {
+ p_variable->setter_pointer = parse_identifier();
}
- } break;
- case GDScriptTokenizer::TK_WILDCARD: {
- tokenizer->advance();
- pattern->pt_type = PatternNode::PT_WILDCARD;
- } break;
- // all the constants like strings and numbers
- default: {
- Node *value = _parse_and_reduce_expression(pattern, p_static);
- if (!value) {
- _set_error("Expect constant expression or variables in a pattern");
- return nullptr;
- }
-
- if (value->type == Node::TYPE_OPERATOR) {
- // Maybe it's SomeEnum.VALUE
- Node *current_value = value;
-
- while (current_value->type == Node::TYPE_OPERATOR) {
- OperatorNode *op_node = static_cast<OperatorNode *>(current_value);
+ break;
+ case VariableNode::PROP_NONE:
+ break; // Unreachable.
+ }
+}
- if (op_node->op != OperatorNode::OP_INDEX_NAMED) {
- _set_error("Invalid operator in pattern. Only index (`A.B`) is allowed");
- return nullptr;
- }
- current_value = op_node->arguments[0];
- }
+void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
+ switch (p_variable->property) {
+ case VariableNode::PROP_INLINE:
+ consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)");
- if (current_value->type != Node::TYPE_IDENTIFIER) {
- _set_error("Only constant expression or variables allowed in a pattern");
- return nullptr;
- }
-
- } else if (value->type != Node::TYPE_IDENTIFIER && value->type != Node::TYPE_CONSTANT) {
- _set_error("Only constant expressions or variables allowed in a pattern");
- return nullptr;
+ p_variable->getter = parse_suite("getter definition");
+ break;
+ case VariableNode::PROP_SETGET:
+ consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")");
+ make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable);
+ if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected getter function name after "=".)")) {
+ p_variable->getter_pointer = parse_identifier();
}
-
- pattern->pt_type = PatternNode::PT_CONSTANT;
- pattern->constant = value;
- } break;
+ break;
+ case VariableNode::PROP_NONE:
+ break; // Unreachable.
}
-
- return pattern;
}
-void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static) {
- IndentLevel current_level = indent_level.back()->get();
-
- p_block->has_return = true;
-
- bool catch_all_appeared = false;
-
- while (true) {
- while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) {
- ;
- }
-
- // GDScriptTokenizer::Token token = tokenizer->get_token();
- if (error_set) {
- return;
- }
-
- if (current_level.indent > indent_level.back()->get().indent) {
- break; // go back a level
- }
-
- pending_newline = -1;
-
- PatternBranchNode *branch = alloc_node<PatternBranchNode>();
- branch->body = alloc_node<BlockNode>();
- branch->body->parent_block = p_block;
- p_block->sub_blocks.push_back(branch->body);
- current_block = branch->body;
-
- branch->patterns.push_back(_parse_pattern(p_static));
- if (!branch->patterns[0]) {
- break;
- }
+GDScriptParser::ConstantNode *GDScriptParser::parse_constant() {
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
+ return nullptr;
+ }
- bool has_binding = branch->patterns[0]->pt_type == PatternNode::PT_BIND;
- bool catch_all = has_binding || branch->patterns[0]->pt_type == PatternNode::PT_WILDCARD;
+ ConstantNode *constant = alloc_node<ConstantNode>();
+ constant->identifier = parse_identifier();
-#ifdef DEBUG_ENABLED
- // Branches after a wildcard or binding are unreachable
- if (catch_all_appeared && !current_function->has_unreachable_code) {
- _add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String());
- current_function->has_unreachable_code = true;
+ if (match(GDScriptTokenizer::Token::COLON)) {
+ if (check((GDScriptTokenizer::Token::EQUAL))) {
+ // Infer type.
+ constant->infer_datatype = true;
+ } else {
+ // Parse type.
+ constant->datatype_specifier = parse_type();
}
-#endif
-
- while (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- tokenizer->advance();
- branch->patterns.push_back(_parse_pattern(p_static));
- if (!branch->patterns[branch->patterns.size() - 1]) {
- return;
- }
+ }
- PatternNode::PatternType pt = branch->patterns[branch->patterns.size() - 1]->pt_type;
+ if (consume(GDScriptTokenizer::Token::EQUAL, R"(Expected initializer after constant name.)")) {
+ // Initializer.
+ constant->initializer = parse_expression(false);
- if (pt == PatternNode::PT_BIND) {
- _set_error("Cannot use bindings with multipattern.");
- return;
- }
-
- catch_all = catch_all || pt == PatternNode::PT_WILDCARD;
+ if (constant->initializer == nullptr) {
+ push_error(R"(Expected initializer expression for constant.)");
+ return nullptr;
}
+ }
- catch_all_appeared = catch_all_appeared || catch_all;
+ end_statement("constant declaration");
- if (!_enter_indent_block()) {
- _set_error("Expected block in pattern branch");
- return;
- }
+ return constant;
+}
- _parse_block(branch->body, p_static);
+GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name.)")) {
+ return nullptr;
+ }
- current_block = p_block;
+ ParameterNode *parameter = alloc_node<ParameterNode>();
+ parameter->identifier = parse_identifier();
- if (!branch->body->has_return) {
- p_block->has_return = false;
+ if (match(GDScriptTokenizer::Token::COLON)) {
+ if (check((GDScriptTokenizer::Token::EQUAL))) {
+ // Infer type.
+ parameter->infer_datatype = true;
+ } else {
+ // Parse type.
+ make_completion_context(COMPLETION_TYPE_NAME, parameter);
+ parameter->datatype_specifier = parse_type();
}
-
- p_branches.push_back(branch);
}
- // Even if all branches return, there is possibility of default fallthrough
- if (!catch_all_appeared) {
- p_block->has_return = false;
+ if (match(GDScriptTokenizer::Token::EQUAL)) {
+ // Default value.
+ parameter->default_value = parse_expression(false);
}
-}
-
-void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) {
- const DataType &to_match_type = p_node_to_match->get_datatype();
-
- switch (p_pattern->pt_type) {
- case PatternNode::PT_CONSTANT: {
- DataType pattern_type = _reduce_node_type(p_pattern->constant);
- if (error_set) {
- return;
- }
-
- OperatorNode *type_comp = nullptr;
-
- // static type check if possible
- if (pattern_type.has_type && to_match_type.has_type) {
- if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) {
- _set_error("The pattern type (" + pattern_type.to_string() + ") isn't compatible with the type of the value to match (" + to_match_type.to_string() + ").",
- p_pattern->line);
- return;
- }
- } else {
- // runtime typecheck
- BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
- typeof_node->function = GDScriptFunctions::TYPE_OF;
-
- OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
- typeof_match_value->op = OperatorNode::OP_CALL;
- typeof_match_value->arguments.push_back(typeof_node);
- typeof_match_value->arguments.push_back(p_node_to_match);
-
- OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>();
- typeof_pattern_value->op = OperatorNode::OP_CALL;
- typeof_pattern_value->arguments.push_back(typeof_node);
- typeof_pattern_value->arguments.push_back(p_pattern->constant);
-
- type_comp = alloc_node<OperatorNode>();
- type_comp->op = OperatorNode::OP_EQUAL;
- type_comp->arguments.push_back(typeof_match_value);
- type_comp->arguments.push_back(typeof_pattern_value);
- }
-
- // compare the actual values
- OperatorNode *value_comp = alloc_node<OperatorNode>();
- value_comp->op = OperatorNode::OP_EQUAL;
- value_comp->arguments.push_back(p_pattern->constant);
- value_comp->arguments.push_back(p_node_to_match);
-
- if (type_comp) {
- OperatorNode *full_comparison = alloc_node<OperatorNode>();
- full_comparison->op = OperatorNode::OP_AND;
- full_comparison->arguments.push_back(type_comp);
- full_comparison->arguments.push_back(value_comp);
-
- p_resulting_node = full_comparison;
- } else {
- p_resulting_node = value_comp;
- }
-
- } break;
- case PatternNode::PT_BIND: {
- p_bindings[p_pattern->bind] = p_node_to_match;
-
- // a bind always matches
- ConstantNode *true_value = alloc_node<ConstantNode>();
- true_value->value = Variant(true);
- true_value->datatype = _type_from_variant(true_value->value);
- p_resulting_node = true_value;
- } break;
- case PatternNode::PT_ARRAY: {
- bool open_ended = false;
-
- if (p_pattern->array.size() > 0) {
- if (p_pattern->array[p_pattern->array.size() - 1]->pt_type == PatternNode::PT_IGNORE_REST) {
- open_ended = true;
- }
- }
-
- // typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() >= length
- // typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() == length
-
- {
- OperatorNode *type_comp = nullptr;
- // static type check if possible
- if (to_match_type.has_type) {
- // must be an array
- if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::ARRAY) {
- _set_error("Cannot match an array pattern with a non-array expression.", p_pattern->line);
- return;
- }
- } else {
- // runtime typecheck
- BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
- typeof_node->function = GDScriptFunctions::TYPE_OF;
-
- OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
- typeof_match_value->op = OperatorNode::OP_CALL;
- typeof_match_value->arguments.push_back(typeof_node);
- typeof_match_value->arguments.push_back(p_node_to_match);
-
- IdentifierNode *typeof_array = alloc_node<IdentifierNode>();
- typeof_array->name = "TYPE_ARRAY";
-
- type_comp = alloc_node<OperatorNode>();
- type_comp->op = OperatorNode::OP_EQUAL;
- type_comp->arguments.push_back(typeof_match_value);
- type_comp->arguments.push_back(typeof_array);
- }
-
- // size
- ConstantNode *length = alloc_node<ConstantNode>();
- length->value = Variant(open_ended ? p_pattern->array.size() - 1 : p_pattern->array.size());
- length->datatype = _type_from_variant(length->value);
-
- OperatorNode *call = alloc_node<OperatorNode>();
- call->op = OperatorNode::OP_CALL;
- call->arguments.push_back(p_node_to_match);
-
- IdentifierNode *size = alloc_node<IdentifierNode>();
- size->name = "size";
- call->arguments.push_back(size);
-
- OperatorNode *length_comparison = alloc_node<OperatorNode>();
- length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL;
- length_comparison->arguments.push_back(call);
- length_comparison->arguments.push_back(length);
-
- if (type_comp) {
- OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
- type_and_length_comparison->op = OperatorNode::OP_AND;
- type_and_length_comparison->arguments.push_back(type_comp);
- type_and_length_comparison->arguments.push_back(length_comparison);
-
- p_resulting_node = type_and_length_comparison;
- } else {
- p_resulting_node = length_comparison;
- }
- }
-
- for (int i = 0; i < p_pattern->array.size(); i++) {
- PatternNode *pattern = p_pattern->array[i];
-
- Node *condition = nullptr;
-
- ConstantNode *index = alloc_node<ConstantNode>();
- index->value = Variant(i);
- index->datatype = _type_from_variant(index->value);
-
- OperatorNode *indexed_value = alloc_node<OperatorNode>();
- indexed_value->op = OperatorNode::OP_INDEX;
- indexed_value->arguments.push_back(p_node_to_match);
- indexed_value->arguments.push_back(index);
-
- _generate_pattern(pattern, indexed_value, condition, p_bindings);
- // concatenate all the patterns with &&
- OperatorNode *and_node = alloc_node<OperatorNode>();
- and_node->op = OperatorNode::OP_AND;
- and_node->arguments.push_back(p_resulting_node);
- and_node->arguments.push_back(condition);
-
- p_resulting_node = and_node;
- }
-
- } break;
- case PatternNode::PT_DICTIONARY: {
- bool open_ended = false;
-
- if (p_pattern->array.size() > 0) {
- open_ended = true;
- }
-
- // typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() >= length
- // typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() == length
-
- {
- OperatorNode *type_comp = nullptr;
- // static type check if possible
- if (to_match_type.has_type) {
- // must be an dictionary
- if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::DICTIONARY) {
- _set_error("Cannot match an dictionary pattern with a non-dictionary expression.", p_pattern->line);
- return;
- }
- } else {
- // runtime typecheck
- BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
- typeof_node->function = GDScriptFunctions::TYPE_OF;
-
- OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
- typeof_match_value->op = OperatorNode::OP_CALL;
- typeof_match_value->arguments.push_back(typeof_node);
- typeof_match_value->arguments.push_back(p_node_to_match);
-
- IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>();
- typeof_dictionary->name = "TYPE_DICTIONARY";
-
- type_comp = alloc_node<OperatorNode>();
- type_comp->op = OperatorNode::OP_EQUAL;
- type_comp->arguments.push_back(typeof_match_value);
- type_comp->arguments.push_back(typeof_dictionary);
- }
-
- // size
- ConstantNode *length = alloc_node<ConstantNode>();
- length->value = Variant(open_ended ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size());
- length->datatype = _type_from_variant(length->value);
-
- OperatorNode *call = alloc_node<OperatorNode>();
- call->op = OperatorNode::OP_CALL;
- call->arguments.push_back(p_node_to_match);
-
- IdentifierNode *size = alloc_node<IdentifierNode>();
- size->name = "size";
- call->arguments.push_back(size);
-
- OperatorNode *length_comparison = alloc_node<OperatorNode>();
- length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL;
- length_comparison->arguments.push_back(call);
- length_comparison->arguments.push_back(length);
-
- if (type_comp) {
- OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
- type_and_length_comparison->op = OperatorNode::OP_AND;
- type_and_length_comparison->arguments.push_back(type_comp);
- type_and_length_comparison->arguments.push_back(length_comparison);
-
- p_resulting_node = type_and_length_comparison;
- } else {
- p_resulting_node = length_comparison;
- }
- }
-
- for (Map<ConstantNode *, PatternNode *>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) {
- Node *condition = nullptr;
-
- // check for has, then for pattern
-
- IdentifierNode *has = alloc_node<IdentifierNode>();
- has->name = "has";
-
- OperatorNode *has_call = alloc_node<OperatorNode>();
- has_call->op = OperatorNode::OP_CALL;
- has_call->arguments.push_back(p_node_to_match);
- has_call->arguments.push_back(has);
- has_call->arguments.push_back(e->key());
-
- if (e->value()) {
- OperatorNode *indexed_value = alloc_node<OperatorNode>();
- indexed_value->op = OperatorNode::OP_INDEX;
- indexed_value->arguments.push_back(p_node_to_match);
- indexed_value->arguments.push_back(e->key());
-
- _generate_pattern(e->value(), indexed_value, condition, p_bindings);
-
- OperatorNode *has_and_pattern = alloc_node<OperatorNode>();
- has_and_pattern->op = OperatorNode::OP_AND;
- has_and_pattern->arguments.push_back(has_call);
- has_and_pattern->arguments.push_back(condition);
-
- condition = has_and_pattern;
-
- } else {
- condition = has_call;
- }
-
- // concatenate all the patterns with &&
- OperatorNode *and_node = alloc_node<OperatorNode>();
- and_node->op = OperatorNode::OP_AND;
- and_node->arguments.push_back(p_resulting_node);
- and_node->arguments.push_back(condition);
-
- p_resulting_node = and_node;
- }
-
- } break;
- case PatternNode::PT_IGNORE_REST:
- case PatternNode::PT_WILDCARD: {
- // simply generate a `true`
- ConstantNode *true_value = alloc_node<ConstantNode>();
- true_value->value = Variant(true);
- true_value->datatype = _type_from_variant(true_value->value);
- p_resulting_node = true_value;
- } break;
- default: {
- } break;
- }
+ return parameter;
}
-void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = "#match_value";
- id->line = p_match_statement->line;
- id->datatype = _reduce_node_type(p_match_statement->val_to_match);
- if (id->datatype.has_type) {
- _mark_line_as_safe(id->line);
- } else {
- _mark_line_as_unsafe(id->line);
- }
-
- if (error_set) {
- return;
+GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
+ return nullptr;
}
- for (int i = 0; i < p_match_statement->branches.size(); i++) {
- PatternBranchNode *branch = p_match_statement->branches[i];
+ SignalNode *signal = alloc_node<SignalNode>();
+ signal->identifier = parse_identifier();
- MatchNode::CompiledPatternBranch compiled_branch;
- compiled_branch.compiled_pattern = nullptr;
-
- Map<StringName, Node *> binding;
-
- for (int j = 0; j < branch->patterns.size(); j++) {
- PatternNode *pattern = branch->patterns[j];
- _mark_line_as_safe(pattern->line);
-
- Map<StringName, Node *> bindings;
- Node *resulting_node = nullptr;
- _generate_pattern(pattern, id, resulting_node, bindings);
-
- if (!resulting_node) {
- return;
+ if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ while (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
+ ParameterNode *parameter = parse_parameter();
+ if (parameter == nullptr) {
+ break;
}
-
- if (!binding.empty() && !bindings.empty()) {
- _set_error("Multipatterns can't contain bindings");
- return;
- } else {
- binding = bindings;
+ if (parameter->default_value != nullptr) {
+ push_error(R"(Signal parameters cannot have a default value.)");
}
-
- // Result is always a boolean
- DataType resulting_node_type;
- resulting_node_type.has_type = true;
- resulting_node_type.is_constant = true;
- resulting_node_type.kind = DataType::BUILTIN;
- resulting_node_type.builtin_type = Variant::BOOL;
- resulting_node->set_datatype(resulting_node_type);
-
- if (compiled_branch.compiled_pattern) {
- OperatorNode *or_node = alloc_node<OperatorNode>();
- or_node->op = OperatorNode::OP_OR;
- or_node->arguments.push_back(compiled_branch.compiled_pattern);
- or_node->arguments.push_back(resulting_node);
-
- compiled_branch.compiled_pattern = or_node;
+ if (signal->parameters_indices.has(parameter->identifier->name)) {
+ push_error(vformat(R"(Parameter with name "%s" was already declared for this signal.)", parameter->identifier->name));
} else {
- // single pattern | first one
- compiled_branch.compiled_pattern = resulting_node;
- }
- }
-
- // prepare the body ...hehe
- for (Map<StringName, Node *>::Element *e = binding.front(); e; e = e->next()) {
- if (!branch->body->variables.has(e->key())) {
- _set_error("Parser bug: missing pattern bind variable.", branch->line);
- ERR_FAIL();
+ signal->parameters_indices[parameter->identifier->name] = signal->parameters.size();
+ signal->parameters.push_back(parameter);
}
-
- LocalVarNode *local_var = branch->body->variables[e->key()];
- local_var->assign = e->value();
- local_var->set_datatype(local_var->assign->get_datatype());
- local_var->assignments++;
-
- IdentifierNode *id2 = alloc_node<IdentifierNode>();
- id2->name = local_var->name;
- id2->datatype = local_var->datatype;
- id2->declared_block = branch->body;
- id2->set_datatype(local_var->assign->get_datatype());
-
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_ASSIGN;
- op->arguments.push_back(id2);
- op->arguments.push_back(local_var->assign);
- local_var->assign_op = op;
-
- branch->body->statements.push_front(op);
- branch->body->statements.push_front(local_var);
}
-
- compiled_branch.body = branch->body;
-
- p_match_statement->compiled_pattern_branches.push_back(compiled_branch);
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");
}
-}
-
-void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
- IndentLevel current_level = indent_level.back()->get();
-
-#ifdef DEBUG_ENABLED
-
- pending_newline = -1; // reset for the new block
-
- NewLineNode *nl = alloc_node<NewLineNode>();
- nl->line = tokenizer->get_token_line();
- p_block->statements.push_back(nl);
-#endif
+ end_statement("signal declaration");
- bool is_first_line = true;
+ return signal;
+}
- while (true) {
- if (!is_first_line && indent_level.back()->prev() && indent_level.back()->prev()->get().indent == current_level.indent) {
- if (indent_level.back()->prev()->get().is_mixed(current_level)) {
- _set_error("Mixed tabs and spaces in indentation.");
- return;
- }
- // pythonic single-line expression, don't parse future lines
- indent_level.pop_back();
- p_block->end_line = tokenizer->get_token_line();
- return;
- }
- is_first_line = false;
+GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
+ EnumNode *enum_node = alloc_node<EnumNode>();
+ bool named = false;
- GDScriptTokenizer::Token token = tokenizer->get_token();
- if (error_set) {
- return;
- }
+ if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
+ advance();
+ enum_node->identifier = parse_identifier();
+ named = true;
+ }
- if (current_level.indent > indent_level.back()->get().indent) {
- p_block->end_line = tokenizer->get_token_line();
- return; //go back a level
- }
+ push_multiline(true);
+ consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
- if (pending_newline != -1) {
- NewLineNode *nl2 = alloc_node<NewLineNode>();
- nl2->line = pending_newline;
- p_block->statements.push_back(nl2);
- pending_newline = -1;
- }
+ int current_value = 0;
-#ifdef DEBUG_ENABLED
- switch (token) {
- case GDScriptTokenizer::TK_EOF:
- case GDScriptTokenizer::TK_ERROR:
- case GDScriptTokenizer::TK_NEWLINE:
- case GDScriptTokenizer::TK_CF_PASS: {
- // will check later
- } break;
- default: {
- if (p_block->has_return && !current_function->has_unreachable_code) {
- _add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String());
- current_function->has_unreachable_code = true;
- }
- } break;
+ do {
+ if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
+ break; // Allow trailing comma.
}
-#endif // DEBUG_ENABLED
- switch (token) {
- case GDScriptTokenizer::TK_EOF: {
- p_block->end_line = tokenizer->get_token_line();
- return; // End of file!
- } break;
- case GDScriptTokenizer::TK_ERROR: {
- return;
- } break;
- case GDScriptTokenizer::TK_NEWLINE: {
- int line = tokenizer->get_token_line();
-
- if (!_parse_newline()) {
- if (!error_set) {
- p_block->end_line = tokenizer->get_token_line();
- pending_newline = p_block->end_line;
- }
- return;
- }
-
- _mark_line_as_safe(line);
- NewLineNode *nl2 = alloc_node<NewLineNode>();
- nl2->line = line;
- p_block->statements.push_back(nl2);
-
- } break;
- case GDScriptTokenizer::TK_CF_PASS: {
- if (tokenizer->get_token(1) != GDScriptTokenizer::TK_SEMICOLON && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE && tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF) {
- _set_error("Expected \";\" or a line break.");
- return;
- }
- _mark_line_as_safe(tokenizer->get_token_line());
- tokenizer->advance();
- if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) {
- // Ignore semicolon after 'pass'.
- tokenizer->advance();
- }
- } break;
- case GDScriptTokenizer::TK_PR_VAR: {
- // Variable declaration and (eventual) initialization.
-
- tokenizer->advance();
- int var_line = tokenizer->get_token_line();
- if (!tokenizer->is_token_literal(0, true)) {
- _set_error("Expected an identifier for the local variable name.");
- return;
- }
- StringName n = tokenizer->get_token_literal();
- tokenizer->advance();
- if (current_function) {
- for (int i = 0; i < current_function->arguments.size(); i++) {
- if (n == current_function->arguments[i]) {
- _set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(current_function->line) + ").");
- return;
- }
- }
- }
- BlockNode *check_block = p_block;
- while (check_block) {
- if (check_block->variables.has(n)) {
- _set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(check_block->variables[n]->line) + ").");
- return;
- }
- check_block = check_block->parent_block;
- }
-
- //must know when the local variable is declared
- LocalVarNode *lv = alloc_node<LocalVarNode>();
- lv->name = n;
- lv->line = var_line;
- p_block->statements.push_back(lv);
-
- Node *assigned = nullptr;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
- if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
- lv->datatype = DataType();
-#ifdef DEBUG_ENABLED
- lv->datatype.infer_type = true;
-#endif
- tokenizer->advance();
- } else if (!_parse_type(lv->datatype)) {
- _set_error("Expected a type for the variable.");
- return;
- }
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
- tokenizer->advance();
- Node *subexpr = _parse_and_reduce_expression(p_block, p_static);
- if (!subexpr) {
- if (_recover_from_completion()) {
- break;
- }
- return;
- }
+ if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) {
+ EnumNode::Value item;
+ item.identifier = parse_identifier();
- lv->assignments++;
- assigned = subexpr;
- } else {
- assigned = _get_default_value_for_type(lv->datatype, var_line);
- }
- //must be added later, to avoid self-referencing.
- p_block->variables.insert(n, lv);
-
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = n;
- id->declared_block = p_block;
- id->line = var_line;
-
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_ASSIGN;
- op->arguments.push_back(id);
- op->arguments.push_back(assigned);
- op->line = var_line;
- p_block->statements.push_back(op);
- lv->assign_op = op;
- lv->assign = assigned;
-
- if (!_end_statement()) {
- _set_end_statement_error("var");
- return;
- }
-
- } break;
- case GDScriptTokenizer::TK_CF_IF: {
- tokenizer->advance();
-
- Node *condition = _parse_and_reduce_expression(p_block, p_static);
- if (!condition) {
- if (_recover_from_completion()) {
+ if (!named) {
+ // TODO: Abstract this recursive member check.
+ ClassNode *parent = current_class;
+ while (parent != nullptr) {
+ if (parent->members_indices.has(item.identifier->name)) {
+ push_error(vformat(R"(Name "%s" is already used as a class %s.)", item.identifier->name, parent->get_member(item.identifier->name).get_type_name()));
break;
}
- return;
- }
-
- ControlFlowNode *cf_if = alloc_node<ControlFlowNode>();
-
- cf_if->cf_type = ControlFlowNode::CF_IF;
- cf_if->arguments.push_back(condition);
-
- cf_if->body = alloc_node<BlockNode>();
- cf_if->body->parent_block = p_block;
- cf_if->body->if_condition = condition; //helps code completion
-
- p_block->sub_blocks.push_back(cf_if->body);
-
- if (!_enter_indent_block(cf_if->body)) {
- _set_error("Expected an indented block after \"if\".");
- p_block->end_line = tokenizer->get_token_line();
- return;
+ parent = parent->outer;
}
+ }
- current_block = cf_if->body;
- _parse_block(cf_if->body, p_static);
- current_block = p_block;
-
- if (error_set) {
- return;
- }
- p_block->statements.push_back(cf_if);
-
- bool all_have_return = cf_if->body->has_return;
- bool have_else = false;
-
- while (true) {
- while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) {
- ;
- }
-
- if (indent_level.back()->get().indent < current_level.indent) { //not at current indent level
- p_block->end_line = tokenizer->get_token_line();
- return;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELIF) {
- if (indent_level.back()->get().indent > current_level.indent) {
- _set_error("Invalid indentation.");
- return;
- }
-
- tokenizer->advance();
-
- cf_if->body_else = alloc_node<BlockNode>();
- cf_if->body_else->parent_block = p_block;
- p_block->sub_blocks.push_back(cf_if->body_else);
-
- ControlFlowNode *cf_else = alloc_node<ControlFlowNode>();
- cf_else->cf_type = ControlFlowNode::CF_IF;
-
- //condition
- Node *condition2 = _parse_and_reduce_expression(p_block, p_static);
- if (!condition2) {
- if (_recover_from_completion()) {
- break;
- }
- return;
- }
- cf_else->arguments.push_back(condition2);
- cf_else->cf_type = ControlFlowNode::CF_IF;
-
- cf_if->body_else->statements.push_back(cf_else);
- cf_if = cf_else;
- cf_if->body = alloc_node<BlockNode>();
- cf_if->body->parent_block = p_block;
- p_block->sub_blocks.push_back(cf_if->body);
-
- if (!_enter_indent_block(cf_if->body)) {
- _set_error("Expected an indented block after \"elif\".");
- p_block->end_line = tokenizer->get_token_line();
- return;
- }
-
- current_block = cf_else->body;
- _parse_block(cf_else->body, p_static);
- current_block = p_block;
- if (error_set) {
- return;
- }
-
- all_have_return = all_have_return && cf_else->body->has_return;
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) {
- if (indent_level.back()->get().indent > current_level.indent) {
- _set_error("Invalid indentation.");
- return;
- }
-
- tokenizer->advance();
- cf_if->body_else = alloc_node<BlockNode>();
- cf_if->body_else->parent_block = p_block;
- p_block->sub_blocks.push_back(cf_if->body_else);
-
- if (!_enter_indent_block(cf_if->body_else)) {
- _set_error("Expected an indented block after \"else\".");
- p_block->end_line = tokenizer->get_token_line();
- return;
- }
- current_block = cf_if->body_else;
- _parse_block(cf_if->body_else, p_static);
- current_block = p_block;
- if (error_set) {
- return;
- }
-
- all_have_return = all_have_return && cf_if->body_else->has_return;
- have_else = true;
-
- break; //after else, exit
+ if (match(GDScriptTokenizer::Token::EQUAL)) {
+ if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected integer value after "=".)")) {
+ item.custom_value = parse_literal();
+ if (item.custom_value->value.get_type() != Variant::INT) {
+ push_error(R"(Expected integer value after "=".)");
+ item.custom_value = nullptr;
} else {
- break;
- }
- }
-
- cf_if->body->has_return = all_have_return;
- // If there's no else block, path out of the if might not have a return
- p_block->has_return = all_have_return && have_else;
-
- } break;
- case GDScriptTokenizer::TK_CF_WHILE: {
- tokenizer->advance();
- Node *condition2 = _parse_and_reduce_expression(p_block, p_static);
- if (!condition2) {
- if (_recover_from_completion()) {
- break;
- }
- return;
- }
-
- ControlFlowNode *cf_while = alloc_node<ControlFlowNode>();
-
- cf_while->cf_type = ControlFlowNode::CF_WHILE;
- cf_while->arguments.push_back(condition2);
-
- cf_while->body = alloc_node<BlockNode>();
- cf_while->body->parent_block = p_block;
- cf_while->body->can_break = true;
- cf_while->body->can_continue = true;
- p_block->sub_blocks.push_back(cf_while->body);
-
- if (!_enter_indent_block(cf_while->body)) {
- _set_error("Expected an indented block after \"while\".");
- p_block->end_line = tokenizer->get_token_line();
- return;
- }
-
- current_block = cf_while->body;
- _parse_block(cf_while->body, p_static);
- current_block = p_block;
- if (error_set) {
- return;
- }
- p_block->statements.push_back(cf_while);
- } break;
- case GDScriptTokenizer::TK_CF_FOR: {
- tokenizer->advance();
-
- if (!tokenizer->is_token_literal(0, true)) {
- _set_error("Identifier expected after \"for\".");
- }
-
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = tokenizer->get_token_identifier();
-#ifdef DEBUG_ENABLED
- for (int j = 0; j < current_class->variables.size(); j++) {
- if (current_class->variables[j].identifier == id->name) {
- _add_warning(GDScriptWarning::SHADOWED_VARIABLE, id->line, id->name, itos(current_class->variables[j].line));
- }
- }
-#endif // DEBUG_ENABLED
-
- BlockNode *check_block = p_block;
- while (check_block) {
- if (check_block->variables.has(id->name)) {
- _set_error("Variable \"" + String(id->name) + "\" already defined in the scope (at line " + itos(check_block->variables[id->name]->line) + ").");
- return;
+ current_value = item.custom_value->value;
}
- check_block = check_block->parent_block;
- }
-
- tokenizer->advance();
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_IN) {
- _set_error("\"in\" expected after identifier.");
- return;
}
+ }
+ item.value = current_value++;
+ enum_node->values.push_back(item);
+ if (!named) {
+ // Add as member of current class.
+ current_class->add_member(item);
+ }
+ }
+ } while (match(GDScriptTokenizer::Token::COMMA));
- tokenizer->advance();
-
- Node *container = _parse_and_reduce_expression(p_block, p_static);
- if (!container) {
- if (_recover_from_completion()) {
- break;
- }
- return;
- }
-
- DataType iter_type;
-
- if (container->type == Node::TYPE_OPERATOR) {
- OperatorNode *op = static_cast<OperatorNode *>(container);
- if (op->op == OperatorNode::OP_CALL && op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && static_cast<BuiltInFunctionNode *>(op->arguments[0])->function == GDScriptFunctions::GEN_RANGE) {
- //iterating a range, so see if range() can be optimized without allocating memory, by replacing it by vectors (which can work as iterable too!)
-
- Vector<Node *> args;
- Vector<double> constants;
-
- bool constant = true;
-
- for (int i = 1; i < op->arguments.size(); i++) {
- args.push_back(op->arguments[i]);
- if (op->arguments[i]->type == Node::TYPE_CONSTANT) {
- ConstantNode *c = static_cast<ConstantNode *>(op->arguments[i]);
- if (c->value.get_type() == Variant::FLOAT || c->value.get_type() == Variant::INT) {
- constants.push_back(c->value);
- } else {
- constant = false;
- }
- } else {
- constant = false;
- }
- }
-
- if (args.size() > 0 && args.size() < 4) {
- if (constant) {
- ConstantNode *cn = alloc_node<ConstantNode>();
- switch (args.size()) {
- case 1:
- cn->value = (int64_t)constants[0];
- break;
- case 2:
- cn->value = Vector2i(constants[0], constants[1]);
- break;
- case 3:
- cn->value = Vector3i(constants[0], constants[1], constants[2]);
- break;
- }
- cn->datatype = _type_from_variant(cn->value);
- container = cn;
- } else {
- OperatorNode *on = alloc_node<OperatorNode>();
- on->op = OperatorNode::OP_CALL;
-
- TypeNode *tn = alloc_node<TypeNode>();
- on->arguments.push_back(tn);
-
- switch (args.size()) {
- case 1:
- tn->vtype = Variant::INT;
- break;
- case 2:
- tn->vtype = Variant::VECTOR2I;
- break;
- case 3:
- tn->vtype = Variant::VECTOR3I;
- break;
- }
-
- for (int i = 0; i < args.size(); i++) {
- on->arguments.push_back(args[i]);
- }
-
- container = on;
- }
- }
-
- iter_type.has_type = true;
- iter_type.kind = DataType::BUILTIN;
- iter_type.builtin_type = Variant::INT;
- }
- }
-
- ControlFlowNode *cf_for = alloc_node<ControlFlowNode>();
-
- cf_for->cf_type = ControlFlowNode::CF_FOR;
- cf_for->arguments.push_back(id);
- cf_for->arguments.push_back(container);
-
- cf_for->body = alloc_node<BlockNode>();
- cf_for->body->parent_block = p_block;
- cf_for->body->can_break = true;
- cf_for->body->can_continue = true;
- p_block->sub_blocks.push_back(cf_for->body);
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
- if (!_enter_indent_block(cf_for->body)) {
- _set_error("Expected indented block after \"for\".");
- p_block->end_line = tokenizer->get_token_line();
- return;
- }
+ end_statement("enum");
- current_block = cf_for->body;
-
- // this is for checking variable for redefining
- // inside this _parse_block
- LocalVarNode *lv = alloc_node<LocalVarNode>();
- lv->name = id->name;
- lv->line = id->line;
- lv->assignments++;
- id->declared_block = cf_for->body;
- lv->set_datatype(iter_type);
- id->set_datatype(iter_type);
- cf_for->body->variables.insert(id->name, lv);
- _parse_block(cf_for->body, p_static);
- current_block = p_block;
-
- if (error_set) {
- return;
- }
- p_block->statements.push_back(cf_for);
- } break;
- case GDScriptTokenizer::TK_CF_CONTINUE: {
- BlockNode *upper_block = p_block;
- bool is_continue_valid = false;
- while (upper_block) {
- if (upper_block->can_continue) {
- is_continue_valid = true;
- break;
- }
- upper_block = upper_block->parent_block;
- }
+ return enum_node;
+}
- if (!is_continue_valid) {
- _set_error("Unexpected keyword \"continue\" outside a loop.");
- return;
- }
+GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
+ bool _static = false;
+ if (previous.type == GDScriptTokenizer::Token::STATIC) {
+ // TODO: Improve message if user uses "static" with "var" or "const"
+ if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) {
+ return nullptr;
+ }
+ _static = true;
+ }
- _mark_line_as_safe(tokenizer->get_token_line());
- tokenizer->advance();
- ControlFlowNode *cf_continue = alloc_node<ControlFlowNode>();
- cf_continue->cf_type = ControlFlowNode::CF_CONTINUE;
- p_block->statements.push_back(cf_continue);
- if (!_end_statement()) {
- _set_end_statement_error("continue");
- return;
- }
- } break;
- case GDScriptTokenizer::TK_CF_BREAK: {
- BlockNode *upper_block = p_block;
- bool is_break_valid = false;
- while (upper_block) {
- if (upper_block->can_break) {
- is_break_valid = true;
- break;
- }
- upper_block = upper_block->parent_block;
- }
+ FunctionNode *function = alloc_node<FunctionNode>();
+ make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
- if (!is_break_valid) {
- _set_error("Unexpected keyword \"break\" outside a loop.");
- return;
- }
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
+ return nullptr;
+ }
- _mark_line_as_safe(tokenizer->get_token_line());
- tokenizer->advance();
- ControlFlowNode *cf_break = alloc_node<ControlFlowNode>();
- cf_break->cf_type = ControlFlowNode::CF_BREAK;
- p_block->statements.push_back(cf_break);
- if (!_end_statement()) {
- _set_end_statement_error("break");
- return;
- }
- } break;
- case GDScriptTokenizer::TK_CF_RETURN: {
- tokenizer->advance();
- ControlFlowNode *cf_return = alloc_node<ControlFlowNode>();
- cf_return->cf_type = ControlFlowNode::CF_RETURN;
- cf_return->line = tokenizer->get_token_line(-1);
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON || tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
- //expect end of statement
- p_block->statements.push_back(cf_return);
- if (!_end_statement()) {
- return;
- }
- } else {
- //expect expression
- Node *retexpr = _parse_and_reduce_expression(p_block, p_static);
- if (!retexpr) {
- if (_recover_from_completion()) {
- break;
- }
- return;
- }
- cf_return->arguments.push_back(retexpr);
- p_block->statements.push_back(cf_return);
- if (!_end_statement()) {
- _set_end_statement_error("return");
- return;
- }
- }
- p_block->has_return = true;
+ FunctionNode *previous_function = current_function;
+ current_function = function;
- } break;
- case GDScriptTokenizer::TK_CF_MATCH: {
- tokenizer->advance();
+ function->identifier = parse_identifier();
+ function->is_static = _static;
- MatchNode *match_node = alloc_node<MatchNode>();
+ push_multiline(true);
+ consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)");
- Node *val_to_match = _parse_and_reduce_expression(p_block, p_static);
+ SuiteNode *body = alloc_node<SuiteNode>();
+ SuiteNode *previous_suite = current_suite;
+ current_suite = body;
- if (!val_to_match) {
- if (_recover_from_completion()) {
- break;
- }
- return;
- }
-
- match_node->val_to_match = val_to_match;
-
- if (!_enter_indent_block()) {
- _set_error("Expected indented pattern matching block after \"match\".");
- return;
+ if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
+ bool default_used = false;
+ do {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+ // Allow for trailing comma.
+ break;
+ }
+ ParameterNode *parameter = parse_parameter();
+ if (parameter == nullptr) {
+ break;
+ }
+ if (parameter->default_value != nullptr) {
+ default_used = true;
+ } else {
+ if (default_used) {
+ push_error("Cannot have a mandatory parameters after optional parameters.");
+ continue;
}
+ }
+ if (function->parameters_indices.has(parameter->identifier->name)) {
+ push_error(vformat(R"(Parameter with name "%s" was already declared for this function.)", parameter->identifier->name));
+ } else {
+ function->parameters_indices[parameter->identifier->name] = function->parameters.size();
+ function->parameters.push_back(parameter);
+ body->add_local(parameter);
+ }
+ } while (match(GDScriptTokenizer::Token::COMMA));
+ }
- BlockNode *compiled_branches = alloc_node<BlockNode>();
- compiled_branches->parent_block = p_block;
- compiled_branches->parent_class = p_block->parent_class;
- compiled_branches->can_continue = true;
-
- p_block->sub_blocks.push_back(compiled_branches);
-
- _parse_pattern_block(compiled_branches, match_node->branches, p_static);
-
- if (error_set) {
- return;
- }
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after function parameters.)*");
- ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>();
- match_cf_node->cf_type = ControlFlowNode::CF_MATCH;
- match_cf_node->match = match_node;
- match_cf_node->body = compiled_branches;
+ if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
+ make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function);
+ function->return_type = parse_type(true);
+ }
- p_block->has_return = p_block->has_return || compiled_branches->has_return;
- p_block->statements.push_back(match_cf_node);
+ // TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
+ consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after function declaration.)");
- _end_statement();
- } break;
- case GDScriptTokenizer::TK_PR_ASSERT: {
- tokenizer->advance();
+ current_suite = previous_suite;
+ function->body = parse_suite("function declaration", body);
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- _set_error("Expected '(' after assert");
- return;
- }
+ current_function = previous_function;
+ return function;
+}
- int assert_line = tokenizer->get_token_line();
+GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_valid_targets) {
+ AnnotationNode *annotation = alloc_node<AnnotationNode>();
- tokenizer->advance();
+ annotation->name = previous.literal;
- Vector<Node *> args;
- const bool result = _parse_arguments(p_block, args, p_static);
- if (!result) {
- return;
- }
+ make_completion_context(COMPLETION_ANNOTATION, annotation);
- if (args.empty() || args.size() > 2) {
- _set_error("Wrong number of arguments, expected 1 or 2", assert_line);
- return;
- }
+ bool valid = true;
- AssertNode *an = alloc_node<AssertNode>();
- an->condition = _reduce_expression(args[0], p_static);
- an->line = assert_line;
+ if (!valid_annotations.has(annotation->name)) {
+ push_error(vformat(R"(Unrecognized annotation: "%s".)", annotation->name));
+ valid = false;
+ }
- if (args.size() == 2) {
- an->message = _reduce_expression(args[1], p_static);
- } else {
- ConstantNode *message_node = alloc_node<ConstantNode>();
- message_node->value = String();
- message_node->datatype = _type_from_variant(message_node->value);
- an->message = message_node;
- }
+ annotation->info = &valid_annotations[annotation->name];
- p_block->statements.push_back(an);
+ if (!annotation->applies_to(p_valid_targets)) {
+ push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ valid = false;
+ }
- if (!_end_statement()) {
- _set_end_statement_error("assert");
- return;
- }
- } break;
- case GDScriptTokenizer::TK_PR_BREAKPOINT: {
- tokenizer->advance();
- BreakpointNode *bn = alloc_node<BreakpointNode>();
- p_block->statements.push_back(bn);
-
- if (!_end_statement()) {
- _set_end_statement_error("breakpoint");
- return;
- }
- } break;
- default: {
- Node *expression = _parse_and_reduce_expression(p_block, p_static, false, true);
- if (!expression) {
- if (_recover_from_completion()) {
- break;
- }
- return;
- }
- p_block->statements.push_back(expression);
- if (!_end_statement()) {
- // Attempt to guess a better error message if the user "retypes" a variable
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
- _set_error("Unexpected ':=', use '=' instead. Expected end of statement after expression.");
- } else {
- _set_error(vformat("Expected end of statement after expression, got %s instead.", tokenizer->get_token_name(tokenizer->get_token())));
- }
- return;
+ if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ // Arguments.
+ push_completion_call(annotation);
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true);
+ if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
+ int argument_index = 0;
+ do {
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true);
+ set_last_completion_call_arg(argument_index++);
+ ExpressionNode *argument = parse_expression(false);
+ if (argument == nullptr) {
+ valid = false;
+ continue;
}
+ annotation->arguments.push_back(argument);
+ } while (match(GDScriptTokenizer::Token::COMMA));
- } break;
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*");
}
+ pop_completion_call();
}
-}
-
-bool GDScriptParser::_parse_newline() {
- if (tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) {
- IndentLevel current_level = indent_level.back()->get();
- int indent = tokenizer->get_token_line_indent();
- int tabs = tokenizer->get_token_line_tab_indent();
- IndentLevel new_level(indent, tabs);
-
- if (new_level.is_mixed(current_level)) {
- _set_error("Mixed tabs and spaces in indentation.");
- return false;
- }
-
- if (indent > current_level.indent) {
- _set_error("Unexpected indentation.");
- return false;
- }
-
- if (indent < current_level.indent) {
- while (indent < current_level.indent) {
- //exit block
- if (indent_level.size() == 1) {
- _set_error("Invalid indentation. Bug?");
- return false;
- }
- indent_level.pop_back();
+ match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
- if (indent_level.back()->get().indent < indent) {
- _set_error("Unindent does not match any outer indentation level.");
- return false;
- }
-
- if (indent_level.back()->get().is_mixed(current_level)) {
- _set_error("Mixed tabs and spaces in indentation.");
- return false;
- }
-
- current_level = indent_level.back()->get();
- }
-
- tokenizer->advance();
- return false;
- }
+ if (valid) {
+ valid = validate_annotation_arguments(annotation);
}
- tokenizer->advance();
- return true;
+ return valid ? annotation : nullptr;
}
-void GDScriptParser::_parse_extends(ClassNode *p_class) {
- if (p_class->extends_used) {
- _set_error("\"extends\" can only be present once per script.");
- return;
- }
-
- if (!p_class->constant_expressions.empty() || !p_class->subclasses.empty() || !p_class->functions.empty() || !p_class->variables.empty()) {
- _set_error("\"extends\" must be used before anything else.");
- return;
+void GDScriptParser::clear_unused_annotations() {
+ for (const List<AnnotationNode *>::Element *E = annotation_stack.front(); E != nullptr; E = E->next()) {
+ AnnotationNode *annotation = E->get();
+ push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation);
}
- p_class->extends_used = true;
+ annotation_stack.clear();
+}
- tokenizer->advance();
+bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, int p_optional_arguments, bool p_is_vararg) {
+ ERR_FAIL_COND_V_MSG(valid_annotations.has(p_info.name), false, vformat(R"(Annotation "%s" already registered.)", p_info.name));
- if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token_type() == Variant::OBJECT) {
- p_class->extends_class.push_back(Variant::get_type_name(Variant::OBJECT));
- tokenizer->advance();
- return;
+ AnnotationInfo new_annotation;
+ new_annotation.info = p_info;
+ new_annotation.info.default_arguments.resize(p_optional_arguments);
+ if (p_is_vararg) {
+ new_annotation.info.flags |= METHOD_FLAG_VARARG;
}
+ new_annotation.apply = p_apply;
+ new_annotation.target_kind = p_target_kinds;
- // see if inheritance happens from a file
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) {
- Variant constant = tokenizer->get_token_constant();
- if (constant.get_type() != Variant::STRING) {
- _set_error("\"extends\" constant must be a string.");
- return;
- }
+ valid_annotations[p_info.name] = new_annotation;
+ return true;
+}
- p_class->extends_file = constant;
- tokenizer->advance();
+GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite) {
+ SuiteNode *suite = p_suite != nullptr ? p_suite : alloc_node<SuiteNode>();
+ suite->parent_block = current_suite;
+ current_suite = suite;
- // Add parent script as a dependency
- String parent = constant;
- if (parent.is_rel_path()) {
- parent = base_path.plus_file(parent).simplify_path();
- }
- dependencies.push_back(parent);
+ bool multiline = false;
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PERIOD) {
- return;
- } else {
- tokenizer->advance();
- }
+ if (check(GDScriptTokenizer::Token::NEWLINE)) {
+ multiline = true;
}
- while (true) {
- switch (tokenizer->get_token()) {
- case GDScriptTokenizer::TK_IDENTIFIER: {
- StringName identifier = tokenizer->get_token_identifier();
- p_class->extends_class.push_back(identifier);
- } break;
-
- case GDScriptTokenizer::TK_PERIOD:
- break;
-
- default: {
- _set_error("Invalid \"extends\" syntax, expected string constant (path) and/or identifier (parent class).");
- return;
- }
- }
-
- tokenizer->advance(1);
-
- switch (tokenizer->get_token()) {
- case GDScriptTokenizer::TK_IDENTIFIER:
- case GDScriptTokenizer::TK_PERIOD:
- continue;
+ if (multiline) {
+ consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after %s.)", p_context));
- default:
- return;
+ if (!consume(GDScriptTokenizer::Token::INDENT, vformat(R"(Expected indented block after %s.)", p_context))) {
+ current_suite = suite->parent_block;
+ return suite;
}
}
-}
-
-void GDScriptParser::_parse_class(ClassNode *p_class) {
- IndentLevel current_level = indent_level.back()->get();
- while (true) {
- GDScriptTokenizer::Token token = tokenizer->get_token();
- if (error_set) {
- return;
- }
-
- if (current_level.indent > indent_level.back()->get().indent) {
- p_class->end_line = tokenizer->get_token_line();
- return; //go back a level
+ do {
+ Node *statement = parse_statement();
+ if (statement == nullptr) {
+ continue;
}
+ suite->statements.push_back(statement);
- switch (token) {
- case GDScriptTokenizer::TK_CURSOR: {
- tokenizer->advance();
- } break;
- case GDScriptTokenizer::TK_EOF: {
- p_class->end_line = tokenizer->get_token_line();
- return; // End of file!
- } break;
- case GDScriptTokenizer::TK_ERROR: {
- return; // Go back.
- } break;
- case GDScriptTokenizer::TK_NEWLINE: {
- if (!_parse_newline()) {
- if (!error_set) {
- p_class->end_line = tokenizer->get_token_line();
- }
- return;
- }
- } break;
- case GDScriptTokenizer::TK_PR_EXTENDS: {
- _mark_line_as_safe(tokenizer->get_token_line());
- _parse_extends(p_class);
- if (error_set) {
- return;
- }
- if (!_end_statement()) {
- _set_end_statement_error("extends");
- return;
- }
-
- } break;
- case GDScriptTokenizer::TK_PR_CLASS_NAME: {
- _mark_line_as_safe(tokenizer->get_token_line());
- if (p_class->owner) {
- _set_error("\"class_name\" is only valid for the main class namespace.");
- return;
- }
- if (self_path.begins_with("res://") && self_path.find("::") != -1) {
- _set_error("\"class_name\" isn't allowed in built-in scripts.");
- return;
- }
- if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) {
- _set_error("\"class_name\" syntax: \"class_name <UniqueName>\"");
- return;
- }
- if (p_class->classname_used) {
- _set_error("\"class_name\" can only be present once per script.");
- return;
- }
-
- p_class->classname_used = true;
-
- p_class->name = tokenizer->get_token_identifier(1);
-
- if (self_path != String() && ScriptServer::is_global_class(p_class->name) && ScriptServer::get_global_class_path(p_class->name) != self_path) {
- _set_error("Unique global class \"" + p_class->name + "\" already exists at path: " + ScriptServer::get_global_class_path(p_class->name));
- return;
- }
-
- if (ClassDB::class_exists(p_class->name)) {
- _set_error("The class \"" + p_class->name + "\" shadows a native class.");
- return;
- }
-
- if (p_class->classname_used && ProjectSettings::get_singleton()->has_setting("autoload/" + p_class->name)) {
- const String autoload_path = ProjectSettings::get_singleton()->get_setting("autoload/" + p_class->name);
- if (autoload_path.begins_with("*")) {
- // It's a singleton, and not just a regular AutoLoad script.
- _set_error("The class \"" + p_class->name + "\" conflicts with the AutoLoad singleton of the same name, and is therefore redundant. Remove the class_name declaration to fix this error.");
- }
- return;
+ // Register locals.
+ switch (statement->type) {
+ case Node::VARIABLE: {
+ VariableNode *variable = static_cast<VariableNode *>(statement);
+ const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name);
+ if (local.type != SuiteNode::Local::UNDEFINED) {
+ push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name));
}
-
- tokenizer->advance(2);
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- tokenizer->advance();
-
- if ((tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING)) {
-#ifdef TOOLS_ENABLED
- if (Engine::get_singleton()->is_editor_hint()) {
- Variant constant = tokenizer->get_token_constant();
- String icon_path = constant.operator String();
-
- String abs_icon_path = icon_path.is_rel_path() ? self_path.get_base_dir().plus_file(icon_path).simplify_path() : icon_path;
- if (!FileAccess::exists(abs_icon_path)) {
- _set_error("No class icon found at: " + abs_icon_path);
- return;
- }
-
- p_class->icon_path = icon_path;
- }
-#endif
-
- tokenizer->advance();
+ current_suite->add_local(variable);
+ break;
+ }
+ case Node::CONSTANT: {
+ ConstantNode *constant = static_cast<ConstantNode *>(statement);
+ const SuiteNode::Local &local = current_suite->get_local(constant->identifier->name);
+ if (local.type != SuiteNode::Local::UNDEFINED) {
+ String name;
+ if (local.type == SuiteNode::Local::CONSTANT) {
+ name = "constant";
} else {
- _set_error("The optional parameter after \"class_name\" must be a string constant file path to an icon.");
- return;
+ name = "variable";
}
-
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) {
- _set_error("The class icon must be separated by a comma.");
- return;
- }
-
- } break;
- case GDScriptTokenizer::TK_PR_TOOL: {
- if (p_class->tool) {
- _set_error("The \"tool\" keyword can only be present once per script.");
- return;
- }
-
- p_class->tool = true;
- tokenizer->advance();
-
- } break;
- case GDScriptTokenizer::TK_PR_CLASS: {
- //class inside class :D
-
- StringName name;
-
- if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) {
- _set_error("\"class\" syntax: \"class <Name>:\" or \"class <Name> extends <BaseClass>:\"");
- return;
- }
- name = tokenizer->get_token_identifier(1);
- tokenizer->advance(2);
-
- // Check if name is shadowing something else
- if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) {
- _set_error("The class \"" + String(name) + "\" shadows a native class.");
- return;
- }
- if (ScriptServer::is_global_class(name)) {
- _set_error("Can't override name of the unique global class \"" + name + "\". It already exists at: " + ScriptServer::get_global_class_path(p_class->name));
- return;
+ push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name));
}
- ClassNode *outer_class = p_class;
- while (outer_class) {
- for (int i = 0; i < outer_class->subclasses.size(); i++) {
- if (outer_class->subclasses[i]->name == name) {
- _set_error("Another class named \"" + String(name) + "\" already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ").");
- return;
- }
- }
- if (outer_class->constant_expressions.has(name)) {
- _set_error("A constant named \"" + String(name) + "\" already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ").");
- return;
- }
- for (int i = 0; i < outer_class->variables.size(); i++) {
- if (outer_class->variables[i].identifier == name) {
- _set_error("A variable named \"" + String(name) + "\" already exists in the outer class scope (at line " + itos(outer_class->variables[i].line) + ").");
- return;
- }
- }
-
- outer_class = outer_class->owner;
- }
-
- ClassNode *newclass = alloc_node<ClassNode>();
- newclass->initializer = alloc_node<BlockNode>();
- newclass->initializer->parent_class = newclass;
- newclass->ready = alloc_node<BlockNode>();
- newclass->ready->parent_class = newclass;
- newclass->name = name;
- newclass->owner = p_class;
-
- p_class->subclasses.push_back(newclass);
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_EXTENDS) {
- _parse_extends(newclass);
- if (error_set) {
- return;
- }
- }
-
- if (!_enter_indent_block()) {
- _set_error("Indented block expected.");
- return;
- }
- current_class = newclass;
- _parse_class(newclass);
- current_class = p_class;
-
- } break;
- /* this is for functions....
- case GDScriptTokenizer::TK_CF_PASS: {
-
- tokenizer->advance(1);
- } break;
- */
- case GDScriptTokenizer::TK_PR_STATIC: {
- tokenizer->advance();
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
- _set_error("Expected \"func\".");
- return;
- }
-
- [[fallthrough]];
+ current_suite->add_local(constant);
+ break;
}
- case GDScriptTokenizer::TK_PR_FUNCTION: {
- bool _static = false;
- pending_newline = -1;
-
- if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_STATIC) {
- _static = true;
- }
-
- tokenizer->advance();
- StringName name;
+ default:
+ break;
+ }
- if (_get_completable_identifier(COMPLETION_VIRTUAL_FUNC, name)) {
- }
+ } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !is_at_end());
- if (name == StringName()) {
- _set_error("Expected an identifier after \"func\" (syntax: \"func <identifier>([arguments]):\").");
- return;
- }
+ if (multiline) {
+ consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context));
+ }
- for (int i = 0; i < p_class->functions.size(); i++) {
- if (p_class->functions[i]->name == name) {
- _set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->functions[i]->line) + ").");
- }
- }
- for (int i = 0; i < p_class->static_functions.size(); i++) {
- if (p_class->static_functions[i]->name == name) {
- _set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->static_functions[i]->line) + ").");
- }
- }
+ current_suite = suite->parent_block;
+ return suite;
+}
+GDScriptParser::Node *GDScriptParser::parse_statement() {
+ Node *result = nullptr;
#ifdef DEBUG_ENABLED
- if (p_class->constant_expressions.has(name)) {
- _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name);
- }
- for (int i = 0; i < p_class->variables.size(); i++) {
- if (p_class->variables[i].identifier == name) {
- _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_VARIABLE, -1, name);
- }
- }
- for (int i = 0; i < p_class->subclasses.size(); i++) {
- if (p_class->subclasses[i]->name == name) {
- _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name);
- }
- }
-#endif // DEBUG_ENABLED
+ bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code;
+#endif
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- _set_error("Expected \"(\" after the identifier (syntax: \"func <identifier>([arguments]):\" ).");
- return;
+ switch (current.type) {
+ case GDScriptTokenizer::Token::PASS:
+ advance();
+ result = alloc_node<PassNode>();
+ end_statement(R"("pass")");
+ break;
+ case GDScriptTokenizer::Token::VAR:
+ advance();
+ result = parse_variable();
+ break;
+ case GDScriptTokenizer::Token::CONST:
+ advance();
+ result = parse_constant();
+ break;
+ case GDScriptTokenizer::Token::IF:
+ advance();
+ result = parse_if();
+ break;
+ case GDScriptTokenizer::Token::FOR:
+ advance();
+ result = parse_for();
+ break;
+ case GDScriptTokenizer::Token::WHILE:
+ advance();
+ result = parse_while();
+ break;
+ case GDScriptTokenizer::Token::MATCH:
+ advance();
+ result = parse_match();
+ break;
+ case GDScriptTokenizer::Token::BREAK:
+ advance();
+ result = parse_break();
+ break;
+ case GDScriptTokenizer::Token::CONTINUE:
+ advance();
+ result = parse_continue();
+ break;
+ case GDScriptTokenizer::Token::RETURN: {
+ advance();
+ ReturnNode *n_return = alloc_node<ReturnNode>();
+ if (!is_statement_end()) {
+ if (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
+ push_error(R"(Constructor cannot return a value.)");
}
+ n_return->return_value = parse_expression(false);
+ }
+ result = n_return;
- tokenizer->advance();
-
- Vector<StringName> arguments;
- Vector<DataType> argument_types;
- Vector<Node *> default_values;
-#ifdef DEBUG_ENABLED
- Vector<int> arguments_usage;
-#endif // DEBUG_ENABLED
-
- int fnline = tokenizer->get_token_line();
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- //has arguments
- bool defaulting = false;
- while (true) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
- tokenizer->advance();
- continue;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_VAR) {
- tokenizer->advance(); //var before the identifier is allowed
- }
+ current_suite->has_return = true;
- if (!tokenizer->is_token_literal(0, true)) {
- _set_error("Expected an identifier for an argument.");
- return;
- }
+ end_statement("return statement");
+ break;
+ }
+ case GDScriptTokenizer::Token::BREAKPOINT:
+ advance();
+ result = alloc_node<BreakpointNode>();
+ end_statement(R"("breakpoint")");
+ break;
+ case GDScriptTokenizer::Token::ASSERT:
+ advance();
+ result = parse_assert();
+ break;
+ case GDScriptTokenizer::Token::ANNOTATION: {
+ advance();
+ AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT);
+ if (annotation != nullptr) {
+ annotation_stack.push_back(annotation);
+ }
+ break;
+ }
+ default: {
+ // Expression statement.
+ ExpressionNode *expression = parse_expression(true); // Allow assignment here.
+ if (expression == nullptr) {
+ push_error(vformat(R"(Expected statement, found "%s" instead.)", previous.get_name()));
+ }
+ end_statement("expression");
+ result = expression;
- StringName argname = tokenizer->get_token_identifier();
- for (int i = 0; i < arguments.size(); i++) {
- if (arguments[i] == argname) {
- _set_error("The argument name \"" + String(argname) + "\" is defined multiple times.");
- return;
- }
- }
- arguments.push_back(argname);
#ifdef DEBUG_ENABLED
- arguments_usage.push_back(0);
-#endif // DEBUG_ENABLED
-
- tokenizer->advance();
-
- DataType argtype;
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
- if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
- argtype.infer_type = true;
- tokenizer->advance();
- } else if (!_parse_type(argtype)) {
- _set_error("Expected a type for an argument.");
- return;
- }
- }
- argument_types.push_back(argtype);
-
- if (defaulting && tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) {
- _set_error("Default parameter expected.");
- return;
- }
-
- //tokenizer->advance();
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
- defaulting = true;
- tokenizer->advance(1);
- Node *defval = _parse_and_reduce_expression(p_class, _static);
- if (!defval || error_set) {
- return;
- }
-
- OperatorNode *on = alloc_node<OperatorNode>();
- on->op = OperatorNode::OP_ASSIGN;
- on->line = fnline;
-
- IdentifierNode *in = alloc_node<IdentifierNode>();
- in->name = argname;
- in->line = fnline;
-
- on->arguments.push_back(in);
- on->arguments.push_back(defval);
- /* no ..
- if (defval->type!=Node::TYPE_CONSTANT) {
-
- _set_error("default argument must be constant");
- }
- */
- default_values.push_back(on);
- }
-
- while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
- tokenizer->advance();
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- tokenizer->advance();
- continue;
- } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \",\" or \")\".");
- return;
- }
-
+ if (expression != nullptr) {
+ switch (expression->type) {
+ case Node::CALL:
+ case Node::ASSIGNMENT:
+ case Node::AWAIT:
+ // Fine.
break;
- }
+ default:
+ push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);
}
+ }
+#endif
+ break;
+ }
+ }
- tokenizer->advance();
-
- BlockNode *block = alloc_node<BlockNode>();
- block->parent_class = p_class;
-
- FunctionNode *function = alloc_node<FunctionNode>();
- function->name = name;
- function->arguments = arguments;
- function->argument_types = argument_types;
- function->default_values = default_values;
- function->_static = _static;
- function->line = fnline;
#ifdef DEBUG_ENABLED
- function->arguments_usage = arguments_usage;
-#endif // DEBUG_ENABLED
- function->rpc_mode = rpc_mode;
- rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
-
- if (name == "_init") {
- if (_static) {
- _set_error("The constructor cannot be static.");
- return;
- }
-
- if (p_class->extends_used) {
- OperatorNode *cparent = alloc_node<OperatorNode>();
- cparent->op = OperatorNode::OP_PARENT_CALL;
- block->statements.push_back(cparent);
-
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = "_init";
- cparent->arguments.push_back(id);
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) {
- tokenizer->advance();
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- _set_error("Expected \"(\" for parent constructor arguments.");
- return;
- }
- tokenizer->advance();
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- //has arguments
- parenthesis++;
- while (true) {
- current_function = function;
- Node *arg = _parse_and_reduce_expression(p_class, _static);
- if (!arg) {
- return;
- }
- current_function = nullptr;
- cparent->arguments.push_back(arg);
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- tokenizer->advance();
- continue;
- } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \",\" or \")\".");
- return;
- }
-
- break;
- }
- parenthesis--;
- }
-
- tokenizer->advance();
- }
- } else {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) {
- _set_error("Parent constructor call found for a class without inheritance.");
- return;
- }
- }
- }
-
- DataType return_type;
- if (tokenizer->get_token() == GDScriptTokenizer::TK_FORWARD_ARROW) {
- if (!_parse_type(return_type, true)) {
- _set_error("Expected a return type for the function.");
- return;
- }
- }
-
- if (!_enter_indent_block(block)) {
- _set_error(vformat("Indented block expected after declaration of \"%s\" function.", function->name));
- return;
- }
-
- function->return_type = return_type;
-
- if (_static) {
- p_class->static_functions.push_back(function);
- } else {
- p_class->functions.push_back(function);
- }
-
- current_function = function;
- function->body = block;
- current_block = block;
- _parse_block(block, _static);
- current_block = nullptr;
-
- //arguments
- } break;
- case GDScriptTokenizer::TK_PR_SIGNAL: {
- tokenizer->advance();
-
- if (!tokenizer->is_token_literal()) {
- _set_error("Expected an identifier after \"signal\".");
- return;
- }
-
- ClassNode::Signal sig;
- sig.name = tokenizer->get_token_identifier();
- sig.emissions = 0;
- sig.line = tokenizer->get_token_line();
-
- for (int i = 0; i < current_class->_signals.size(); i++) {
- if (current_class->_signals[i].name == sig.name) {
- _set_error("The signal \"" + sig.name + "\" already exists in this class (at line: " + itos(current_class->_signals[i].line) + ").");
- return;
- }
- }
-
- tokenizer->advance();
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- tokenizer->advance();
- while (true) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
- tokenizer->advance();
- continue;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- tokenizer->advance();
- break;
- }
-
- if (!tokenizer->is_token_literal(0, true)) {
- _set_error("Expected an identifier in a \"signal\" argument.");
- return;
- }
+ if (unreachable) {
+ current_suite->has_unreachable_code = true;
+ push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name);
+ }
+#endif
- sig.arguments.push_back(tokenizer->get_token_identifier());
- tokenizer->advance();
+ if (panic_mode) {
+ synchronize();
+ }
- while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
- tokenizer->advance();
- }
+ return result;
+}
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- tokenizer->advance();
- } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \",\" or \")\" after a \"signal\" parameter identifier.");
- return;
- }
- }
- }
+GDScriptParser::AssertNode *GDScriptParser::parse_assert() {
+ // TODO: Add assert message.
+ AssertNode *assert = alloc_node<AssertNode>();
- p_class->_signals.push_back(sig);
+ consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "assert".)");
+ assert->condition = parse_expression(false);
+ if (assert->condition == nullptr) {
+ push_error("Expected expression to assert.");
+ return nullptr;
+ }
- if (!_end_statement()) {
- _set_end_statement_error("signal");
- return;
- }
- } break;
- case GDScriptTokenizer::TK_PR_EXPORT: {
- tokenizer->advance();
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
-#define _ADVANCE_AND_CONSUME_NEWLINES \
- do { \
- tokenizer->advance(); \
- } while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE)
-
- _ADVANCE_AND_CONSUME_NEWLINES;
- parenthesis++;
-
- String hint_prefix = "";
- bool is_arrayed = false;
-
- while (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE &&
- tokenizer->get_token_type() == Variant::ARRAY &&
- tokenizer->get_token(1) == GDScriptTokenizer::TK_COMMA) {
- tokenizer->advance(); // Array
- tokenizer->advance(); // Comma
- if (is_arrayed) {
- hint_prefix += itos(Variant::ARRAY) + ":";
- } else {
- is_arrayed = true;
- }
- }
+ if (match(GDScriptTokenizer::Token::COMMA)) {
+ // Error message.
+ if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected error message for assert after ",".)")) {
+ assert->message = parse_literal();
+ if (assert->message->value.get_type() != Variant::STRING) {
+ push_error(R"(Expected string for assert error message.)");
+ }
+ } else {
+ return nullptr;
+ }
+ }
- if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) {
- Variant::Type type = tokenizer->get_token_type();
- if (type == Variant::NIL) {
- _set_error("Can't export null type.");
- return;
- }
- if (type == Variant::OBJECT) {
- _set_error("Can't export raw object type.");
- return;
- }
- current_export.type = type;
- current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- // hint expected next!
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- switch (type) {
- case Variant::INT: {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") {
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- _set_error("Expected \",\" in the bit flags hint.");
- return;
- }
-
- current_export.hint = PROPERTY_HINT_FLAGS;
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- bool first = true;
- while (true) {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
- current_export = PropertyInfo();
- _set_error("Expected a string constant in the named bit flags hint.");
- return;
- }
-
- String c = tokenizer->get_token_constant();
- if (!first) {
- current_export.hint_string += ",";
- } else {
- first = false;
- }
-
- current_export.hint_string += c.xml_escape();
-
- _ADVANCE_AND_CONSUME_NEWLINES;
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- break;
- }
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- current_export = PropertyInfo();
- _set_error("Expected \")\" or \",\" in the named bit flags hint.");
- return;
- }
- _ADVANCE_AND_CONSUME_NEWLINES;
- }
-
- break;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_RENDER") {
- _ADVANCE_AND_CONSUME_NEWLINES;
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \")\" in the layers 2D render hint.");
- return;
- }
- current_export.hint = PROPERTY_HINT_LAYERS_2D_RENDER;
- break;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_PHYSICS") {
- _ADVANCE_AND_CONSUME_NEWLINES;
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \")\" in the layers 2D physics hint.");
- return;
- }
- current_export.hint = PROPERTY_HINT_LAYERS_2D_PHYSICS;
- break;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_RENDER") {
- _ADVANCE_AND_CONSUME_NEWLINES;
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \")\" in the layers 3D render hint.");
- return;
- }
- current_export.hint = PROPERTY_HINT_LAYERS_3D_RENDER;
- break;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_PHYSICS") {
- _ADVANCE_AND_CONSUME_NEWLINES;
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \")\" in the layers 3D physics hint.");
- return;
- }
- current_export.hint = PROPERTY_HINT_LAYERS_3D_PHYSICS;
- break;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) {
- //enumeration
- current_export.hint = PROPERTY_HINT_ENUM;
- bool first = true;
- while (true) {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
- current_export = PropertyInfo();
- _set_error("Expected a string constant in the enumeration hint.");
- return;
- }
-
- String c = tokenizer->get_token_constant();
- if (!first) {
- current_export.hint_string += ",";
- } else {
- first = false;
- }
-
- current_export.hint_string += c.xml_escape();
-
- _ADVANCE_AND_CONSUME_NEWLINES;
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- break;
- }
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- current_export = PropertyInfo();
- _set_error("Expected \")\" or \",\" in the enumeration hint.");
- return;
- }
-
- _ADVANCE_AND_CONSUME_NEWLINES;
- }
-
- break;
- }
-
- [[fallthrough]];
- }
- case Variant::FLOAT: {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EASE") {
- current_export.hint = PROPERTY_HINT_EXP_EASING;
- _ADVANCE_AND_CONSUME_NEWLINES;
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \")\" in the hint.");
- return;
- }
- break;
- }
-
- // range
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EXP") {
- current_export.hint = PROPERTY_HINT_EXP_RANGE;
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- break;
- } else if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- _set_error("Expected \")\" or \",\" in the exponential range hint.");
- return;
- }
- _ADVANCE_AND_CONSUME_NEWLINES;
- } else {
- current_export.hint = PROPERTY_HINT_RANGE;
- }
-
- float sign = 1.0;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) {
- sign = -1;
- _ADVANCE_AND_CONSUME_NEWLINES;
- }
- if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
- current_export = PropertyInfo();
- _set_error("Expected a range in the numeric hint.");
- return;
- }
-
- current_export.hint_string = rtos(sign * double(tokenizer->get_token_constant()));
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- current_export.hint_string = "0," + current_export.hint_string;
- break;
- }
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- current_export = PropertyInfo();
- _set_error("Expected \",\" or \")\" in the numeric range hint.");
- return;
- }
-
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- sign = 1.0;
- if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) {
- sign = -1;
- _ADVANCE_AND_CONSUME_NEWLINES;
- }
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
- current_export = PropertyInfo();
- _set_error("Expected a number as upper bound in the numeric range hint.");
- return;
- }
-
- current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant()));
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- break;
- }
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- current_export = PropertyInfo();
- _set_error("Expected \",\" or \")\" in the numeric range hint.");
- return;
- }
-
- _ADVANCE_AND_CONSUME_NEWLINES;
- sign = 1.0;
- if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) {
- sign = -1;
- _ADVANCE_AND_CONSUME_NEWLINES;
- }
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
- current_export = PropertyInfo();
- _set_error("Expected a number as step in the numeric range hint.");
- return;
- }
-
- current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant()));
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- } break;
- case Variant::STRING: {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) {
- //enumeration
- current_export.hint = PROPERTY_HINT_ENUM;
- bool first = true;
- while (true) {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
- current_export = PropertyInfo();
- _set_error("Expected a string constant in the enumeration hint.");
- return;
- }
-
- String c = tokenizer->get_token_constant();
- if (!first) {
- current_export.hint_string += ",";
- } else {
- first = false;
- }
-
- current_export.hint_string += c.xml_escape();
- _ADVANCE_AND_CONSUME_NEWLINES;
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- break;
- }
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- current_export = PropertyInfo();
- _set_error("Expected \")\" or \",\" in the enumeration hint.");
- return;
- }
- _ADVANCE_AND_CONSUME_NEWLINES;
- }
-
- break;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "DIR") {
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- current_export.hint = PROPERTY_HINT_DIR;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER || !(tokenizer->get_token_identifier() == "GLOBAL")) {
- _set_error("Expected \"GLOBAL\" after comma in the directory hint.");
- return;
- }
- if (!p_class->tool) {
- _set_error("Global filesystem hints may only be used in tool scripts.");
- return;
- }
- current_export.hint = PROPERTY_HINT_GLOBAL_DIR;
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \")\" in the hint.");
- return;
- }
- } else {
- _set_error("Expected \")\" or \",\" in the hint.");
- return;
- }
- break;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FILE") {
- current_export.hint = PROPERTY_HINT_FILE;
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "GLOBAL") {
- if (!p_class->tool) {
- _set_error("Global filesystem hints may only be used in tool scripts.");
- return;
- }
- current_export.hint = PROPERTY_HINT_GLOBAL_FILE;
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- break;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- _ADVANCE_AND_CONSUME_NEWLINES;
- } else {
- _set_error("Expected \")\" or \",\" in the hint.");
- return;
- }
- }
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
- if (current_export.hint == PROPERTY_HINT_GLOBAL_FILE) {
- _set_error("Expected string constant with filter.");
- } else {
- _set_error("Expected \"GLOBAL\" or string constant with filter.");
- }
- return;
- }
- current_export.hint_string = tokenizer->get_token_constant();
- _ADVANCE_AND_CONSUME_NEWLINES;
- }
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \")\" in the hint.");
- return;
- }
- break;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "MULTILINE") {
- current_export.hint = PROPERTY_HINT_MULTILINE_TEXT;
- _ADVANCE_AND_CONSUME_NEWLINES;
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected \")\" in the hint.");
- return;
- }
- break;
- }
- } break;
- case Variant::COLOR: {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER) {
- current_export = PropertyInfo();
- _set_error("Color type hint expects RGB or RGBA as hints.");
- return;
- }
-
- String identifier = tokenizer->get_token_identifier();
- if (identifier == "RGB") {
- current_export.hint = PROPERTY_HINT_COLOR_NO_ALPHA;
- } else if (identifier == "RGBA") {
- //none
- } else {
- current_export = PropertyInfo();
- _set_error("Color type hint expects RGB or RGBA as hints.");
- return;
- }
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- } break;
- default: {
- current_export = PropertyInfo();
- _set_error("Type \"" + Variant::get_type_name(type) + "\" can't take hints.");
- return;
- } break;
- }
- }
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after assert expression.)*");
- } else {
- parenthesis++;
- Node *subexpr = _parse_and_reduce_expression(p_class, true, true);
- if (!subexpr) {
- if (_recover_from_completion()) {
- break;
- }
- return;
- }
- parenthesis--;
+ end_statement(R"("assert")");
- if (subexpr->type != Node::TYPE_CONSTANT) {
- current_export = PropertyInfo();
- _set_error("Expected a constant expression.");
- }
+ return assert;
+}
- Variant constant = static_cast<ConstantNode *>(subexpr)->value;
+GDScriptParser::BreakNode *GDScriptParser::parse_break() {
+ if (!can_break) {
+ push_error(R"(Cannot use "break" outside of a loop.)");
+ }
+ end_statement(R"("break")");
+ return alloc_node<BreakNode>();
+}
- if (constant.get_type() == Variant::OBJECT) {
- GDScriptNativeClass *native_class = Object::cast_to<GDScriptNativeClass>(constant);
+GDScriptParser::ContinueNode *GDScriptParser::parse_continue() {
+ if (!can_continue) {
+ push_error(R"(Cannot use "continue" outside of a loop or pattern matching block.)");
+ }
+ current_suite->has_continue = true;
+ end_statement(R"("continue")");
+ return alloc_node<ContinueNode>();
+}
- if (native_class && ClassDB::is_parent_class(native_class->get_name(), "Resource")) {
- current_export.type = Variant::OBJECT;
- current_export.hint = PROPERTY_HINT_RESOURCE_TYPE;
- current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
+GDScriptParser::ForNode *GDScriptParser::parse_for() {
+ ForNode *n_for = alloc_node<ForNode>();
- current_export.hint_string = native_class->get_name();
- current_export.class_name = native_class->get_name();
+ if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected loop variable name after "for".)")) {
+ n_for->variable = parse_identifier();
+ }
- } else {
- current_export = PropertyInfo();
- _set_error("The export hint isn't a resource type.");
- }
- } else if (constant.get_type() == Variant::DICTIONARY) {
- // Enumeration
- bool is_flags = false;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- _ADVANCE_AND_CONSUME_NEWLINES;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") {
- is_flags = true;
- _ADVANCE_AND_CONSUME_NEWLINES;
- } else {
- current_export = PropertyInfo();
- _set_error("Expected \"FLAGS\" after comma.");
- }
- }
+ consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable name.)");
- current_export.type = Variant::INT;
- current_export.hint = is_flags ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM;
- current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
- Dictionary enum_values = constant;
-
- List<Variant> keys;
- enum_values.get_key_list(&keys);
-
- bool first = true;
- for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
- if (enum_values[E->get()].get_type() == Variant::INT) {
- if (!first) {
- current_export.hint_string += ",";
- } else {
- first = false;
- }
-
- current_export.hint_string += E->get().operator String().camelcase_to_underscore(true).capitalize().xml_escape();
- if (!is_flags) {
- current_export.hint_string += ":";
- current_export.hint_string += enum_values[E->get()].operator String().xml_escape();
- }
- }
- }
- } else {
- current_export = PropertyInfo();
- _set_error("Expected type for export.");
- return;
- }
- }
+ n_for->list = parse_expression(false);
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- current_export = PropertyInfo();
- _set_error("Expected \")\" or \",\" after the export hint.");
- return;
- }
+ consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)");
- tokenizer->advance();
- parenthesis--;
+ // Save break/continue state.
+ bool could_break = can_break;
+ bool could_continue = can_continue;
- if (is_arrayed) {
- hint_prefix += itos(current_export.type);
- if (current_export.hint) {
- hint_prefix += "/" + itos(current_export.hint);
- }
- current_export.hint_string = hint_prefix + ":" + current_export.hint_string;
- current_export.hint = PROPERTY_HINT_TYPE_STRING;
- current_export.type = Variant::ARRAY;
- }
-#undef _ADVANCE_AND_CONSUME_NEWLINES
- }
+ // Allow break/continue.
+ can_break = true;
+ can_continue = true;
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_ONREADY && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTER && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPET && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTESYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTERSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPETSYNC) {
- current_export = PropertyInfo();
- _set_error("Expected \"var\", \"onready\", \"remote\", \"master\", \"puppet\", \"remotesync\", \"mastersync\", \"puppetsync\".");
- return;
- }
+ SuiteNode *suite = alloc_node<SuiteNode>();
+ if (n_for->variable) {
+ suite->add_local(SuiteNode::Local(n_for->variable));
+ }
+ suite->parent_for = n_for;
- continue;
- } break;
- case GDScriptTokenizer::TK_PR_ONREADY: {
- //may be fallthrough from export, ignore if so
- tokenizer->advance();
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
- _set_error("Expected \"var\".");
- return;
- }
+ n_for->loop = parse_suite(R"("for" block)", suite);
- continue;
- } break;
- case GDScriptTokenizer::TK_PR_REMOTE: {
- //may be fallthrough from export, ignore if so
- tokenizer->advance();
- if (current_export.type) {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
- _set_error("Expected \"var\".");
- return;
- }
+ // Reset break/continue state.
+ can_break = could_break;
+ can_continue = could_continue;
- } else {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
- _set_error("Expected \"var\" or \"func\".");
- return;
- }
- }
- rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE;
-
- continue;
- } break;
- case GDScriptTokenizer::TK_PR_MASTER: {
- //may be fallthrough from export, ignore if so
- tokenizer->advance();
- if (current_export.type) {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
- _set_error("Expected \"var\".");
- return;
- }
+ return n_for;
+}
- } else {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
- _set_error("Expected \"var\" or \"func\".");
- return;
- }
- }
+GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
+ IfNode *n_if = alloc_node<IfNode>();
- rpc_mode = MultiplayerAPI::RPC_MODE_MASTER;
- continue;
- } break;
- case GDScriptTokenizer::TK_PR_PUPPET: {
- //may be fallthrough from export, ignore if so
- tokenizer->advance();
- if (current_export.type) {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
- _set_error("Expected \"var\".");
- return;
- }
+ n_if->condition = parse_expression(false);
+ if (n_if->condition == nullptr) {
+ push_error(vformat(R"(Expected conditional expression after "%s".)", p_token));
+ }
- } else {
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
- _set_error("Expected \"var\" or \"func\".");
- return;
- }
- }
+ consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after "%s" condition.)", p_token));
- rpc_mode = MultiplayerAPI::RPC_MODE_PUPPET;
- continue;
- } break;
- case GDScriptTokenizer::TK_PR_REMOTESYNC: {
- //may be fallthrough from export, ignore if so
- tokenizer->advance();
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
- if (current_export.type) {
- _set_error("Expected \"var\".");
- } else {
- _set_error("Expected \"var\" or \"func\".");
- }
- return;
- }
+ n_if->true_block = parse_suite(vformat(R"("%s" block)", p_token));
+ n_if->true_block->parent_if = n_if;
- rpc_mode = MultiplayerAPI::RPC_MODE_REMOTESYNC;
- continue;
- } break;
- case GDScriptTokenizer::TK_PR_MASTERSYNC: {
- //may be fallthrough from export, ignore if so
- tokenizer->advance();
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
- if (current_export.type) {
- _set_error("Expected \"var\".");
- } else {
- _set_error("Expected \"var\" or \"func\".");
- }
- return;
- }
-
- rpc_mode = MultiplayerAPI::RPC_MODE_MASTERSYNC;
- continue;
- } break;
- case GDScriptTokenizer::TK_PR_PUPPETSYNC: {
- //may be fallthrough from export, ignore if so
- tokenizer->advance();
- if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
- if (current_export.type) {
- _set_error("Expected \"var\".");
- } else {
- _set_error("Expected \"var\" or \"func\".");
- }
- return;
- }
-
- rpc_mode = MultiplayerAPI::RPC_MODE_PUPPETSYNC;
- continue;
- } break;
- case GDScriptTokenizer::TK_PR_VAR: {
- // variable declaration and (eventual) initialization
+ if (n_if->true_block->has_continue) {
+ current_suite->has_continue = true;
+ }
- ClassNode::Member member;
+ if (match(GDScriptTokenizer::Token::ELIF)) {
+ IfNode *elif = parse_if("elif");
- bool autoexport = tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_EXPORT;
- if (current_export.type != Variant::NIL) {
- member._export = current_export;
- current_export = PropertyInfo();
- }
+ SuiteNode *else_block = alloc_node<SuiteNode>();
+ else_block->statements.push_back(elif);
+ n_if->false_block = else_block;
+ } else if (match(GDScriptTokenizer::Token::ELSE)) {
+ consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "else".)");
+ n_if->false_block = parse_suite(R"("else" block)");
+ }
- bool onready = tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_ONREADY;
+ if (n_if->false_block != nullptr && n_if->false_block->has_return && n_if->true_block->has_return) {
+ current_suite->has_return = true;
+ }
+ if (n_if->false_block != nullptr && n_if->false_block->has_continue) {
+ current_suite->has_continue = true;
+ }
- tokenizer->advance();
- if (!tokenizer->is_token_literal(0, true)) {
- _set_error("Expected an identifier for the member variable name.");
- return;
- }
+ return n_if;
+}
- member.identifier = tokenizer->get_token_literal();
- member.expression = nullptr;
- member._export.name = member.identifier;
- member.line = tokenizer->get_token_line();
- member.usages = 0;
- member.rpc_mode = rpc_mode;
-
- if (current_class->constant_expressions.has(member.identifier)) {
- _set_error("A constant named \"" + String(member.identifier) + "\" already exists in this class (at line: " +
- itos(current_class->constant_expressions[member.identifier].expression->line) + ").");
- return;
- }
+GDScriptParser::MatchNode *GDScriptParser::parse_match() {
+ MatchNode *match = alloc_node<MatchNode>();
- for (int i = 0; i < current_class->variables.size(); i++) {
- if (current_class->variables[i].identifier == member.identifier) {
- _set_error("Variable \"" + String(member.identifier) + "\" already exists in this class (at line: " +
- itos(current_class->variables[i].line) + ").");
- return;
- }
- }
+ match->test = parse_expression(false);
+ if (match->test == nullptr) {
+ push_error(R"(Expected expression to test after "match".)");
+ }
- for (int i = 0; i < current_class->subclasses.size(); i++) {
- if (current_class->subclasses[i]->name == member.identifier) {
- _set_error("A class named \"" + String(member.identifier) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
- return;
- }
- }
-#ifdef DEBUG_ENABLED
- for (int i = 0; i < current_class->functions.size(); i++) {
- if (current_class->functions[i]->name == member.identifier) {
- _add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier);
- break;
- }
- }
- for (int i = 0; i < current_class->static_functions.size(); i++) {
- if (current_class->static_functions[i]->name == member.identifier) {
- _add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier);
- break;
- }
- }
-#endif // DEBUG_ENABLED
- tokenizer->advance();
+ consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" expression.)");
+ consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected a newline after "match" statement.)");
- rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+ if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected an indented block after "match" statement.)")) {
+ return match;
+ }
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
- if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
- member.data_type = DataType();
#ifdef DEBUG_ENABLED
- member.data_type.infer_type = true;
+ bool all_have_return = true;
+ bool have_wildcard = false;
+ bool wildcard_has_return = false;
+ bool have_wildcard_without_continue = false;
#endif
- tokenizer->advance();
- } else if (!_parse_type(member.data_type)) {
- _set_error("Expected a type for the class variable.");
- return;
- }
- }
-
- if (autoexport && member.data_type.has_type) {
- if (member.data_type.kind == DataType::BUILTIN) {
- member._export.type = member.data_type.builtin_type;
- } else if (member.data_type.kind == DataType::NATIVE) {
- if (ClassDB::is_parent_class(member.data_type.native_type, "Resource")) {
- member._export.type = Variant::OBJECT;
- member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
- member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
- member._export.hint_string = member.data_type.native_type;
- member._export.class_name = member.data_type.native_type;
- } else {
- _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
- return;
- }
-
- } else {
- _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
- return;
- }
- }
-#ifdef TOOLS_ENABLED
- Callable::CallError ce;
- member.default_value = Variant::construct(member._export.type, nullptr, 0, ce);
-#endif
+ while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) {
+ MatchBranchNode *branch = parse_match_branch();
+ if (branch == nullptr) {
+ continue;
+ }
- if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
#ifdef DEBUG_ENABLED
- int line = tokenizer->get_token_line();
-#endif
- tokenizer->advance();
-
- Node *subexpr = _parse_and_reduce_expression(p_class, false, autoexport || member._export.type != Variant::NIL);
- if (!subexpr) {
- if (_recover_from_completion()) {
- break;
- }
- return;
- }
-
- //discourage common error
- if (!onready && subexpr->type == Node::TYPE_OPERATOR) {
- OperatorNode *op = static_cast<OperatorNode *>(subexpr);
- if (op->op == OperatorNode::OP_CALL && op->arguments[0]->type == Node::TYPE_SELF && op->arguments[1]->type == Node::TYPE_IDENTIFIER) {
- IdentifierNode *id = static_cast<IdentifierNode *>(op->arguments[1]);
- if (id->name == "get_node") {
- _set_error("Use \"onready var " + String(member.identifier) + " = get_node(...)\" instead.");
- return;
- }
- }
- }
-
- member.expression = subexpr;
-
- if (autoexport && !member.data_type.has_type) {
- if (subexpr->type != Node::TYPE_CONSTANT) {
- _set_error("Type-less export needs a constant expression assigned to infer type.");
- return;
- }
-
- ConstantNode *cn = static_cast<ConstantNode *>(subexpr);
- if (cn->value.get_type() == Variant::NIL) {
- _set_error("Can't accept a null constant expression for inferring export type.");
- return;
- }
-
- if (!_reduce_export_var_type(cn->value, member.line)) {
- return;
- }
-
- member._export.type = cn->value.get_type();
- member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
- if (cn->value.get_type() == Variant::OBJECT) {
- Object *obj = cn->value;
- Resource *res = Object::cast_to<Resource>(obj);
- if (res == nullptr) {
- _set_error("The exported constant isn't a type or resource.");
- return;
- }
- member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
- member._export.hint_string = res->get_class();
- }
- }
-#ifdef TOOLS_ENABLED
- if (subexpr->type == Node::TYPE_CONSTANT && (member._export.type != Variant::NIL || member.data_type.has_type)) {
- ConstantNode *cn = static_cast<ConstantNode *>(subexpr);
- if (cn->value.get_type() != Variant::NIL) {
- if (member._export.type != Variant::NIL && cn->value.get_type() != member._export.type) {
- if (Variant::can_convert(cn->value.get_type(), member._export.type)) {
- Callable::CallError err;
- const Variant *args = &cn->value;
- cn->value = Variant::construct(member._export.type, &args, 1, err);
- } else {
- _set_error("Can't convert the provided value to the export type.");
- return;
- }
- }
- member.default_value = cn->value;
- }
- }
-#endif
-
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = member.identifier;
- id->datatype = member.data_type;
-
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_INIT_ASSIGN;
- op->arguments.push_back(id);
- op->arguments.push_back(subexpr);
+ if (have_wildcard_without_continue) {
+ push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN);
+ }
-#ifdef DEBUG_ENABLED
- NewLineNode *nl2 = alloc_node<NewLineNode>();
- nl2->line = line;
- if (onready) {
- p_class->ready->statements.push_back(nl2);
- } else {
- p_class->initializer->statements.push_back(nl2);
- }
+ if (branch->has_wildcard) {
+ have_wildcard = true;
+ if (branch->block->has_return) {
+ wildcard_has_return = true;
+ }
+ if (!branch->block->has_continue) {
+ have_wildcard_without_continue = true;
+ }
+ }
+ if (!branch->block->has_return) {
+ all_have_return = false;
+ }
#endif
- if (onready) {
- p_class->ready->statements.push_back(op);
- } else {
- p_class->initializer->statements.push_back(op);
- }
-
- member.initial_assignment = op;
-
- } else {
- if (autoexport && !member.data_type.has_type) {
- _set_error("Type-less export needs a constant expression assigned to infer type.");
- return;
- }
-
- Node *expr;
-
- if (member.data_type.has_type) {
- expr = _get_default_value_for_type(member.data_type);
- } else {
- DataType exported_type;
- exported_type.has_type = true;
- exported_type.kind = DataType::BUILTIN;
- exported_type.builtin_type = member._export.type;
- expr = _get_default_value_for_type(exported_type);
- }
-
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = member.identifier;
- id->datatype = member.data_type;
-
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_INIT_ASSIGN;
- op->arguments.push_back(id);
- op->arguments.push_back(expr);
-
- p_class->initializer->statements.push_back(op);
-
- member.initial_assignment = op;
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_SETGET) {
- tokenizer->advance();
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- //just comma means using only getter
- if (!tokenizer->is_token_literal()) {
- _set_error("Expected an identifier for the setter function after \"setget\".");
- }
-
- member.setter = tokenizer->get_token_literal();
-
- tokenizer->advance();
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- //there is a getter
- tokenizer->advance();
-
- if (!tokenizer->is_token_literal()) {
- _set_error("Expected an identifier for the getter function after \",\".");
- }
-
- member.getter = tokenizer->get_token_literal();
- tokenizer->advance();
- }
- }
-
- p_class->variables.push_back(member);
-
- if (!_end_statement()) {
- _set_end_statement_error("var");
- return;
- }
- } break;
- case GDScriptTokenizer::TK_PR_CONST: {
- // constant declaration and initialization
-
- ClassNode::Constant constant;
-
- tokenizer->advance();
- if (!tokenizer->is_token_literal(0, true)) {
- _set_error("Expected an identifier for the constant.");
- return;
- }
-
- StringName const_id = tokenizer->get_token_literal();
- int line = tokenizer->get_token_line();
-
- if (current_class->constant_expressions.has(const_id)) {
- _set_error("Constant \"" + String(const_id) + "\" already exists in this class (at line " +
- itos(current_class->constant_expressions[const_id].expression->line) + ").");
- return;
- }
-
- for (int i = 0; i < current_class->variables.size(); i++) {
- if (current_class->variables[i].identifier == const_id) {
- _set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " +
- itos(current_class->variables[i].line) + ").");
- return;
- }
- }
-
- for (int i = 0; i < current_class->subclasses.size(); i++) {
- if (current_class->subclasses[i]->name == const_id) {
- _set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
- return;
- }
- }
+ match->branches.push_back(branch);
+ }
- tokenizer->advance();
+ consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)");
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
- if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
- constant.type = DataType();
#ifdef DEBUG_ENABLED
- constant.type.infer_type = true;
+ if (wildcard_has_return || (all_have_return && have_wildcard)) {
+ current_suite->has_return = true;
+ }
#endif
- tokenizer->advance();
- } else if (!_parse_type(constant.type)) {
- _set_error("Expected a type for the class constant.");
- return;
- }
- }
-
- if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) {
- _set_error("Constants must be assigned immediately.");
- return;
- }
-
- tokenizer->advance();
-
- Node *subexpr = _parse_and_reduce_expression(p_class, true, true);
- if (!subexpr) {
- if (_recover_from_completion()) {
- break;
- }
- return;
- }
-
- if (subexpr->type != Node::TYPE_CONSTANT) {
- _set_error("Expected a constant expression.", line);
- return;
- }
- subexpr->line = line;
- constant.expression = subexpr;
-
- p_class->constant_expressions.insert(const_id, constant);
-
- if (!_end_statement()) {
- _set_end_statement_error("const");
- return;
- }
-
- } break;
- case GDScriptTokenizer::TK_PR_ENUM: {
- //multiple constant declarations..
-
- int last_assign = -1; // Incremented by 1 right before the assignment.
- String enum_name;
- Dictionary enum_dict;
-
- tokenizer->advance();
- if (tokenizer->is_token_literal(0, true)) {
- enum_name = tokenizer->get_token_literal();
-
- if (current_class->constant_expressions.has(enum_name)) {
- _set_error("A constant named \"" + String(enum_name) + "\" already exists in this class (at line " +
- itos(current_class->constant_expressions[enum_name].expression->line) + ").");
- return;
- }
-
- for (int i = 0; i < current_class->variables.size(); i++) {
- if (current_class->variables[i].identifier == enum_name) {
- _set_error("A variable named \"" + String(enum_name) + "\" already exists in this class (at line " +
- itos(current_class->variables[i].line) + ").");
- return;
- }
- }
-
- for (int i = 0; i < current_class->subclasses.size(); i++) {
- if (current_class->subclasses[i]->name == enum_name) {
- _set_error("A class named \"" + String(enum_name) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
- return;
- }
- }
-
- tokenizer->advance();
- }
- if (tokenizer->get_token() != GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) {
- _set_error("Expected \"{\" in the enum declaration.");
- return;
- }
- tokenizer->advance();
-
- while (true) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) {
- tokenizer->advance(); // Ignore newlines
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) {
- tokenizer->advance();
- break; // End of enum
- } else if (!tokenizer->is_token_literal(0, true)) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
- _set_error("Unexpected end of file.");
- } else {
- _set_error(String("Unexpected ") + GDScriptTokenizer::get_token_name(tokenizer->get_token()) + ", expected an identifier.");
- }
-
- return;
- } else { // tokenizer->is_token_literal(0, true)
- StringName const_id = tokenizer->get_token_literal();
-
- tokenizer->advance();
-
- ConstantNode *enum_value_expr;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
- tokenizer->advance();
- Node *subexpr = _parse_and_reduce_expression(p_class, true, true);
- if (!subexpr) {
- if (_recover_from_completion()) {
- break;
- }
- return;
- }
-
- if (subexpr->type != Node::TYPE_CONSTANT) {
- _set_error("Expected a constant expression.");
- return;
- }
-
- enum_value_expr = static_cast<ConstantNode *>(subexpr);
-
- if (enum_value_expr->value.get_type() != Variant::INT) {
- _set_error("Expected an integer value for \"enum\".");
- return;
- }
-
- last_assign = enum_value_expr->value;
-
- } else {
- last_assign = last_assign + 1;
- enum_value_expr = alloc_node<ConstantNode>();
- enum_value_expr->value = last_assign;
- enum_value_expr->datatype = _type_from_variant(enum_value_expr->value);
- }
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
- tokenizer->advance();
- } else if (tokenizer->is_token_literal(0, true)) {
- _set_error("Unexpected identifier.");
- return;
- }
-
- if (enum_name != "") {
- enum_dict[const_id] = enum_value_expr->value;
- } else {
- if (current_class->constant_expressions.has(const_id)) {
- _set_error("A constant named \"" + String(const_id) + "\" already exists in this class (at line " +
- itos(current_class->constant_expressions[const_id].expression->line) + ").");
- return;
- }
-
- for (int i = 0; i < current_class->variables.size(); i++) {
- if (current_class->variables[i].identifier == const_id) {
- _set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " +
- itos(current_class->variables[i].line) + ").");
- return;
- }
- }
-
- for (int i = 0; i < current_class->subclasses.size(); i++) {
- if (current_class->subclasses[i]->name == const_id) {
- _set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
- return;
- }
- }
-
- ClassNode::Constant constant;
- constant.type.has_type = true;
- constant.type.kind = DataType::BUILTIN;
- constant.type.builtin_type = Variant::INT;
- constant.expression = enum_value_expr;
- p_class->constant_expressions.insert(const_id, constant);
- }
- }
- }
+ return match;
+}
- if (enum_name != "") {
- ClassNode::Constant enum_constant;
- ConstantNode *cn = alloc_node<ConstantNode>();
- cn->value = enum_dict;
- cn->datatype = _type_from_variant(cn->value);
+GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
+ MatchBranchNode *branch = alloc_node<MatchBranchNode>();
- enum_constant.expression = cn;
- enum_constant.type = cn->datatype;
- p_class->constant_expressions.insert(enum_name, enum_constant);
- }
+ bool has_bind = false;
- if (!_end_statement()) {
- _set_end_statement_error("enum");
- return;
- }
+ do {
+ PatternNode *pattern = parse_match_pattern();
+ if (pattern == nullptr) {
+ continue;
+ }
+ if (pattern->pattern_type == PatternNode::PT_BIND) {
+ has_bind = true;
+ }
+ if (branch->patterns.size() > 0 && has_bind) {
+ push_error(R"(Cannot use a variable bind with multiple patterns.)");
+ }
+ if (pattern->pattern_type == PatternNode::PT_REST) {
+ push_error(R"(Rest pattern can only be used inside array and dictionary patterns.)");
+ } else if (pattern->pattern_type == PatternNode::PT_BIND || pattern->pattern_type == PatternNode::PT_WILDCARD) {
+ branch->has_wildcard = true;
+ }
+ branch->patterns.push_back(pattern);
+ } while (match(GDScriptTokenizer::Token::COMMA));
- } break;
+ if (branch->patterns.empty()) {
+ push_error(R"(No pattern found for "match" branch.)");
+ }
- case GDScriptTokenizer::TK_CONSTANT: {
- if (tokenizer->get_token_constant().get_type() == Variant::STRING) {
- tokenizer->advance();
- // Ignore
- } else {
- _set_error(String() + "Unexpected constant of type: " + Variant::get_type_name(tokenizer->get_token_constant().get_type()));
- return;
- }
- } break;
+ consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)");
- case GDScriptTokenizer::TK_CF_PASS: {
- tokenizer->advance();
- } break;
+ // Save continue state.
+ bool could_continue = can_continue;
+ // Allow continue for match.
+ can_continue = true;
- default: {
- _set_error(String() + "Unexpected token: " + tokenizer->get_token_name(tokenizer->get_token()) + ":" + tokenizer->get_token_identifier());
- return;
+ SuiteNode *suite = alloc_node<SuiteNode>();
+ if (branch->patterns.size() > 0) {
+ List<StringName> binds;
+ branch->patterns[0]->binds.get_key_list(&binds);
- } break;
+ for (List<StringName>::Element *E = binds.front(); E != nullptr; E = E->next()) {
+ SuiteNode::Local local(branch->patterns[0]->binds[E->get()]);
+ suite->add_local(local);
}
}
-}
-void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive) {
- if (p_class->base_type.has_type) {
- // Already determined
- } else if (p_class->extends_used) {
- //do inheritance
- String path = p_class->extends_file;
+ branch->block = parse_suite("match pattern block", suite);
- Ref<GDScript> script;
- StringName native;
- ClassNode *base_class = nullptr;
+ // Restore continue state.
+ can_continue = could_continue;
- if (path != "") {
- //path (and optionally subclasses)
+ return branch;
+}
- if (path.is_rel_path()) {
- String base = base_path;
+GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_root_pattern) {
+ PatternNode *pattern = alloc_node<PatternNode>();
- if (base == "" || base.is_rel_path()) {
- _set_error("Couldn't resolve relative path for the parent class: " + path, p_class->line);
- return;
- }
- path = base.plus_file(path).simplify_path();
- }
- script = ResourceLoader::load(path);
- if (script.is_null()) {
- _set_error("Couldn't load the base class: " + path, p_class->line);
- return;
+ switch (current.type) {
+ case GDScriptTokenizer::Token::LITERAL:
+ advance();
+ pattern->pattern_type = PatternNode::PT_LITERAL;
+ pattern->literal = parse_literal();
+ if (pattern->literal == nullptr) {
+ // Error happened.
+ return nullptr;
}
- if (!script->is_valid()) {
- _set_error("Script isn't fully loaded (cyclic preload?): " + path, p_class->line);
- return;
+ break;
+ case GDScriptTokenizer::Token::VAR: {
+ // Bind.
+ advance();
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected bind name after "var".)")) {
+ return nullptr;
}
+ pattern->pattern_type = PatternNode::PT_BIND;
+ pattern->bind = parse_identifier();
- if (p_class->extends_class.size()) {
- for (int i = 0; i < p_class->extends_class.size(); i++) {
- String sub = p_class->extends_class[i];
- if (script->get_subclasses().has(sub)) {
- Ref<Script> subclass = script->get_subclasses()[sub]; //avoid reference from disappearing
- script = subclass;
- } else {
- _set_error("Couldn't find the subclass: " + sub, p_class->line);
- return;
- }
+ PatternNode *root_pattern = p_root_pattern == nullptr ? pattern : p_root_pattern;
+
+ if (p_root_pattern != nullptr) {
+ if (p_root_pattern->has_bind(pattern->bind->name)) {
+ push_error(vformat(R"(Bind variable name "%s" was already used in this pattern.)", pattern->bind->name));
+ return nullptr;
}
}
- } else {
- if (p_class->extends_class.size() == 0) {
- _set_error("Parser bug: undecidable inheritance.", p_class->line);
- ERR_FAIL();
+ if (current_suite->has_local(pattern->bind->name)) {
+ push_error(vformat(R"(There's already a %s named "%s" in this scope.)", current_suite->get_local(pattern->bind->name).get_name(), pattern->bind->name));
+ return nullptr;
}
- //look around for the subclasses
-
- int extend_iter = 1;
- String base = p_class->extends_class[0];
- ClassNode *p = p_class->owner;
- Ref<GDScript> base_script;
-
- if (ScriptServer::is_global_class(base)) {
- base_script = ResourceLoader::load(ScriptServer::get_global_class_path(base));
- if (!base_script.is_valid()) {
- _set_error("The class \"" + base + "\" couldn't be fully loaded (script error or cyclic dependency).", p_class->line);
- return;
- }
- p = nullptr;
- } else {
- List<PropertyInfo> props;
- ProjectSettings::get_singleton()->get_property_list(&props);
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- String s = E->get().name;
- if (!s.begins_with("autoload/")) {
+
+ root_pattern->binds[pattern->bind->name] = pattern->bind;
+
+ } break;
+ case GDScriptTokenizer::Token::UNDERSCORE:
+ // Wildcard.
+ advance();
+ pattern->pattern_type = PatternNode::PT_WILDCARD;
+ break;
+ case GDScriptTokenizer::Token::PERIOD_PERIOD:
+ // Rest.
+ advance();
+ pattern->pattern_type = PatternNode::PT_REST;
+ break;
+ case GDScriptTokenizer::Token::BRACKET_OPEN: {
+ // Array.
+ advance();
+ pattern->pattern_type = PatternNode::PT_ARRAY;
+
+ if (!check(GDScriptTokenizer::Token::BRACKET_CLOSE)) {
+ do {
+ PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern);
+ if (sub_pattern == nullptr) {
continue;
}
- String name = s.get_slice("/", 1);
- if (name == base) {
- String singleton_path = ProjectSettings::get_singleton()->get(s);
- if (singleton_path.begins_with("*")) {
- singleton_path = singleton_path.right(1);
- }
- if (!singleton_path.begins_with("res://")) {
- singleton_path = "res://" + singleton_path;
- }
- base_script = ResourceLoader::load(singleton_path);
- if (!base_script.is_valid()) {
- _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line);
- return;
- }
- p = nullptr;
+ if (pattern->rest_used) {
+ push_error(R"(The ".." pattern must be the last element in the pattern array.)");
+ } else if (sub_pattern->pattern_type == PatternNode::PT_REST) {
+ pattern->rest_used = true;
}
- }
+ pattern->array.push_back(sub_pattern);
+ } while (match(GDScriptTokenizer::Token::COMMA));
}
-
- while (p) {
- bool found = false;
-
- for (int i = 0; i < p->subclasses.size(); i++) {
- if (p->subclasses[i]->name == base) {
- ClassNode *test = p->subclasses[i];
- while (test) {
- if (test == p_class) {
- _set_error("Cyclic inheritance.", test->line);
- return;
+ consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" to close the array pattern.)");
+ break;
+ }
+ case GDScriptTokenizer::Token::BRACE_OPEN: {
+ // Dictionary.
+ advance();
+ pattern->pattern_type = PatternNode::PT_DICTIONARY;
+
+ if (!check(GDScriptTokenizer::Token::BRACE_CLOSE) && !is_at_end()) {
+ do {
+ if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) {
+ // Rest.
+ if (pattern->rest_used) {
+ push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)");
+ } else {
+ PatternNode *sub_pattern = alloc_node<PatternNode>();
+ sub_pattern->pattern_type = PatternNode::PT_REST;
+ pattern->dictionary.push_back({ nullptr, sub_pattern });
+ pattern->rest_used = true;
+ }
+ } else {
+ ExpressionNode *key = parse_expression(false);
+ if (key == nullptr) {
+ push_error(R"(Expected expression as key for dictionary pattern.)");
+ }
+ if (match(GDScriptTokenizer::Token::COLON)) {
+ // Value pattern.
+ PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern);
+ if (sub_pattern == nullptr) {
+ continue;
}
- if (test->base_type.kind == DataType::CLASS) {
- test = test->base_type.class_type;
+ if (pattern->rest_used) {
+ push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)");
+ } else if (sub_pattern->pattern_type == PatternNode::PT_REST) {
+ push_error(R"(The ".." pattern cannot be used as a value.)");
} else {
- break;
+ pattern->dictionary.push_back({ key, sub_pattern });
}
- }
- found = true;
- if (extend_iter < p_class->extends_class.size()) {
- // Keep looking at current classes if possible
- base = p_class->extends_class[extend_iter++];
- p = p->subclasses[i];
} else {
- base_class = p->subclasses[i];
- }
- break;
- }
- }
-
- if (base_class) {
- break;
- }
- if (found) {
- continue;
- }
-
- if (p->constant_expressions.has(base)) {
- if (p->constant_expressions[base].expression->type != Node::TYPE_CONSTANT) {
- _set_error("Couldn't resolve the constant \"" + base + "\".", p_class->line);
- return;
- }
- const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[base].expression);
- base_script = cn->value;
- if (base_script.is_null()) {
- _set_error("Constant isn't a class: " + base, p_class->line);
- return;
- }
- break;
- }
-
- p = p->owner;
- }
-
- if (base_script.is_valid()) {
- String ident = base;
- Ref<GDScript> find_subclass = base_script;
-
- for (int i = extend_iter; i < p_class->extends_class.size(); i++) {
- String subclass = p_class->extends_class[i];
-
- ident += ("." + subclass);
-
- if (find_subclass->get_subclasses().has(subclass)) {
- find_subclass = find_subclass->get_subclasses()[subclass];
- } else if (find_subclass->get_constants().has(subclass)) {
- Ref<GDScript> new_base_class = find_subclass->get_constants()[subclass];
- if (new_base_class.is_null()) {
- _set_error("Constant isn't a class: " + ident, p_class->line);
- return;
+ // Key match only.
+ pattern->dictionary.push_back({ key, nullptr });
}
- find_subclass = new_base_class;
- } else {
- _set_error("Couldn't find the subclass: " + ident, p_class->line);
- return;
}
- }
-
- script = find_subclass;
-
- } else if (!base_class) {
- if (p_class->extends_class.size() > 1) {
- _set_error("Invalid inheritance (unknown class + subclasses).", p_class->line);
- return;
- }
- //if not found, try engine classes
- if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) {
- _set_error("Unknown class: \"" + base + "\"", p_class->line);
- return;
- }
-
- native = base;
+ } while (match(GDScriptTokenizer::Token::COMMA));
}
+ consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)");
+ break;
}
-
- if (base_class) {
- p_class->base_type.has_type = true;
- p_class->base_type.kind = DataType::CLASS;
- p_class->base_type.class_type = base_class;
- } else if (script.is_valid()) {
- p_class->base_type.has_type = true;
- p_class->base_type.kind = DataType::GDSCRIPT;
- p_class->base_type.script_type = script;
- p_class->base_type.native_type = script->get_instance_base_type();
- } else if (native != StringName()) {
- p_class->base_type.has_type = true;
- p_class->base_type.kind = DataType::NATIVE;
- p_class->base_type.native_type = native;
- } else {
- _set_error("Couldn't determine inheritance.", p_class->line);
- return;
- }
-
- } else {
- // without extends, implicitly extend Reference
- p_class->base_type.has_type = true;
- p_class->base_type.kind = DataType::NATIVE;
- p_class->base_type.native_type = "Reference";
- }
-
- if (p_recursive) {
- // Recursively determine subclasses
- for (int i = 0; i < p_class->subclasses.size(); i++) {
- _determine_inheritance(p_class->subclasses[i], p_recursive);
- }
- }
-}
-
-String GDScriptParser::DataType::to_string() const {
- if (!has_type) {
- return "var";
- }
- switch (kind) {
- case BUILTIN: {
- if (builtin_type == Variant::NIL) {
- return "null";
- }
- return Variant::get_type_name(builtin_type);
- } break;
- case NATIVE: {
- if (is_meta_type) {
- return "GDScriptNativeClass";
- }
- return native_type.operator String();
- } break;
-
- case GDSCRIPT: {
- Ref<GDScript> gds = script_type;
- const String &gds_class = gds->get_script_class_name();
- if (!gds_class.empty()) {
- return gds_class;
+ default: {
+ // Expression.
+ ExpressionNode *expression = parse_expression(false);
+ if (expression == nullptr) {
+ push_error(R"(Expected expression for match pattern.)");
+ } else {
+ pattern->pattern_type = PatternNode::PT_EXPRESSION;
+ pattern->expression = expression;
}
- [[fallthrough]];
+ break;
}
- case SCRIPT: {
- if (is_meta_type) {
- return script_type->get_class_name().operator String();
- }
- String name = script_type->get_name();
- if (name != String()) {
- return name;
- }
- name = script_type->get_path().get_file();
- if (name != String()) {
- return name;
- }
- return native_type.operator String();
- } break;
- case CLASS: {
- ERR_FAIL_COND_V(!class_type, String());
- if (is_meta_type) {
- return "GDScript";
- }
- if (class_type->name == StringName()) {
- return "self";
- }
- return class_type->name.operator String();
- } break;
- case UNRESOLVED: {
- } break;
}
- return "Unresolved";
+ return pattern;
}
-bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) {
- tokenizer->advance();
- r_type.has_type = true;
-
- bool finished = false;
- bool can_index = false;
- String full_name;
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
- completion_cursor = StringName();
- completion_type = COMPLETION_TYPE_HINT;
- completion_class = current_class;
- completion_function = current_function;
- completion_line = tokenizer->get_token_line();
- completion_argument = 0;
- completion_block = current_block;
- completion_found = true;
- completion_ident_is_call = p_can_be_void;
- tokenizer->advance();
- }
+bool GDScriptParser::PatternNode::has_bind(const StringName &p_name) {
+ return binds.has(p_name);
+}
- switch (tokenizer->get_token()) {
- case GDScriptTokenizer::TK_PR_VOID: {
- if (!p_can_be_void) {
- return false;
- }
- r_type.kind = DataType::BUILTIN;
- r_type.builtin_type = Variant::NIL;
- } break;
- case GDScriptTokenizer::TK_BUILT_IN_TYPE: {
- r_type.builtin_type = tokenizer->get_token_type();
- if (tokenizer->get_token_type() == Variant::OBJECT) {
- r_type.kind = DataType::NATIVE;
- r_type.native_type = "Object";
- } else {
- r_type.kind = DataType::BUILTIN;
- }
- } break;
- case GDScriptTokenizer::TK_IDENTIFIER: {
- r_type.native_type = tokenizer->get_token_identifier();
- if (ClassDB::class_exists(r_type.native_type) || ClassDB::class_exists("_" + r_type.native_type.operator String())) {
- r_type.kind = DataType::NATIVE;
- } else {
- r_type.kind = DataType::UNRESOLVED;
- can_index = true;
- full_name = r_type.native_type;
- }
- } break;
- default: {
- return false;
- }
- }
+GDScriptParser::IdentifierNode *GDScriptParser::PatternNode::get_bind(const StringName &p_name) {
+ return binds[p_name];
+}
- tokenizer->advance();
+GDScriptParser::WhileNode *GDScriptParser::parse_while() {
+ WhileNode *n_while = alloc_node<WhileNode>();
- if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
- completion_cursor = r_type.native_type;
- completion_type = COMPLETION_TYPE_HINT;
- completion_class = current_class;
- completion_function = current_function;
- completion_line = tokenizer->get_token_line();
- completion_argument = 0;
- completion_block = current_block;
- completion_found = true;
- completion_ident_is_call = p_can_be_void;
- tokenizer->advance();
+ n_while->condition = parse_expression(false);
+ if (n_while->condition == nullptr) {
+ push_error(R"(Expected conditional expression after "while".)");
}
- if (can_index) {
- while (!finished) {
- switch (tokenizer->get_token()) {
- case GDScriptTokenizer::TK_PERIOD: {
- if (!can_index) {
- _set_error("Unexpected \".\".");
- return false;
- }
- can_index = false;
- tokenizer->advance();
- } break;
- case GDScriptTokenizer::TK_IDENTIFIER: {
- if (can_index) {
- _set_error("Unexpected identifier.");
- return false;
- }
+ consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "while" condition.)");
- StringName id;
- bool has_completion = _get_completable_identifier(COMPLETION_TYPE_HINT_INDEX, id);
- if (id == StringName()) {
- id = "@temp";
- }
+ // Save break/continue state.
+ bool could_break = can_break;
+ bool could_continue = can_continue;
- full_name += "." + id.operator String();
- can_index = true;
- if (has_completion) {
- completion_cursor = full_name;
- }
- } break;
- default: {
- finished = true;
- } break;
- }
- }
+ // Allow break/continue.
+ can_break = true;
+ can_continue = true;
- if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PERIOD) {
- _set_error("Expected a subclass identifier.");
- return false;
- }
+ n_while->loop = parse_suite(R"("while" block)");
- r_type.native_type = full_name;
- }
+ // Reset break/continue state.
+ can_break = could_break;
+ can_continue = could_continue;
- return true;
+ return n_while;
}
-GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, int p_line) {
- if (!p_source.has_type) {
- return p_source;
- }
- if (p_source.kind != DataType::UNRESOLVED) {
- return p_source;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_precedence, bool p_can_assign, bool p_stop_on_assign) {
+ // Switch multiline mode on for grouping tokens.
+ // Do this early to avoid the tokenizer generating whitespace tokens.
+ switch (current.type) {
+ case GDScriptTokenizer::Token::PARENTHESIS_OPEN:
+ case GDScriptTokenizer::Token::BRACE_OPEN:
+ case GDScriptTokenizer::Token::BRACKET_OPEN:
+ push_multiline(true);
+ break;
+ default:
+ break; // Nothing to do.
}
- Vector<String> full_name = p_source.native_type.operator String().split(".", false);
- int name_part = 0;
+ // Completion can appear whenever an expression is expected.
+ make_completion_context(COMPLETION_IDENTIFIER, nullptr);
- DataType result;
- result.has_type = true;
+ GDScriptTokenizer::Token token = advance();
+ ParseFunction prefix_rule = get_rule(token.type)->prefix;
- while (name_part < full_name.size()) {
- bool found = false;
- StringName id = full_name[name_part];
- DataType base_type = result;
+ if (prefix_rule == nullptr) {
+ // Expected expression. Let the caller give the proper error message.
+ return nullptr;
+ }
- ClassNode *p = nullptr;
- if (name_part == 0) {
- if (ScriptServer::is_global_class(id)) {
- String script_path = ScriptServer::get_global_class_path(id);
- if (script_path == self_path) {
- result.kind = DataType::CLASS;
- result.class_type = static_cast<ClassNode *>(head);
- } else {
- Ref<Script> script = ResourceLoader::load(script_path);
- Ref<GDScript> gds = script;
- if (gds.is_valid()) {
- if (!gds->is_valid()) {
- _set_error("The class \"" + id + "\" couldn't be fully loaded (script error or cyclic dependency).", p_line);
- return DataType();
- }
- result.kind = DataType::GDSCRIPT;
- result.script_type = gds;
- } else if (script.is_valid()) {
- result.kind = DataType::SCRIPT;
- result.script_type = script;
- } else {
- _set_error("The class \"" + id + "\" was found in global scope, but its script couldn't be loaded.", p_line);
- return DataType();
- }
- }
- name_part++;
- continue;
- }
- List<PropertyInfo> props;
- ProjectSettings::get_singleton()->get_property_list(&props);
- String singleton_path;
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- String s = E->get().name;
- if (!s.begins_with("autoload/")) {
- continue;
- }
- String name = s.get_slice("/", 1);
- if (name == id) {
- singleton_path = ProjectSettings::get_singleton()->get(s);
- if (singleton_path.begins_with("*")) {
- singleton_path = singleton_path.right(1);
- }
- if (!singleton_path.begins_with("res://")) {
- singleton_path = "res://" + singleton_path;
- }
- break;
- }
- }
- if (!singleton_path.empty()) {
- Ref<Script> script = ResourceLoader::load(singleton_path);
- Ref<GDScript> gds = script;
- if (gds.is_valid()) {
- if (!gds->is_valid()) {
- _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line);
- return DataType();
- }
- result.kind = DataType::GDSCRIPT;
- result.script_type = gds;
- } else if (script.is_valid()) {
- result.kind = DataType::SCRIPT;
- result.script_type = script;
- } else {
- _set_error("Couldn't fully load singleton script '" + id + "' (possible cyclic reference or parse error).", p_line);
- return DataType();
- }
- name_part++;
- continue;
- }
+ ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign);
- p = current_class;
- } else if (base_type.kind == DataType::CLASS) {
- p = base_type.class_type;
+ while (p_precedence <= get_rule(current.type)->precedence) {
+ if (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) {
+ return previous_operand;
}
- while (p) {
- if (p->constant_expressions.has(id)) {
- if (p->constant_expressions[id].expression->type != Node::TYPE_CONSTANT) {
- _set_error("Parser bug: unresolved constant.", p_line);
- ERR_FAIL_V(result);
- }
- const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[id].expression);
- Ref<GDScript> gds = cn->value;
- if (gds.is_valid()) {
- result.kind = DataType::GDSCRIPT;
- result.script_type = gds;
- found = true;
- } else {
- Ref<Script> scr = cn->value;
- if (scr.is_valid()) {
- result.kind = DataType::SCRIPT;
- result.script_type = scr;
- found = true;
- }
- }
+ // Also switch multiline mode on here for infix operators.
+ switch (current.type) {
+ // case GDScriptTokenizer::Token::BRACE_OPEN: // Not an infix operator.
+ case GDScriptTokenizer::Token::PARENTHESIS_OPEN:
+ case GDScriptTokenizer::Token::BRACKET_OPEN:
+ push_multiline(true);
break;
- }
-
- // Inner classes
- ClassNode *outer_class = p;
- while (outer_class) {
- if (outer_class->name == id) {
- found = true;
- result.kind = DataType::CLASS;
- result.class_type = outer_class;
- break;
- }
- for (int i = 0; i < outer_class->subclasses.size(); i++) {
- if (outer_class->subclasses[i] == p) {
- continue;
- }
- if (outer_class->subclasses[i]->name == id) {
- found = true;
- result.kind = DataType::CLASS;
- result.class_type = outer_class->subclasses[i];
- break;
- }
- }
- if (found) {
- break;
- }
- outer_class = outer_class->owner;
- }
-
- if (!found && p->base_type.kind == DataType::CLASS) {
- p = p->base_type.class_type;
- } else {
- base_type = p->base_type;
- break;
- }
- }
-
- // Still look for class constants in parent scripts
- if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) {
- Ref<Script> scr = base_type.script_type;
- ERR_FAIL_COND_V(scr.is_null(), result);
- while (scr.is_valid()) {
- Map<StringName, Variant> constants;
- scr->get_constants(&constants);
-
- if (constants.has(id)) {
- Ref<GDScript> gds = constants[id];
-
- if (gds.is_valid()) {
- result.kind = DataType::GDSCRIPT;
- result.script_type = gds;
- found = true;
- } else {
- Ref<Script> scr2 = constants[id];
- if (scr2.is_valid()) {
- result.kind = DataType::SCRIPT;
- result.script_type = scr2;
- found = true;
- }
- }
- }
- if (found) {
- break;
- } else {
- scr = scr->get_base_script();
- }
- }
+ default:
+ break; // Nothing to do.
}
+ token = advance();
+ ParseFunction infix_rule = get_rule(token.type)->infix;
+ previous_operand = (this->*infix_rule)(previous_operand, p_can_assign);
+ }
- if (!found && !for_completion) {
- String base;
- if (name_part == 0) {
- base = "self";
- } else {
- base = result.to_string();
- }
- _set_error("The identifier \"" + String(id) + "\" isn't a valid type (not a script or class), or couldn't be found on base \"" +
- base + "\".",
- p_line);
- return DataType();
- }
+ return previous_operand;
+}
- name_part++;
- }
+GDScriptParser::ExpressionNode *GDScriptParser::parse_expression(bool p_can_assign, bool p_stop_on_assign) {
+ return parse_precedence(PREC_ASSIGNMENT, p_can_assign, p_stop_on_assign);
+}
- return result;
+GDScriptParser::IdentifierNode *GDScriptParser::parse_identifier() {
+ return static_cast<IdentifierNode *>(parse_identifier(nullptr, false));
}
-GDScriptParser::DataType GDScriptParser::_type_from_variant(const Variant &p_value) const {
- DataType result;
- result.has_type = true;
- result.is_constant = true;
- result.kind = DataType::BUILTIN;
- result.builtin_type = p_value.get_type();
+GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ if (!previous.is_identifier()) {
+ ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token.");
+ }
+ IdentifierNode *identifier = alloc_node<IdentifierNode>();
+ identifier->name = previous.get_identifier();
- if (result.builtin_type == Variant::OBJECT) {
- Object *obj = p_value.operator Object *();
- if (!obj) {
- return DataType();
- }
- result.native_type = obj->get_class_name();
- Ref<Script> scr = p_value;
- if (scr.is_valid()) {
- result.is_meta_type = true;
- } else {
- result.is_meta_type = false;
- scr = obj->get_script();
- }
- if (scr.is_valid()) {
- result.script_type = scr;
- Ref<GDScript> gds = scr;
- if (gds.is_valid()) {
- result.kind = DataType::GDSCRIPT;
- } else {
- result.kind = DataType::SCRIPT;
- }
- result.native_type = scr->get_instance_base_type();
- } else {
- result.kind = DataType::NATIVE;
+ if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
+ const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
+ switch (declaration.type) {
+ case SuiteNode::Local::CONSTANT:
+ identifier->source = IdentifierNode::LOCAL_CONSTANT;
+ identifier->constant_source = declaration.constant;
+ declaration.constant->usages++;
+ break;
+ case SuiteNode::Local::VARIABLE:
+ identifier->source = IdentifierNode::LOCAL_VARIABLE;
+ identifier->variable_source = declaration.variable;
+ declaration.variable->usages++;
+ break;
+ case SuiteNode::Local::PARAMETER:
+ identifier->source = IdentifierNode::FUNCTION_PARAMETER;
+ identifier->parameter_source = declaration.parameter;
+ declaration.parameter->usages++;
+ break;
+ case SuiteNode::Local::FOR_VARIABLE:
+ identifier->source = IdentifierNode::LOCAL_ITERATOR;
+ identifier->bind_source = declaration.bind;
+ declaration.bind->usages++;
+ break;
+ case SuiteNode::Local::PATTERN_BIND:
+ identifier->source = IdentifierNode::LOCAL_BIND;
+ identifier->bind_source = declaration.bind;
+ declaration.bind->usages++;
+ break;
+ case SuiteNode::Local::UNDEFINED:
+ ERR_FAIL_V_MSG(nullptr, "Undefined local found.");
}
}
- return result;
+ return identifier;
}
-GDScriptParser::DataType GDScriptParser::_type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant) const {
- DataType ret;
- if (p_property.type == Variant::NIL && (p_nil_is_variant || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) {
- // Variant
- return ret;
- }
- ret.has_type = true;
- ret.builtin_type = p_property.type;
- if (p_property.type == Variant::OBJECT) {
- ret.kind = DataType::NATIVE;
- ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
- } else {
- ret.kind = DataType::BUILTIN;
- }
- return ret;
+GDScriptParser::LiteralNode *GDScriptParser::parse_literal() {
+ return static_cast<LiteralNode *>(parse_literal(nullptr, false));
}
-GDScriptParser::DataType GDScriptParser::_type_from_gdtype(const GDScriptDataType &p_gdtype) const {
- DataType result;
- if (!p_gdtype.has_type) {
- return result;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ if (previous.type != GDScriptTokenizer::Token::LITERAL) {
+ push_error("Parser bug: parsing literal node without literal token.");
+ ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token.");
}
- result.has_type = true;
- result.builtin_type = p_gdtype.builtin_type;
- result.native_type = p_gdtype.native_type;
- result.script_type = p_gdtype.script_type;
-
- switch (p_gdtype.kind) {
- case GDScriptDataType::UNINITIALIZED: {
- ERR_PRINT("Uninitialized datatype. Please report a bug.");
- } break;
- case GDScriptDataType::BUILTIN: {
- result.kind = DataType::BUILTIN;
- } break;
- case GDScriptDataType::NATIVE: {
- result.kind = DataType::NATIVE;
- } break;
- case GDScriptDataType::GDSCRIPT: {
- result.kind = DataType::GDSCRIPT;
- } break;
- case GDScriptDataType::SCRIPT: {
- result.kind = DataType::SCRIPT;
- } break;
- }
- return result;
+ LiteralNode *literal = alloc_node<LiteralNode>();
+ literal->value = previous.literal;
+ return literal;
}
-GDScriptParser::DataType GDScriptParser::_get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const {
- if (!p_a.has_type || !p_b.has_type) {
- r_valid = true;
- return DataType();
+GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ if (!current_function || current_function->is_static) {
+ push_error(R"(Cannot use "self" outside a non-static function.)");
}
+ SelfNode *self = alloc_node<SelfNode>();
+ self->current_class = current_class;
+ return self;
+}
- Variant::Type a_type = p_a.kind == DataType::BUILTIN ? p_a.builtin_type : Variant::OBJECT;
- Variant::Type b_type = p_b.kind == DataType::BUILTIN ? p_b.builtin_type : Variant::OBJECT;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_builtin_constant(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ GDScriptTokenizer::Token::Type op_type = previous.type;
+ LiteralNode *constant = alloc_node<LiteralNode>();
- Variant a;
- REF a_ref;
- if (a_type == Variant::OBJECT) {
- a_ref.instance();
- a = a_ref;
- } else {
- Callable::CallError err;
- a = Variant::construct(a_type, nullptr, 0, err);
- if (err.error != Callable::CallError::CALL_OK) {
- r_valid = false;
- return DataType();
- }
- }
- Variant b;
- REF b_ref;
- if (b_type == Variant::OBJECT) {
- b_ref.instance();
- b = b_ref;
- } else {
- Callable::CallError err;
- b = Variant::construct(b_type, nullptr, 0, err);
- if (err.error != Callable::CallError::CALL_OK) {
- r_valid = false;
- return DataType();
- }
+ switch (op_type) {
+ case GDScriptTokenizer::Token::CONST_PI:
+ constant->value = Math_PI;
+ break;
+ case GDScriptTokenizer::Token::CONST_TAU:
+ constant->value = Math_TAU;
+ break;
+ case GDScriptTokenizer::Token::CONST_INF:
+ constant->value = Math_INF;
+ break;
+ case GDScriptTokenizer::Token::CONST_NAN:
+ constant->value = Math_NAN;
+ break;
+ default:
+ return nullptr; // Unreachable.
}
- // Avoid division by zero
- if (a_type == Variant::INT || a_type == Variant::FLOAT) {
- Variant::evaluate(Variant::OP_ADD, a, 1, a, r_valid);
- }
- if (b_type == Variant::INT || b_type == Variant::FLOAT) {
- Variant::evaluate(Variant::OP_ADD, b, 1, b, r_valid);
- }
- if (a_type == Variant::STRING && b_type != Variant::ARRAY) {
- a = "%s"; // Work around for formatting operator (%)
- }
+ return constant;
+}
- Variant ret;
- Variant::evaluate(p_op, a, b, ret, r_valid);
+GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ GDScriptTokenizer::Token::Type op_type = previous.type;
+ UnaryOpNode *operation = alloc_node<UnaryOpNode>();
- if (r_valid) {
- return _type_from_variant(ret);
+ switch (op_type) {
+ case GDScriptTokenizer::Token::MINUS:
+ operation->operation = UnaryOpNode::OP_NEGATIVE;
+ operation->variant_op = Variant::OP_NEGATE;
+ operation->operand = parse_precedence(PREC_SIGN, false);
+ break;
+ case GDScriptTokenizer::Token::PLUS:
+ operation->operation = UnaryOpNode::OP_POSITIVE;
+ operation->variant_op = Variant::OP_POSITIVE;
+ operation->operand = parse_precedence(PREC_SIGN, false);
+ break;
+ case GDScriptTokenizer::Token::TILDE:
+ operation->operation = UnaryOpNode::OP_COMPLEMENT;
+ operation->variant_op = Variant::OP_BIT_NEGATE;
+ operation->operand = parse_precedence(PREC_BIT_NOT, false);
+ break;
+ case GDScriptTokenizer::Token::NOT:
+ case GDScriptTokenizer::Token::BANG:
+ operation->operation = UnaryOpNode::OP_LOGIC_NOT;
+ operation->variant_op = Variant::OP_NOT;
+ operation->operand = parse_precedence(PREC_LOGIC_NOT, false);
+ break;
+ default:
+ return nullptr; // Unreachable.
}
- return DataType();
+ return operation;
}
-Variant::Operator GDScriptParser::_get_variant_operation(const OperatorNode::Operator &p_op) const {
- switch (p_op) {
- case OperatorNode::OP_NEG: {
- return Variant::OP_NEGATE;
- } break;
- case OperatorNode::OP_POS: {
- return Variant::OP_POSITIVE;
- } break;
- case OperatorNode::OP_NOT: {
- return Variant::OP_NOT;
- } break;
- case OperatorNode::OP_BIT_INVERT: {
- return Variant::OP_BIT_NEGATE;
- } break;
- case OperatorNode::OP_IN: {
- return Variant::OP_IN;
- } break;
- case OperatorNode::OP_EQUAL: {
- return Variant::OP_EQUAL;
- } break;
- case OperatorNode::OP_NOT_EQUAL: {
- return Variant::OP_NOT_EQUAL;
- } break;
- case OperatorNode::OP_LESS: {
- return Variant::OP_LESS;
- } break;
- case OperatorNode::OP_LESS_EQUAL: {
- return Variant::OP_LESS_EQUAL;
- } break;
- case OperatorNode::OP_GREATER: {
- return Variant::OP_GREATER;
- } break;
- case OperatorNode::OP_GREATER_EQUAL: {
- return Variant::OP_GREATER_EQUAL;
- } break;
- case OperatorNode::OP_AND: {
- return Variant::OP_AND;
- } break;
- case OperatorNode::OP_OR: {
- return Variant::OP_OR;
- } break;
- case OperatorNode::OP_ASSIGN_ADD:
- case OperatorNode::OP_ADD: {
- return Variant::OP_ADD;
- } break;
- case OperatorNode::OP_ASSIGN_SUB:
- case OperatorNode::OP_SUB: {
- return Variant::OP_SUBTRACT;
- } break;
- case OperatorNode::OP_ASSIGN_MUL:
- case OperatorNode::OP_MUL: {
- return Variant::OP_MULTIPLY;
- } break;
- case OperatorNode::OP_ASSIGN_DIV:
- case OperatorNode::OP_DIV: {
- return Variant::OP_DIVIDE;
- } break;
- case OperatorNode::OP_ASSIGN_MOD:
- case OperatorNode::OP_MOD: {
- return Variant::OP_MODULE;
- } break;
- case OperatorNode::OP_ASSIGN_BIT_AND:
- case OperatorNode::OP_BIT_AND: {
- return Variant::OP_BIT_AND;
- } break;
- case OperatorNode::OP_ASSIGN_BIT_OR:
- case OperatorNode::OP_BIT_OR: {
- return Variant::OP_BIT_OR;
- } break;
- case OperatorNode::OP_ASSIGN_BIT_XOR:
- case OperatorNode::OP_BIT_XOR: {
- return Variant::OP_BIT_XOR;
- } break;
- case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
- case OperatorNode::OP_SHIFT_LEFT: {
- return Variant::OP_SHIFT_LEFT;
- }
- case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
- case OperatorNode::OP_SHIFT_RIGHT: {
- return Variant::OP_SHIFT_RIGHT;
- }
- default: {
- return Variant::OP_MAX;
- } break;
- }
-}
+GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ GDScriptTokenizer::Token op = previous;
+ BinaryOpNode *operation = alloc_node<BinaryOpNode>();
-bool GDScriptParser::_is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion) const {
- // Ignore for completion
- if (!check_types || for_completion) {
- return true;
+ Precedence precedence = (Precedence)(get_rule(op.type)->precedence + 1);
+ operation->left_operand = p_previous_operand;
+ operation->right_operand = parse_precedence(precedence, false);
+
+ if (operation->right_operand == nullptr) {
+ push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name()));
}
- // Can't test if not all have type
- if (!p_container.has_type || !p_expression.has_type) {
- return true;
+
+ // TODO: Store the Variant operator here too (in the node).
+ // TODO: Also for unary, ternary, and assignment.
+ switch (op.type) {
+ case GDScriptTokenizer::Token::PLUS:
+ operation->operation = BinaryOpNode::OP_ADDITION;
+ operation->variant_op = Variant::OP_ADD;
+ break;
+ case GDScriptTokenizer::Token::MINUS:
+ operation->operation = BinaryOpNode::OP_SUBTRACTION;
+ operation->variant_op = Variant::OP_SUBTRACT;
+ break;
+ case GDScriptTokenizer::Token::STAR:
+ operation->operation = BinaryOpNode::OP_MULTIPLICATION;
+ operation->variant_op = Variant::OP_MULTIPLY;
+ break;
+ case GDScriptTokenizer::Token::SLASH:
+ operation->operation = BinaryOpNode::OP_DIVISION;
+ operation->variant_op = Variant::OP_DIVIDE;
+ break;
+ case GDScriptTokenizer::Token::PERCENT:
+ operation->operation = BinaryOpNode::OP_MODULO;
+ operation->variant_op = Variant::OP_MODULE;
+ break;
+ case GDScriptTokenizer::Token::LESS_LESS:
+ operation->operation = BinaryOpNode::OP_BIT_LEFT_SHIFT;
+ operation->variant_op = Variant::OP_SHIFT_LEFT;
+ break;
+ case GDScriptTokenizer::Token::GREATER_GREATER:
+ operation->operation = BinaryOpNode::OP_BIT_RIGHT_SHIFT;
+ operation->variant_op = Variant::OP_SHIFT_RIGHT;
+ break;
+ case GDScriptTokenizer::Token::AMPERSAND:
+ operation->operation = BinaryOpNode::OP_BIT_AND;
+ operation->variant_op = Variant::OP_BIT_AND;
+ break;
+ case GDScriptTokenizer::Token::PIPE:
+ operation->operation = BinaryOpNode::OP_BIT_OR;
+ operation->variant_op = Variant::OP_BIT_OR;
+ break;
+ case GDScriptTokenizer::Token::CARET:
+ operation->operation = BinaryOpNode::OP_BIT_XOR;
+ operation->variant_op = Variant::OP_BIT_XOR;
+ break;
+ case GDScriptTokenizer::Token::AND:
+ case GDScriptTokenizer::Token::AMPERSAND_AMPERSAND:
+ operation->operation = BinaryOpNode::OP_LOGIC_AND;
+ operation->variant_op = Variant::OP_AND;
+ break;
+ case GDScriptTokenizer::Token::OR:
+ case GDScriptTokenizer::Token::PIPE_PIPE:
+ operation->operation = BinaryOpNode::OP_LOGIC_OR;
+ operation->variant_op = Variant::OP_OR;
+ break;
+ case GDScriptTokenizer::Token::IS:
+ operation->operation = BinaryOpNode::OP_TYPE_TEST;
+ break;
+ case GDScriptTokenizer::Token::IN:
+ operation->operation = BinaryOpNode::OP_CONTENT_TEST;
+ operation->variant_op = Variant::OP_IN;
+ break;
+ case GDScriptTokenizer::Token::EQUAL_EQUAL:
+ operation->operation = BinaryOpNode::OP_COMP_EQUAL;
+ operation->variant_op = Variant::OP_EQUAL;
+ break;
+ case GDScriptTokenizer::Token::BANG_EQUAL:
+ operation->operation = BinaryOpNode::OP_COMP_NOT_EQUAL;
+ operation->variant_op = Variant::OP_NOT_EQUAL;
+ break;
+ case GDScriptTokenizer::Token::LESS:
+ operation->operation = BinaryOpNode::OP_COMP_LESS;
+ operation->variant_op = Variant::OP_LESS;
+ break;
+ case GDScriptTokenizer::Token::LESS_EQUAL:
+ operation->operation = BinaryOpNode::OP_COMP_LESS_EQUAL;
+ operation->variant_op = Variant::OP_LESS_EQUAL;
+ break;
+ case GDScriptTokenizer::Token::GREATER:
+ operation->operation = BinaryOpNode::OP_COMP_GREATER;
+ operation->variant_op = Variant::OP_GREATER;
+ break;
+ case GDScriptTokenizer::Token::GREATER_EQUAL:
+ operation->operation = BinaryOpNode::OP_COMP_GREATER_EQUAL;
+ operation->variant_op = Variant::OP_GREATER_EQUAL;
+ break;
+ default:
+ return nullptr; // Unreachable.
}
- // Should never get here unresolved
- ERR_FAIL_COND_V(p_container.kind == DataType::UNRESOLVED, false);
- ERR_FAIL_COND_V(p_expression.kind == DataType::UNRESOLVED, false);
+ return operation;
+}
- if (p_container.kind == DataType::BUILTIN && p_expression.kind == DataType::BUILTIN) {
- bool valid = p_container.builtin_type == p_expression.builtin_type;
- if (p_allow_implicit_conversion) {
- valid = valid || Variant::can_convert_strict(p_expression.builtin_type, p_container.builtin_type);
- }
- return valid;
- }
+GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ // Only one ternary operation exists, so no abstraction here.
+ TernaryOpNode *operation = alloc_node<TernaryOpNode>();
+ operation->true_expr = p_previous_operand;
- if (p_container.kind == DataType::BUILTIN && p_container.builtin_type == Variant::OBJECT) {
- // Object built-in is a special case, it's compatible with any object and with null
- if (p_expression.kind == DataType::BUILTIN) {
- return p_expression.builtin_type == Variant::NIL;
- }
- // If it's not a built-in, must be an object
- return true;
- }
+ operation->condition = parse_precedence(PREC_TERNARY, false);
- if (p_container.kind == DataType::BUILTIN || (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type != Variant::NIL)) {
- // Can't mix built-ins with objects
- return false;
+ if (operation->condition == nullptr) {
+ push_error(R"(Expected expression as ternary condition after "if".)");
}
- // From now on everything is objects, check polymorphism
- // The container must be the same class or a superclass of the expression
+ consume(GDScriptTokenizer::Token::ELSE, R"(Expected "else" after ternary operator condition.)");
- if (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type == Variant::NIL) {
- // Null can be assigned to object types
- return true;
+ operation->false_expr = parse_precedence(PREC_TERNARY, false);
+
+ return operation;
+}
+
+GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ if (!p_can_assign) {
+ push_error("Assignment is not allowed inside an expression.");
+ return parse_expression(false); // Return the following expression.
}
- StringName expr_native;
- Ref<Script> expr_script;
- ClassNode *expr_class = nullptr;
+#ifdef DEBUG_ENABLED
+ VariableNode *source_variable = nullptr;
+#endif
- switch (p_expression.kind) {
- case DataType::NATIVE: {
- if (p_container.kind != DataType::NATIVE) {
- // Non-native type can't be a superclass of a native type
- return false;
- }
- if (p_expression.is_meta_type) {
- expr_native = GDScriptNativeClass::get_class_static();
- } else {
- expr_native = p_expression.native_type;
- }
- } break;
- case DataType::SCRIPT:
- case DataType::GDSCRIPT: {
- if (p_container.kind == DataType::CLASS) {
- // This cannot be resolved without cyclic dependencies, so just bail out
- return false;
- }
- if (p_expression.is_meta_type) {
- expr_native = p_expression.script_type->get_class_name();
- } else {
- expr_script = p_expression.script_type;
- expr_native = expr_script->get_instance_base_type();
- }
- } break;
- case DataType::CLASS: {
- if (p_expression.is_meta_type) {
- expr_native = GDScript::get_class_static();
- } else {
- expr_class = p_expression.class_type;
- ClassNode *base = expr_class;
- while (base->base_type.kind == DataType::CLASS) {
- base = base->base_type.class_type;
- }
- expr_native = base->base_type.native_type;
- expr_script = base->base_type.script_type;
+ switch (p_previous_operand->type) {
+ case Node::IDENTIFIER: {
+#ifdef DEBUG_ENABLED
+ // Get source to store assignment count.
+ // Also remove one usage since assignment isn't usage.
+ IdentifierNode *id = static_cast<IdentifierNode *>(p_previous_operand);
+ switch (id->source) {
+ case IdentifierNode::LOCAL_VARIABLE:
+
+ source_variable = id->variable_source;
+ id->variable_source->usages--;
+ break;
+ case IdentifierNode::LOCAL_CONSTANT:
+ id->constant_source->usages--;
+ break;
+ case IdentifierNode::FUNCTION_PARAMETER:
+ id->parameter_source->usages--;
+ break;
+ case IdentifierNode::LOCAL_ITERATOR:
+ case IdentifierNode::LOCAL_BIND:
+ id->bind_source->usages--;
+ break;
+ default:
+ break;
}
+#endif
} break;
- case DataType::BUILTIN: // Already handled above
- case DataType::UNRESOLVED: // Not allowed, see above
+ case Node::SUBSCRIPT:
+ // Okay.
break;
+ default:
+ push_error(R"(Only identifier, attribute access, and subscription access can be used as assignment target.)");
+ return parse_expression(false); // Return the following expression.
}
- // Some classes are prefixed with `_` internally
- if (!ClassDB::class_exists(expr_native)) {
- expr_native = "_" + expr_native;
+ AssignmentNode *assignment = alloc_node<AssignmentNode>();
+ make_completion_context(COMPLETION_ASSIGN, assignment);
+#ifdef DEBUG_ENABLED
+ bool has_operator = true;
+#endif
+ switch (previous.type) {
+ case GDScriptTokenizer::Token::EQUAL:
+ assignment->operation = AssignmentNode::OP_NONE;
+#ifdef DEBUG_ENABLED
+ has_operator = false;
+#endif
+ break;
+ case GDScriptTokenizer::Token::PLUS_EQUAL:
+ assignment->operation = AssignmentNode::OP_ADDITION;
+ break;
+ case GDScriptTokenizer::Token::MINUS_EQUAL:
+ assignment->operation = AssignmentNode::OP_SUBTRACTION;
+ break;
+ case GDScriptTokenizer::Token::STAR_EQUAL:
+ assignment->operation = AssignmentNode::OP_MULTIPLICATION;
+ break;
+ case GDScriptTokenizer::Token::SLASH_EQUAL:
+ assignment->operation = AssignmentNode::OP_DIVISION;
+ break;
+ case GDScriptTokenizer::Token::PERCENT_EQUAL:
+ assignment->operation = AssignmentNode::OP_MODULO;
+ break;
+ case GDScriptTokenizer::Token::LESS_LESS_EQUAL:
+ assignment->operation = AssignmentNode::OP_BIT_SHIFT_LEFT;
+ break;
+ case GDScriptTokenizer::Token::GREATER_GREATER_EQUAL:
+ assignment->operation = AssignmentNode::OP_BIT_SHIFT_RIGHT;
+ break;
+ case GDScriptTokenizer::Token::AMPERSAND_EQUAL:
+ assignment->operation = AssignmentNode::OP_BIT_AND;
+ break;
+ case GDScriptTokenizer::Token::PIPE_EQUAL:
+ assignment->operation = AssignmentNode::OP_BIT_OR;
+ break;
+ case GDScriptTokenizer::Token::CARET_EQUAL:
+ assignment->operation = AssignmentNode::OP_BIT_XOR;
+ break;
+ default:
+ break; // Unreachable.
}
+ assignment->assignee = p_previous_operand;
+ assignment->assigned_value = parse_expression(false);
- switch (p_container.kind) {
- case DataType::NATIVE: {
- if (p_container.is_meta_type) {
- return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static());
- } else {
- StringName container_native = ClassDB::class_exists(p_container.native_type) ? p_container.native_type : StringName("_" + p_container.native_type);
- return ClassDB::is_parent_class(expr_native, container_native);
- }
- } break;
- case DataType::SCRIPT:
- case DataType::GDSCRIPT: {
- if (p_container.is_meta_type) {
- return ClassDB::is_parent_class(expr_native, GDScript::get_class_static());
- }
- if (expr_class == head && p_container.script_type->get_path() == self_path) {
- // Special case: container is self script and expression is self
- return true;
- }
- while (expr_script.is_valid()) {
- if (expr_script == p_container.script_type) {
- return true;
- }
- expr_script = expr_script->get_base_script();
- }
- return false;
- } break;
- case DataType::CLASS: {
- if (p_container.is_meta_type) {
- return ClassDB::is_parent_class(expr_native, GDScript::get_class_static());
- }
- if (p_container.class_type == head && expr_script.is_valid() && expr_script->get_path() == self_path) {
- // Special case: container is self and expression is self script
- return true;
- }
- while (expr_class) {
- if (expr_class == p_container.class_type) {
- return true;
- }
- expr_class = expr_class->base_type.class_type;
- }
- return false;
- } break;
- case DataType::BUILTIN: // Already handled above
- case DataType::UNRESOLVED: // Not allowed, see above
- break;
+#ifdef DEBUG_ENABLED
+ if (has_operator && source_variable != nullptr && source_variable->assignments == 0) {
+ push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name);
}
+#endif
- return false;
+ return assignment;
}
-GDScriptParser::Node *GDScriptParser::_get_default_value_for_type(const DataType &p_type, int p_line) {
- Node *result;
-
- if (p_type.has_type && p_type.kind == DataType::BUILTIN && p_type.builtin_type != Variant::NIL && p_type.builtin_type != Variant::OBJECT) {
- if (p_type.builtin_type == Variant::ARRAY) {
- result = alloc_node<ArrayNode>();
- } else if (p_type.builtin_type == Variant::DICTIONARY) {
- result = alloc_node<DictionaryNode>();
- } else {
- ConstantNode *c = alloc_node<ConstantNode>();
- Callable::CallError err;
- c->value = Variant::construct(p_type.builtin_type, nullptr, 0, err);
- c->datatype = _type_from_variant(c->value);
- result = c;
- }
- } else {
- ConstantNode *c = alloc_node<ConstantNode>();
- c->value = Variant();
- c->datatype = _type_from_variant(c->value);
- result = c;
- }
+GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ AwaitNode *await = alloc_node<AwaitNode>();
+ await->to_await = parse_precedence(PREC_AWAIT, false);
- result->line = p_line;
+ current_function->is_coroutine = true;
- return result;
+ return await;
}
-GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
-#ifdef DEBUG_ENABLED
- if (p_node->get_datatype().has_type && p_node->type != Node::TYPE_ARRAY && p_node->type != Node::TYPE_DICTIONARY) {
-#else
- if (p_node->get_datatype().has_type) {
-#endif
- return p_node->get_datatype();
- }
-
- DataType node_type;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_array(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ ArrayNode *array = alloc_node<ArrayNode>();
- switch (p_node->type) {
- case Node::TYPE_CONSTANT: {
- node_type = _type_from_variant(static_cast<ConstantNode *>(p_node)->value);
- } break;
- case Node::TYPE_TYPE: {
- TypeNode *tn = static_cast<TypeNode *>(p_node);
- node_type.has_type = true;
- node_type.is_meta_type = true;
- node_type.kind = DataType::BUILTIN;
- node_type.builtin_type = tn->vtype;
- } break;
- case Node::TYPE_ARRAY: {
- node_type.has_type = true;
- node_type.kind = DataType::BUILTIN;
- node_type.builtin_type = Variant::ARRAY;
-#ifdef DEBUG_ENABLED
- // Check stuff inside the array
- ArrayNode *an = static_cast<ArrayNode *>(p_node);
- for (int i = 0; i < an->elements.size(); i++) {
- _reduce_node_type(an->elements[i]);
- }
-#endif // DEBUG_ENABLED
- } break;
- case Node::TYPE_DICTIONARY: {
- node_type.has_type = true;
- node_type.kind = DataType::BUILTIN;
- node_type.builtin_type = Variant::DICTIONARY;
-#ifdef DEBUG_ENABLED
- // Check stuff inside the dictionarty
- DictionaryNode *dn = static_cast<DictionaryNode *>(p_node);
- for (int i = 0; i < dn->elements.size(); i++) {
- _reduce_node_type(dn->elements[i].key);
- _reduce_node_type(dn->elements[i].value);
- }
-#endif // DEBUG_ENABLED
- } break;
- case Node::TYPE_SELF: {
- node_type.has_type = true;
- node_type.kind = DataType::CLASS;
- node_type.class_type = current_class;
- node_type.is_constant = true;
- } break;
- case Node::TYPE_IDENTIFIER: {
- IdentifierNode *id = static_cast<IdentifierNode *>(p_node);
- if (id->declared_block) {
- node_type = id->declared_block->variables[id->name]->get_datatype();
- id->declared_block->variables[id->name]->usages += 1;
- } else if (id->name == "#match_value") {
- // It's a special id just for the match statetement, ignore
+ if (!check(GDScriptTokenizer::Token::BRACKET_CLOSE)) {
+ do {
+ if (check(GDScriptTokenizer::Token::BRACKET_CLOSE)) {
+ // Allow for trailing comma.
break;
- } else if (current_function && current_function->arguments.find(id->name) >= 0) {
- int idx = current_function->arguments.find(id->name);
- node_type = current_function->argument_types[idx];
- } else {
- node_type = _reduce_identifier_type(nullptr, id->name, id->line, false);
}
- } break;
- case Node::TYPE_CAST: {
- CastNode *cn = static_cast<CastNode *>(p_node);
-
- DataType source_type = _reduce_node_type(cn->source_node);
- cn->cast_type = _resolve_type(cn->cast_type, cn->line);
- if (source_type.has_type) {
- bool valid = false;
- if (check_types) {
- if (cn->cast_type.kind == DataType::BUILTIN && source_type.kind == DataType::BUILTIN) {
- valid = Variant::can_convert(source_type.builtin_type, cn->cast_type.builtin_type);
- }
- if (cn->cast_type.kind != DataType::BUILTIN && source_type.kind != DataType::BUILTIN) {
- valid = _is_type_compatible(cn->cast_type, source_type) || _is_type_compatible(source_type, cn->cast_type);
- }
- if (!valid) {
- _set_error("Invalid cast. Cannot convert from \"" + source_type.to_string() +
- "\" to \"" + cn->cast_type.to_string() + "\".",
- cn->line);
- return DataType();
- }
- }
+ ExpressionNode *element = parse_expression(false);
+ if (element == nullptr) {
+ push_error(R"(Expected expression as array element.)");
} else {
-#ifdef DEBUG_ENABLED
- _add_warning(GDScriptWarning::UNSAFE_CAST, cn->line, cn->cast_type.to_string());
-#endif // DEBUG_ENABLED
- _mark_line_as_unsafe(cn->line);
+ array->elements.push_back(element);
}
+ } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
+ }
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after array elements.)");
- node_type = cn->cast_type;
-
- } break;
- case Node::TYPE_OPERATOR: {
- OperatorNode *op = static_cast<OperatorNode *>(p_node);
-
- switch (op->op) {
- case OperatorNode::OP_CALL:
- case OperatorNode::OP_PARENT_CALL: {
- node_type = _reduce_function_call_type(op);
- } break;
- case OperatorNode::OP_YIELD: {
- if (op->arguments.size() == 2) {
- DataType base_type = _reduce_node_type(op->arguments[0]);
- DataType signal_type = _reduce_node_type(op->arguments[1]);
- // TODO: Check if signal exists when it's a constant
- if (base_type.has_type && base_type.kind == DataType::BUILTIN && base_type.builtin_type != Variant::NIL && base_type.builtin_type != Variant::OBJECT) {
- _set_error("The first argument of \"yield()\" must be an object.", op->line);
- return DataType();
- }
- if (signal_type.has_type && (signal_type.kind != DataType::BUILTIN || signal_type.builtin_type != Variant::STRING)) {
- _set_error("The second argument of \"yield()\" must be a string.", op->line);
- return DataType();
- }
- }
- // yield can return anything
- node_type.has_type = false;
- } break;
- case OperatorNode::OP_IS:
- case OperatorNode::OP_IS_BUILTIN: {
- if (op->arguments.size() != 2) {
- _set_error("Parser bug: binary operation without 2 arguments.", op->line);
- ERR_FAIL_V(DataType());
- }
-
- DataType value_type = _reduce_node_type(op->arguments[0]);
- DataType type_type = _reduce_node_type(op->arguments[1]);
-
- if (check_types && type_type.has_type) {
- if (!type_type.is_meta_type && (type_type.kind != DataType::NATIVE || !ClassDB::is_parent_class(type_type.native_type, "Script"))) {
- _set_error("Invalid \"is\" test: the right operand isn't a type (neither a native type nor a script).", op->line);
- return DataType();
- }
- type_type.is_meta_type = false; // Test the actual type
- if (!_is_type_compatible(type_type, value_type) && !_is_type_compatible(value_type, type_type)) {
- if (op->op == OperatorNode::OP_IS) {
- _set_error("A value of type \"" + value_type.to_string() + "\" will never be an instance of \"" + type_type.to_string() + "\".", op->line);
- } else {
- _set_error("A value of type \"" + value_type.to_string() + "\" will never be of type \"" + type_type.to_string() + "\".", op->line);
- }
- return DataType();
- }
- }
+ return array;
+}
- node_type.has_type = true;
- node_type.is_constant = true;
- node_type.is_meta_type = false;
- node_type.kind = DataType::BUILTIN;
- node_type.builtin_type = Variant::BOOL;
- } break;
- // Unary operators
- case OperatorNode::OP_NEG:
- case OperatorNode::OP_POS:
- case OperatorNode::OP_NOT:
- case OperatorNode::OP_BIT_INVERT: {
- DataType argument_type = _reduce_node_type(op->arguments[0]);
- if (!argument_type.has_type) {
- break;
- }
+GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ DictionaryNode *dictionary = alloc_node<DictionaryNode>();
- Variant::Operator var_op = _get_variant_operation(op->op);
- bool valid = false;
- node_type = _get_operation_type(var_op, argument_type, argument_type, valid);
+ bool decided_style = false;
+ if (!check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
+ do {
+ if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
+ // Allow for trailing comma.
+ break;
+ }
- if (check_types && !valid) {
- _set_error("Invalid operand type (\"" + argument_type.to_string() +
- "\") to unary operator \"" + Variant::get_operator_name(var_op) + "\".",
- op->line, op->column);
- return DataType();
- }
+ // Key.
+ ExpressionNode *key = parse_expression(false, true); // Stop on "=" so we can check for Lua table style.
- } break;
- // Binary operators
- case OperatorNode::OP_IN:
- case OperatorNode::OP_EQUAL:
- case OperatorNode::OP_NOT_EQUAL:
- case OperatorNode::OP_LESS:
- case OperatorNode::OP_LESS_EQUAL:
- case OperatorNode::OP_GREATER:
- case OperatorNode::OP_GREATER_EQUAL:
- case OperatorNode::OP_AND:
- case OperatorNode::OP_OR:
- case OperatorNode::OP_ADD:
- case OperatorNode::OP_SUB:
- case OperatorNode::OP_MUL:
- case OperatorNode::OP_DIV:
- case OperatorNode::OP_MOD:
- case OperatorNode::OP_SHIFT_LEFT:
- case OperatorNode::OP_SHIFT_RIGHT:
- case OperatorNode::OP_BIT_AND:
- case OperatorNode::OP_BIT_OR:
- case OperatorNode::OP_BIT_XOR: {
- if (op->arguments.size() != 2) {
- _set_error("Parser bug: binary operation without 2 arguments.", op->line);
- ERR_FAIL_V(DataType());
- }
+ if (key == nullptr) {
+ push_error(R"(Expected expression as dictionary key.)");
+ }
- DataType argument_a_type = _reduce_node_type(op->arguments[0]);
- DataType argument_b_type = _reduce_node_type(op->arguments[1]);
- if (!argument_a_type.has_type || !argument_b_type.has_type) {
- _mark_line_as_unsafe(op->line);
+ if (!decided_style) {
+ switch (current.type) {
+ case GDScriptTokenizer::Token::COLON:
+ dictionary->style = DictionaryNode::PYTHON_DICT;
break;
- }
-
- Variant::Operator var_op = _get_variant_operation(op->op);
- bool valid = false;
- node_type = _get_operation_type(var_op, argument_a_type, argument_b_type, valid);
-
- if (check_types && !valid) {
- _set_error("Invalid operand types (\"" + argument_a_type.to_string() + "\" and \"" +
- argument_b_type.to_string() + "\") to operator \"" + Variant::get_operator_name(var_op) + "\".",
- op->line, op->column);
- return DataType();
- }
-#ifdef DEBUG_ENABLED
- if (var_op == Variant::OP_DIVIDE && argument_a_type.kind == DataType::BUILTIN && argument_a_type.builtin_type == Variant::INT &&
- argument_b_type.kind == DataType::BUILTIN && argument_b_type.builtin_type == Variant::INT) {
- _add_warning(GDScriptWarning::INTEGER_DIVISION, op->line);
- }
-#endif // DEBUG_ENABLED
-
- } break;
- // Ternary operators
- case OperatorNode::OP_TERNARY_IF: {
- if (op->arguments.size() != 3) {
- _set_error("Parser bug: ternary operation without 3 arguments.");
- ERR_FAIL_V(DataType());
- }
+ case GDScriptTokenizer::Token::EQUAL:
+ dictionary->style = DictionaryNode::LUA_TABLE;
+ break;
+ default:
+ push_error(R"(Expected ":" or "=" after dictionary key.)");
+ break;
+ }
+ decided_style = true;
+ }
- DataType true_type = _reduce_node_type(op->arguments[1]);
- DataType false_type = _reduce_node_type(op->arguments[2]);
- // Check arguments[0] errors.
- _reduce_node_type(op->arguments[0]);
-
- // If types are equal, then the expression is of the same type
- // If they are compatible, return the broader type
- if (true_type == false_type || _is_type_compatible(true_type, false_type)) {
- node_type = true_type;
- } else if (_is_type_compatible(false_type, true_type)) {
- node_type = false_type;
- } else {
-#ifdef DEBUG_ENABLED
- _add_warning(GDScriptWarning::INCOMPATIBLE_TERNARY, op->line);
-#endif // DEBUG_ENABLED
- }
- } break;
- // Assignment should never happen within an expression
- case OperatorNode::OP_ASSIGN:
- case OperatorNode::OP_ASSIGN_ADD:
- case OperatorNode::OP_ASSIGN_SUB:
- case OperatorNode::OP_ASSIGN_MUL:
- case OperatorNode::OP_ASSIGN_DIV:
- case OperatorNode::OP_ASSIGN_MOD:
- case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
- case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
- case OperatorNode::OP_ASSIGN_BIT_AND:
- case OperatorNode::OP_ASSIGN_BIT_OR:
- case OperatorNode::OP_ASSIGN_BIT_XOR:
- case OperatorNode::OP_INIT_ASSIGN: {
- _set_error("Assignment inside an expression isn't allowed (parser bug?).", op->line);
- return DataType();
-
- } break;
- case OperatorNode::OP_INDEX_NAMED: {
- if (op->arguments.size() != 2) {
- _set_error("Parser bug: named index with invalid arguments.", op->line);
- ERR_FAIL_V(DataType());
- }
- if (op->arguments[1]->type != Node::TYPE_IDENTIFIER) {
- _set_error("Parser bug: named index without identifier argument.", op->line);
- ERR_FAIL_V(DataType());
+ switch (dictionary->style) {
+ case DictionaryNode::LUA_TABLE:
+ if (key != nullptr && key->type != Node::IDENTIFIER) {
+ push_error("Expected identifier as dictionary key.");
}
-
- DataType base_type = _reduce_node_type(op->arguments[0]);
- IdentifierNode *member_id = static_cast<IdentifierNode *>(op->arguments[1]);
-
- if (base_type.has_type) {
- if (check_types && base_type.kind == DataType::BUILTIN) {
- // Variant type, just test if it's possible
- DataType result;
- switch (base_type.builtin_type) {
- case Variant::NIL:
- case Variant::DICTIONARY: {
- result.has_type = false;
- } break;
- default: {
- Callable::CallError err;
- Variant temp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
-
- bool valid = false;
- Variant res = temp.get(member_id->name.operator String(), &valid);
-
- if (valid) {
- result = _type_from_variant(res);
- } else if (check_types) {
- _set_error("Can't get index \"" + String(member_id->name.operator String()) + "\" on base \"" +
- base_type.to_string() + "\".",
- op->line);
- return DataType();
- }
- } break;
- }
- result.is_constant = false;
- node_type = result;
+ if (!match(GDScriptTokenizer::Token::EQUAL)) {
+ if (match(GDScriptTokenizer::Token::COLON)) {
+ push_error(R"(Expected "=" after dictionary key. Mixing dictionary styles is not allowed.)");
+ advance(); // Consume wrong separator anyway.
} else {
- node_type = _reduce_identifier_type(&base_type, member_id->name, op->line, true);
-#ifdef DEBUG_ENABLED
- if (!node_type.has_type) {
- _mark_line_as_unsafe(op->line);
- _add_warning(GDScriptWarning::UNSAFE_PROPERTY_ACCESS, op->line, member_id->name.operator String(), base_type.to_string());
- }
-#endif // DEBUG_ENABLED
+ push_error(R"(Expected "=" after dictionary key.)");
}
- } else {
- _mark_line_as_unsafe(op->line);
}
- if (error_set) {
- return DataType();
- }
- } break;
- case OperatorNode::OP_INDEX: {
- if (op->arguments[1]->type == Node::TYPE_CONSTANT) {
- ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]);
- if (cn->value.get_type() == Variant::STRING) {
- // Treat this as named indexing
-
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = cn->value.operator StringName();
- id->datatype = cn->datatype;
-
- op->op = OperatorNode::OP_INDEX_NAMED;
- op->arguments.write[1] = id;
-
- return _reduce_node_type(op);
+ break;
+ case DictionaryNode::PYTHON_DICT:
+ if (!match(GDScriptTokenizer::Token::COLON)) {
+ if (match(GDScriptTokenizer::Token::EQUAL)) {
+ push_error(R"(Expected ":" after dictionary key. Mixing dictionary styles is not allowed.)");
+ advance(); // Consume wrong separator anyway.
+ } else {
+ push_error(R"(Expected ":" after dictionary key.)");
}
}
+ break;
+ }
- DataType base_type = _reduce_node_type(op->arguments[0]);
- DataType index_type = _reduce_node_type(op->arguments[1]);
-
- if (!base_type.has_type) {
- _mark_line_as_unsafe(op->line);
- break;
- }
-
- if (check_types && index_type.has_type) {
- if (base_type.kind == DataType::BUILTIN) {
- // Check if indexing is valid
- bool error = index_type.kind != DataType::BUILTIN && base_type.builtin_type != Variant::DICTIONARY;
- if (!error) {
- switch (base_type.builtin_type) {
- // Expect int or real as index
- case Variant::PACKED_BYTE_ARRAY:
- case Variant::PACKED_COLOR_ARRAY:
- case Variant::PACKED_INT32_ARRAY:
- case Variant::PACKED_INT64_ARRAY:
- case Variant::PACKED_FLOAT32_ARRAY:
- case Variant::PACKED_FLOAT64_ARRAY:
- case Variant::PACKED_STRING_ARRAY:
- case Variant::PACKED_VECTOR2_ARRAY:
- case Variant::PACKED_VECTOR3_ARRAY:
- case Variant::ARRAY:
- case Variant::STRING: {
- error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT;
- } break;
- // Expect String only
- case Variant::RECT2:
- case Variant::PLANE:
- case Variant::QUAT:
- case Variant::AABB:
- case Variant::OBJECT: {
- error = index_type.builtin_type != Variant::STRING;
- } break;
- // Expect String or number
- case Variant::VECTOR2:
- case Variant::VECTOR3:
- case Variant::TRANSFORM2D:
- case Variant::BASIS:
- case Variant::TRANSFORM: {
- error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT &&
- index_type.builtin_type != Variant::STRING;
- } break;
- // Expect String or int
- case Variant::COLOR: {
- error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING;
- } break;
- default: {
- }
- }
- }
- if (error) {
- _set_error("Invalid index type (" + index_type.to_string() + ") for base \"" + base_type.to_string() + "\".",
- op->line);
- return DataType();
- }
+ // Value.
+ ExpressionNode *value = parse_expression(false);
+ if (value == nullptr) {
+ push_error(R"(Expected expression as dictionary value.)");
+ }
- if (op->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) {
- ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]);
- // Index is a constant, just try it if possible
- switch (base_type.builtin_type) {
- // Arrays/string have variable indexing, can't test directly
- case Variant::STRING:
- case Variant::ARRAY:
- case Variant::DICTIONARY:
- case Variant::PACKED_BYTE_ARRAY:
- case Variant::PACKED_COLOR_ARRAY:
- case Variant::PACKED_INT32_ARRAY:
- case Variant::PACKED_INT64_ARRAY:
- case Variant::PACKED_FLOAT32_ARRAY:
- case Variant::PACKED_FLOAT64_ARRAY:
- case Variant::PACKED_STRING_ARRAY:
- case Variant::PACKED_VECTOR2_ARRAY:
- case Variant::PACKED_VECTOR3_ARRAY: {
- break;
- }
- default: {
- Callable::CallError err;
- Variant temp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
-
- bool valid = false;
- Variant res = temp.get(cn->value, &valid);
-
- if (valid) {
- node_type = _type_from_variant(res);
- node_type.is_constant = false;
- } else if (check_types) {
- _set_error("Can't get index \"" + String(cn->value) + "\" on base \"" +
- base_type.to_string() + "\".",
- op->line);
- return DataType();
- }
- } break;
- }
- } else {
- _mark_line_as_unsafe(op->line);
- }
- } else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) {
- _set_error("Only strings can be used as an index in the base type \"" + base_type.to_string() + "\".", op->line);
- return DataType();
- }
- }
- if (check_types && !node_type.has_type && base_type.kind == DataType::BUILTIN) {
- // Can infer indexing type for some variant types
- DataType result;
- result.has_type = true;
- result.kind = DataType::BUILTIN;
- switch (base_type.builtin_type) {
- // Can't index at all
- case Variant::NIL:
- case Variant::BOOL:
- case Variant::INT:
- case Variant::FLOAT:
- case Variant::NODE_PATH:
- case Variant::_RID: {
- _set_error("Can't index on a value of type \"" + base_type.to_string() + "\".", op->line);
- return DataType();
- } break;
- // Return int
- case Variant::PACKED_BYTE_ARRAY:
- case Variant::PACKED_INT32_ARRAY:
- case Variant::PACKED_INT64_ARRAY: {
- result.builtin_type = Variant::INT;
- } break;
- // Return real
- case Variant::PACKED_FLOAT32_ARRAY:
- case Variant::PACKED_FLOAT64_ARRAY:
- case Variant::VECTOR2:
- case Variant::VECTOR3:
- case Variant::QUAT: {
- result.builtin_type = Variant::FLOAT;
- } break;
- // Return color
- case Variant::PACKED_COLOR_ARRAY: {
- result.builtin_type = Variant::COLOR;
- } break;
- // Return string
- case Variant::PACKED_STRING_ARRAY:
- case Variant::STRING: {
- result.builtin_type = Variant::STRING;
- } break;
- // Return Vector2
- case Variant::PACKED_VECTOR2_ARRAY:
- case Variant::TRANSFORM2D:
- case Variant::RECT2: {
- result.builtin_type = Variant::VECTOR2;
- } break;
- // Return Vector3
- case Variant::PACKED_VECTOR3_ARRAY:
- case Variant::AABB:
- case Variant::BASIS: {
- result.builtin_type = Variant::VECTOR3;
- } break;
- // Depends on the index
- case Variant::TRANSFORM:
- case Variant::PLANE:
- case Variant::COLOR:
- default: {
- result.has_type = false;
- } break;
- }
- node_type = result;
- }
- } break;
- default: {
- _set_error("Parser bug: unhandled operation.", op->line);
- ERR_FAIL_V(DataType());
- }
+ if (key != nullptr && value != nullptr) {
+ dictionary->elements.push_back({ key, value });
}
- } break;
- default: {
- }
+ } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
}
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" after dictionary elements.)");
- node_type = _resolve_type(node_type, p_node->line);
- p_node->set_datatype(node_type);
- return node_type;
+ return dictionary;
}
-bool GDScriptParser::_get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const {
- r_static = false;
- r_default_arg_count = 0;
-
- DataType original_type = p_base_type;
- ClassNode *base = nullptr;
- FunctionNode *callee = nullptr;
+GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ ExpressionNode *grouped = parse_expression(false);
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after grouping expression.)*");
+ return grouped;
+}
- if (p_base_type.kind == DataType::CLASS) {
- base = p_base_type.class_type;
- }
+GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ SubscriptNode *attribute = alloc_node<SubscriptNode>();
- // Look up the current file (parse tree)
- while (!callee && base) {
- for (int i = 0; i < base->static_functions.size(); i++) {
- FunctionNode *func = base->static_functions[i];
- if (p_function == func->name) {
- r_static = true;
- callee = func;
- break;
- }
- }
- if (!callee && !p_base_type.is_meta_type) {
- for (int i = 0; i < base->functions.size(); i++) {
- FunctionNode *func = base->functions[i];
- if (p_function == func->name) {
- callee = func;
- break;
- }
+ if (for_completion) {
+ bool is_builtin = false;
+ if (p_previous_operand->type == Node::IDENTIFIER) {
+ const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand);
+ Variant::Type builtin_type = get_builtin_type(id->name);
+ if (builtin_type < Variant::VARIANT_MAX) {
+ make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT, builtin_type, true);
+ is_builtin = true;
}
}
- p_base_type = base->base_type;
- if (p_base_type.kind == DataType::CLASS) {
- base = p_base_type.class_type;
- } else {
- break;
+ if (!is_builtin) {
+ make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true);
}
}
- if (callee) {
- r_return_type = callee->get_datatype();
- for (int i = 0; i < callee->argument_types.size(); i++) {
- r_arg_types.push_back(callee->argument_types[i]);
- }
- r_default_arg_count = callee->default_values.size();
- return true;
- }
+ attribute->is_attribute = true;
+ attribute->base = p_previous_operand;
- // Nothing in current file, check parent script
- Ref<GDScript> base_gdscript;
- Ref<Script> base_script;
- StringName native;
- if (p_base_type.kind == DataType::GDSCRIPT) {
- base_gdscript = p_base_type.script_type;
- if (base_gdscript.is_null() || !base_gdscript->is_valid()) {
- // GDScript wasn't properly compíled, don't bother trying
- return false;
- }
- } else if (p_base_type.kind == DataType::SCRIPT) {
- base_script = p_base_type.script_type;
- } else if (p_base_type.kind == DataType::NATIVE) {
- native = p_base_type.native_type;
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) {
+ return attribute;
}
+ attribute->attribute = parse_identifier();
- while (base_gdscript.is_valid()) {
- native = base_gdscript->get_instance_base_type();
+ return attribute;
+}
- Map<StringName, GDScriptFunction *> funcs = base_gdscript->get_member_functions();
+GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ SubscriptNode *subscript = alloc_node<SubscriptNode>();
- if (funcs.has(p_function)) {
- GDScriptFunction *f = funcs[p_function];
- r_static = f->is_static();
- r_default_arg_count = f->get_default_argument_count();
- r_return_type = _type_from_gdtype(f->get_return_type());
- for (int i = 0; i < f->get_argument_count(); i++) {
- r_arg_types.push_back(_type_from_gdtype(f->get_argument_type(i)));
- }
- return true;
- }
+ make_completion_context(COMPLETION_SUBSCRIPT, subscript);
- base_gdscript = base_gdscript->get_base_script();
- }
+ subscript->base = p_previous_operand;
+ subscript->index = parse_expression(false);
- while (base_script.is_valid()) {
- native = base_script->get_instance_base_type();
- MethodInfo mi = base_script->get_method_info(p_function);
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)");
- if (!(mi == MethodInfo())) {
- r_return_type = _type_from_property(mi.return_val, false);
- r_default_arg_count = mi.default_arguments.size();
- for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) {
- r_arg_types.push_back(_type_from_property(E->get()));
- }
- return true;
- }
- base_script = base_script->get_base_script();
- }
+ return subscript;
+}
- if (native == StringName()) {
- // Empty native class, might happen in some Script implementations
- // Just ignore it
- return false;
- }
+GDScriptParser::ExpressionNode *GDScriptParser::parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ CastNode *cast = alloc_node<CastNode>();
-#ifdef DEBUG_METHODS_ENABLED
+ cast->operand = p_previous_operand;
+ cast->cast_type = parse_type();
- // Only native remains
- if (!ClassDB::class_exists(native)) {
- native = "_" + native.operator String();
- }
- if (!ClassDB::class_exists(native)) {
- if (!check_types) {
- return false;
- }
- ERR_FAIL_V_MSG(false, "Parser bug: Class '" + String(native) + "' not found.");
+ if (cast->cast_type == nullptr) {
+ push_error(R"(Expected type specifier after "as".)");
+ return p_previous_operand;
}
- MethodBind *method = ClassDB::get_method(native, p_function);
+ return cast;
+}
- if (!method) {
- // Try virtual methods
- List<MethodInfo> virtuals;
- ClassDB::get_virtual_methods(native, &virtuals);
+GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ CallNode *call = alloc_node<CallNode>();
- for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) {
- const MethodInfo &mi = E->get();
- if (mi.name == p_function) {
- r_default_arg_count = mi.default_arguments.size();
- for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) {
- r_arg_types.push_back(_type_from_property(pi->get()));
- }
- r_return_type = _type_from_property(mi.return_val, false);
- r_vararg = mi.flags & METHOD_FLAG_VARARG;
- return true;
+ if (previous.type == GDScriptTokenizer::Token::SUPER) {
+ // Super call.
+ call->is_super = true;
+ push_multiline(true);
+ if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ // Implicit call to the parent method of the same name.
+ if (current_function == nullptr) {
+ push_error(R"(Cannot use implicit "super" call outside of a function.)");
+ pop_multiline();
+ return nullptr;
}
+ call->function_name = current_function->identifier->name;
+ } else {
+ consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)");
+ make_completion_context(COMPLETION_SUPER_METHOD, call, true);
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) {
+ pop_multiline();
+ return nullptr;
+ }
+ IdentifierNode *identifier = parse_identifier();
+ call->callee = identifier;
+ call->function_name = identifier->name;
+ consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after function name.)");
}
-
- // If the base is a script, it might be trying to access members of the Script class itself
- if (original_type.is_meta_type && !(p_function == "new") && (original_type.kind == DataType::SCRIPT || original_type.kind == DataType::GDSCRIPT)) {
- method = ClassDB::get_method(original_type.script_type->get_class_name(), p_function);
-
- if (method) {
- r_static = true;
+ } else {
+ call->callee = p_previous_operand;
+
+ if (call->callee->type == Node::IDENTIFIER) {
+ call->function_name = static_cast<IdentifierNode *>(call->callee)->name;
+ make_completion_context(COMPLETION_METHOD, call->callee);
+ } else if (call->callee->type == Node::SUBSCRIPT) {
+ SubscriptNode *attribute = static_cast<SubscriptNode *>(call->callee);
+ if (attribute->is_attribute) {
+ if (attribute->attribute) {
+ call->function_name = attribute->attribute->name;
+ }
+ make_completion_context(COMPLETION_ATTRIBUTE_METHOD, call->callee);
} else {
- // Try virtual methods of the script type
- virtuals.clear();
- ClassDB::get_virtual_methods(original_type.script_type->get_class_name(), &virtuals);
- for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) {
- const MethodInfo &mi = E->get();
- if (mi.name == p_function) {
- r_default_arg_count = mi.default_arguments.size();
- for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) {
- r_arg_types.push_back(_type_from_property(pi->get()));
- }
- r_return_type = _type_from_property(mi.return_val, false);
- r_static = true;
- r_vararg = mi.flags & METHOD_FLAG_VARARG;
- return true;
- }
- }
- return false;
+ // TODO: The analyzer can see if this is actually a Callable and give better error message.
+ push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");
}
} else {
- return false;
+ push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");
}
}
- r_default_arg_count = method->get_default_argument_count();
- r_return_type = _type_from_property(method->get_return_info(), false);
- r_vararg = method->is_vararg();
-
- for (int i = 0; i < method->get_argument_count(); i++) {
- r_arg_types.push_back(_type_from_property(method->get_argument_info(i)));
- }
- return true;
-#else
- return false;
-#endif
-}
-
-GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const OperatorNode *p_call) {
- if (p_call->arguments.size() < 1) {
- _set_error("Parser bug: function call without enough arguments.", p_call->line);
- ERR_FAIL_V(DataType());
+ if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+ // Arguments.
+ push_completion_call(call);
+ make_completion_context(COMPLETION_CALL_ARGUMENTS, call, 0, true);
+ int argument_index = 0;
+ do {
+ make_completion_context(COMPLETION_CALL_ARGUMENTS, call, argument_index++, true);
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+ // Allow for trailing comma.
+ break;
+ }
+ ExpressionNode *argument = parse_expression(false);
+ if (argument == nullptr) {
+ push_error(R"(Expected expression as the function argument.)");
+ } else {
+ call->arguments.push_back(argument);
+ }
+ } while (match(GDScriptTokenizer::Token::COMMA));
+ pop_completion_call();
}
- DataType return_type;
- List<DataType> arg_types;
- int default_args_count = 0;
- int arg_count = p_call->arguments.size();
- String callee_name;
- bool is_vararg = false;
-
- switch (p_call->arguments[0]->type) {
- case GDScriptParser::Node::TYPE_TYPE: {
- // Built-in constructor, special case
- TypeNode *tn = static_cast<TypeNode *>(p_call->arguments[0]);
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after call arguments.)*");
- Vector<DataType> par_types;
- par_types.resize(p_call->arguments.size() - 1);
- for (int i = 1; i < p_call->arguments.size(); i++) {
- par_types.write[i - 1] = _reduce_node_type(p_call->arguments[i]);
- }
-
- if (error_set) {
- return DataType();
- }
+ return call;
+}
- // Special case: check copy constructor. Those are defined implicitly in Variant.
- if (par_types.size() == 1) {
- if (!par_types[0].has_type || (par_types[0].kind == DataType::BUILTIN && par_types[0].builtin_type == tn->vtype)) {
- DataType result;
- result.has_type = true;
- result.kind = DataType::BUILTIN;
- result.builtin_type = tn->vtype;
- return result;
- }
+GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ if (match(GDScriptTokenizer::Token::LITERAL)) {
+ if (previous.literal.get_type() != Variant::STRING) {
+ push_error(R"(Expect node path as string or identifer after "$".)");
+ return nullptr;
+ }
+ GetNodeNode *get_node = alloc_node<GetNodeNode>();
+ make_completion_context(COMPLETION_GET_NODE, get_node);
+ get_node->string = parse_literal();
+ return get_node;
+ } else if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
+ GetNodeNode *get_node = alloc_node<GetNodeNode>();
+ int chain_position = 0;
+ do {
+ make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++);
+ if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) {
+ return nullptr;
}
+ IdentifierNode *identifier = parse_identifier();
+ get_node->chain.push_back(identifier);
+ } while (match(GDScriptTokenizer::Token::SLASH));
+ return get_node;
+ } else {
+ push_error(R"(Expect node path as string or identifer after "$".)");
+ return nullptr;
+ }
+}
- bool match = false;
- List<MethodInfo> constructors;
- Variant::get_constructor_list(tn->vtype, &constructors);
- PropertyInfo return_type2;
-
- for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) {
- MethodInfo &mi = E->get();
+GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ PreloadNode *preload = alloc_node<PreloadNode>();
+ preload->resolved_path = "<missing path>";
- if (p_call->arguments.size() - 1 < mi.arguments.size() - mi.default_arguments.size()) {
- continue;
- }
- if (p_call->arguments.size() - 1 > mi.arguments.size()) {
- continue;
- }
+ push_multiline(true);
+ consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "preload".)");
- bool types_match = true;
- for (int i = 0; i < par_types.size(); i++) {
- DataType arg_type;
- if (mi.arguments[i].type != Variant::NIL) {
- arg_type.has_type = true;
- arg_type.kind = mi.arguments[i].type == Variant::OBJECT ? DataType::NATIVE : DataType::BUILTIN;
- arg_type.builtin_type = mi.arguments[i].type;
- arg_type.native_type = mi.arguments[i].class_name;
- }
+ make_completion_context(COMPLETION_RESOURCE_PATH, preload);
+ push_completion_call(preload);
- if (!_is_type_compatible(arg_type, par_types[i], true)) {
- types_match = false;
- break;
- } else {
-#ifdef DEBUG_ENABLED
- if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_types[i].kind == DataType::BUILTIN && par_types[i].builtin_type == Variant::FLOAT) {
- _add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, Variant::get_type_name(tn->vtype));
- }
- if (par_types[i].may_yield && p_call->arguments[i + 1]->type == Node::TYPE_OPERATOR) {
- _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i + 1])));
- }
-#endif // DEBUG_ENABLED
- }
- }
+ preload->path = parse_expression(false);
- if (types_match) {
- match = true;
- return_type2 = mi.return_val;
- break;
+ if (preload->path == nullptr) {
+ push_error(R"(Expected resource path after "(".)");
+ } else if (preload->path->type != Node::LITERAL) {
+ push_error("Preloaded path must be a constant string.");
+ } else {
+ LiteralNode *path = static_cast<LiteralNode *>(preload->path);
+ if (path->value.get_type() != Variant::STRING) {
+ push_error("Preloaded path must be a constant string.");
+ } else {
+ preload->resolved_path = path->value;
+ // TODO: Save this as script dependency.
+ if (preload->resolved_path.is_rel_path()) {
+ preload->resolved_path = script_path.get_base_dir().plus_file(preload->resolved_path);
+ }
+ preload->resolved_path = preload->resolved_path.simplify_path();
+ if (!FileAccess::exists(preload->resolved_path)) {
+ push_error(vformat(R"(Preload file "%s" does not exist.)", preload->resolved_path));
+ } else {
+ // TODO: Don't load if validating: use completion cache.
+ preload->resource = ResourceLoader::load(preload->resolved_path);
+ if (preload->resource.is_null()) {
+ push_error(vformat(R"(Could not preload resource file "%s".)", preload->resolved_path));
}
}
+ }
+ }
- if (match) {
- return _type_from_property(return_type2, false);
- } else if (check_types) {
- String err = "No constructor of '";
- err += Variant::get_type_name(tn->vtype);
- err += "' matches the signature '";
- err += Variant::get_type_name(tn->vtype) + "(";
- for (int i = 0; i < par_types.size(); i++) {
- if (i > 0) {
- err += ", ";
- }
- err += par_types[i].to_string();
- }
- err += ")'.";
- _set_error(err, p_call->line, p_call->column);
- return DataType();
- }
- return DataType();
- } break;
- case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: {
- BuiltInFunctionNode *func = static_cast<BuiltInFunctionNode *>(p_call->arguments[0]);
- MethodInfo mi = GDScriptFunctions::get_info(func->function);
+ pop_completion_call();
- return_type = _type_from_property(mi.return_val, false);
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after preload path.)*");
- // Check all arguments beforehand to solve warnings
- for (int i = 1; i < p_call->arguments.size(); i++) {
- _reduce_node_type(p_call->arguments[i]);
- }
+ return preload;
+}
- // Check arguments
+GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ // Just for better error messages.
+ GDScriptTokenizer::Token::Type invalid = previous.type;
- is_vararg = mi.flags & METHOD_FLAG_VARARG;
+ switch (invalid) {
+ case GDScriptTokenizer::Token::QUESTION_MARK:
+ push_error(R"(Unexpected "?" in source. If you want a ternary operator, use "truthy_value if true_condition else falsy_value".)");
+ break;
+ default:
+ return nullptr; // Unreachable.
+ }
- default_args_count = mi.default_arguments.size();
- callee_name = mi.name;
- arg_count -= 1;
+ // Return the previous expression.
+ return p_previous_operand;
+}
- // Check each argument type
- for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) {
- arg_types.push_back(_type_from_property(E->get()));
- }
- } break;
- default: {
- if (p_call->op == OperatorNode::OP_CALL && p_call->arguments.size() < 2) {
- _set_error("Parser bug: self method call without enough arguments.", p_call->line);
- ERR_FAIL_V(DataType());
+GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
+ TypeNode *type = alloc_node<TypeNode>();
+ make_completion_context(p_allow_void ? COMPLETION_TYPE_NAME_OR_VOID : COMPLETION_TYPE_NAME, type);
+ if (!match(GDScriptTokenizer::Token::IDENTIFIER)) {
+ if (match(GDScriptTokenizer::Token::VOID)) {
+ if (p_allow_void) {
+ TypeNode *void_type = alloc_node<TypeNode>();
+ return void_type;
+ } else {
+ push_error(R"("void" is only allowed for a function return type.)");
}
+ }
+ // Leave error message to the caller who knows the context.
+ return nullptr;
+ }
- int arg_id = p_call->op == OperatorNode::OP_CALL ? 1 : 0;
-
- if (p_call->arguments[arg_id]->type != Node::TYPE_IDENTIFIER) {
- _set_error("Parser bug: invalid function call argument.", p_call->line);
- ERR_FAIL_V(DataType());
- }
+ IdentifierNode *type_element = parse_identifier();
+
+ type->type_chain.push_back(type_element);
+
+ int chain_index = 1;
+ while (match(GDScriptTokenizer::Token::PERIOD)) {
+ make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++);
+ if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected inner type name after ".".)")) {
+ type_element = parse_identifier();
+ type->type_chain.push_back(type_element);
+ }
+ }
+
+ return type;
+}
+
+GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Type p_token_type) {
+ // Function table for expression parsing.
+ // clang-format destroys the alignment here, so turn off for the table.
+ /* clang-format off */
+ static ParseRule rules[] = {
+ // PREFIX INFIX PRECEDENCE (for infix)
+ { nullptr, nullptr, PREC_NONE }, // EMPTY,
+ // Basic
+ { nullptr, nullptr, PREC_NONE }, // ANNOTATION,
+ { &GDScriptParser::parse_identifier, nullptr, PREC_NONE }, // IDENTIFIER,
+ { &GDScriptParser::parse_literal, nullptr, PREC_NONE }, // LITERAL,
+ // Comparison
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // LESS,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // LESS_EQUAL,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // GREATER,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // GREATER_EQUAL,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // EQUAL_EQUAL,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // BANG_EQUAL,
+ // Logical
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AND,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // OR,
+ { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // NOT,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AMPERSAND_AMPERSAND,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // PIPE_PIPE,
+ { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // BANG,
+ // Bitwise
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_AND }, // AMPERSAND,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_OR }, // PIPE,
+ { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // TILDE,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_XOR }, // CARET,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_SHIFT }, // LESS_LESS,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_SHIFT }, // GREATER_GREATER,
+ // Math
+ { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_operator, PREC_ADDITION }, // PLUS,
+ { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_operator, PREC_SUBTRACTION }, // MINUS,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // STAR,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // SLASH,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // PERCENT,
+ // Assignment
+ { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // EQUAL,
+ { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PLUS_EQUAL,
+ { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // MINUS_EQUAL,
+ { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // STAR_EQUAL,
+ { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // SLASH_EQUAL,
+ { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PERCENT_EQUAL,
+ { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // LESS_LESS_EQUAL,
+ { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // GREATER_GREATER_EQUAL,
+ { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // AMPERSAND_EQUAL,
+ { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PIPE_EQUAL,
+ { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // CARET_EQUAL,
+ // Control flow
+ { nullptr, &GDScriptParser::parse_ternary_operator, PREC_TERNARY }, // IF,
+ { nullptr, nullptr, PREC_NONE }, // ELIF,
+ { nullptr, nullptr, PREC_NONE }, // ELSE,
+ { nullptr, nullptr, PREC_NONE }, // FOR,
+ { nullptr, nullptr, PREC_NONE }, // WHILE,
+ { nullptr, nullptr, PREC_NONE }, // BREAK,
+ { nullptr, nullptr, PREC_NONE }, // CONTINUE,
+ { nullptr, nullptr, PREC_NONE }, // PASS,
+ { nullptr, nullptr, PREC_NONE }, // RETURN,
+ { nullptr, nullptr, PREC_NONE }, // MATCH,
+ // Keywords
+ { nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS,
+ { nullptr, nullptr, PREC_NONE }, // ASSERT,
+ { &GDScriptParser::parse_await, nullptr, PREC_NONE }, // AWAIT,
+ { nullptr, nullptr, PREC_NONE }, // BREAKPOINT,
+ { nullptr, nullptr, PREC_NONE }, // CLASS,
+ { nullptr, nullptr, PREC_NONE }, // CLASS_NAME,
+ { nullptr, nullptr, PREC_NONE }, // CONST,
+ { nullptr, nullptr, PREC_NONE }, // ENUM,
+ { nullptr, nullptr, PREC_NONE }, // EXTENDS,
+ { nullptr, nullptr, PREC_NONE }, // FUNC,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN,
+ { nullptr, &GDScriptParser::parse_binary_operator, PREC_TYPE_TEST }, // IS,
+ { nullptr, nullptr, PREC_NONE }, // NAMESPACE,
+ { &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD,
+ { &GDScriptParser::parse_self, nullptr, PREC_NONE }, // SELF,
+ { nullptr, nullptr, PREC_NONE }, // SIGNAL,
+ { nullptr, nullptr, PREC_NONE }, // STATIC,
+ { &GDScriptParser::parse_call, nullptr, PREC_NONE }, // SUPER,
+ { nullptr, nullptr, PREC_NONE }, // TRAIT,
+ { nullptr, nullptr, PREC_NONE }, // VAR,
+ { nullptr, nullptr, PREC_NONE }, // VOID,
+ { nullptr, nullptr, PREC_NONE }, // YIELD,
+ // Punctuation
+ { &GDScriptParser::parse_array, &GDScriptParser::parse_subscript, PREC_SUBSCRIPT }, // BRACKET_OPEN,
+ { nullptr, nullptr, PREC_NONE }, // BRACKET_CLOSE,
+ { &GDScriptParser::parse_dictionary, nullptr, PREC_NONE }, // BRACE_OPEN,
+ { nullptr, nullptr, PREC_NONE }, // BRACE_CLOSE,
+ { &GDScriptParser::parse_grouping, &GDScriptParser::parse_call, PREC_CALL }, // PARENTHESIS_OPEN,
+ { nullptr, nullptr, PREC_NONE }, // PARENTHESIS_CLOSE,
+ { nullptr, nullptr, PREC_NONE }, // COMMA,
+ { nullptr, nullptr, PREC_NONE }, // SEMICOLON,
+ { nullptr, &GDScriptParser::parse_attribute, PREC_ATTRIBUTE }, // PERIOD,
+ { nullptr, nullptr, PREC_NONE }, // PERIOD_PERIOD,
+ { nullptr, nullptr, PREC_NONE }, // COLON,
+ { &GDScriptParser::parse_get_node, nullptr, PREC_NONE }, // DOLLAR,
+ { nullptr, nullptr, PREC_NONE }, // FORWARD_ARROW,
+ { nullptr, nullptr, PREC_NONE }, // UNDERSCORE,
+ // Whitespace
+ { nullptr, nullptr, PREC_NONE }, // NEWLINE,
+ { nullptr, nullptr, PREC_NONE }, // INDENT,
+ { nullptr, nullptr, PREC_NONE }, // DEDENT,
+ // Constants
+ { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_PI,
+ { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_TAU,
+ { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_INF,
+ { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_NAN,
+ // Error message improvement
+ { nullptr, nullptr, PREC_NONE }, // VCS_CONFLICT_MARKER,
+ { nullptr, nullptr, PREC_NONE }, // BACKTICK,
+ { nullptr, &GDScriptParser::parse_invalid_token, PREC_CAST }, // QUESTION_MARK,
+ // Special
+ { nullptr, nullptr, PREC_NONE }, // ERROR,
+ { nullptr, nullptr, PREC_NONE }, // TK_EOF,
+ };
+ /* clang-format on */
+ // Avoid desync.
+ static_assert(sizeof(rules) / sizeof(rules[0]) == GDScriptTokenizer::Token::TK_MAX, "Amount of parse rules don't match the amount of token types.");
+
+ // Let's assume this this never invalid, since nothing generates a TK_MAX.
+ return &rules[p_token_type];
+}
+
+bool GDScriptParser::SuiteNode::has_local(const StringName &p_name) const {
+ if (locals_indices.has(p_name)) {
+ return true;
+ }
+ if (parent_block != nullptr) {
+ return parent_block->has_local(p_name);
+ }
+ return false;
+}
- // Check all arguments beforehand to solve warnings
- for (int i = arg_id + 1; i < p_call->arguments.size(); i++) {
- _reduce_node_type(p_call->arguments[i]);
- }
+const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(const StringName &p_name) const {
+ if (locals_indices.has(p_name)) {
+ return locals[locals_indices[p_name]];
+ }
+ if (parent_block != nullptr) {
+ return parent_block->get_local(p_name);
+ }
+ return empty;
+}
- IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]);
- callee_name = func_id->name;
- arg_count -= 1 + arg_id;
+bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) const {
+ return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target);
+}
- DataType base_type;
- if (p_call->op == OperatorNode::OP_PARENT_CALL) {
- base_type = current_class->base_type;
- } else {
- base_type = _reduce_node_type(p_call->arguments[0]);
- }
+bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const {
+ return (info->target_kind & p_target_kinds) > 0;
+}
- if (!base_type.has_type || (base_type.kind == DataType::BUILTIN && base_type.builtin_type == Variant::NIL)) {
- _mark_line_as_unsafe(p_call->line);
- return DataType();
- }
+bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) {
+ ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
- if (base_type.kind == DataType::BUILTIN) {
- Callable::CallError err;
- Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
+ const MethodInfo &info = valid_annotations[p_annotation->name].info;
- if (check_types) {
- if (!tmp.has_method(callee_name)) {
- _set_error("The method \"" + callee_name + "\" isn't declared on base \"" + base_type.to_string() + "\".", p_call->line);
- return DataType();
- }
+ if (((info.flags & METHOD_FLAG_VARARG) == 0) && p_annotation->arguments.size() > info.arguments.size()) {
+ push_error(vformat(R"(Annotation "%s" requires at most %d arguments, but %d were given.)", p_annotation->name, info.arguments.size(), p_annotation->arguments.size()));
+ return false;
+ }
- default_args_count = Variant::get_method_default_arguments(base_type.builtin_type, callee_name).size();
- const Vector<Variant::Type> &var_arg_types = Variant::get_method_argument_types(base_type.builtin_type, callee_name);
+ if (p_annotation->arguments.size() < info.arguments.size() - info.default_arguments.size()) {
+ push_error(vformat(R"(Annotation "%s" requires at least %d arguments, but %d were given.)", p_annotation->name, info.arguments.size() - info.default_arguments.size(), p_annotation->arguments.size()));
+ return false;
+ }
- for (int i = 0; i < var_arg_types.size(); i++) {
- DataType argtype;
- if (var_arg_types[i] != Variant::NIL) {
- argtype.has_type = true;
- argtype.kind = DataType::BUILTIN;
- argtype.builtin_type = var_arg_types[i];
- }
- arg_types.push_back(argtype);
+ const List<PropertyInfo>::Element *E = info.arguments.front();
+ for (int i = 0; i < p_annotation->arguments.size(); i++) {
+ ExpressionNode *argument = p_annotation->arguments[i];
+ const PropertyInfo &parameter = E->get();
+
+ if (E->next() != nullptr) {
+ E = E->next();
+ }
+
+ switch (parameter.type) {
+ case Variant::STRING:
+ case Variant::STRING_NAME:
+ case Variant::NODE_PATH:
+ // Allow "quote-less strings", as long as they are recognized as identifiers.
+ if (argument->type == Node::IDENTIFIER) {
+ IdentifierNode *string = static_cast<IdentifierNode *>(argument);
+ Callable::CallError error;
+ Vector<Variant> args = varray(string->name);
+ const Variant *name = args.ptr();
+ p_annotation->resolved_arguments.push_back(Variant::construct(parameter.type, &(name), 1, error));
+ if (error.error != Callable::CallError::CALL_OK) {
+ push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
+ p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1);
+ return false;
}
+ break;
}
-
- bool rets = false;
- return_type.has_type = true;
- return_type.kind = DataType::BUILTIN;
- return_type.builtin_type = Variant::get_method_return_type(base_type.builtin_type, callee_name, &rets);
- // If the method returns, but it might return any type, (Variant::NIL), pretend we don't know the type.
- // At least make sure we know that it returns
- if (rets && return_type.builtin_type == Variant::NIL) {
- return_type.has_type = false;
+ [[fallthrough]];
+ default: {
+ if (argument->type != Node::LITERAL) {
+ push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
+ return false;
}
- break;
- }
-
- DataType original_type = base_type;
- bool is_initializer = callee_name == "new";
- bool is_static = false;
- bool valid = false;
-
- if (is_initializer && original_type.is_meta_type) {
- // Try to check it as initializer
- base_type = original_type;
- callee_name = "_init";
- base_type.is_meta_type = false;
-
- valid = _get_function_signature(base_type, callee_name, return_type, arg_types,
- default_args_count, is_static, is_vararg);
- return_type = original_type;
- return_type.is_meta_type = false;
-
- valid = true; // There's always an initializer, we can assume this is true
- }
-
- if (!valid) {
- base_type = original_type;
- return_type = DataType();
- valid = _get_function_signature(base_type, callee_name, return_type, arg_types,
- default_args_count, is_static, is_vararg);
- }
-
- if (!valid) {
-#ifdef DEBUG_ENABLED
- if (p_call->arguments[0]->type == Node::TYPE_SELF) {
- _set_error("The method \"" + callee_name + "\" isn't declared in the current class.", p_call->line);
- return DataType();
- }
- DataType tmp_type;
- valid = _get_member_type(original_type, func_id->name, tmp_type);
- if (valid) {
- if (tmp_type.is_constant) {
- _add_warning(GDScriptWarning::CONSTANT_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string());
- } else {
- _add_warning(GDScriptWarning::PROPERTY_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string());
- }
+ Variant value = static_cast<LiteralNode *>(argument)->value;
+ if (!Variant::can_convert_strict(value.get_type(), parameter.type)) {
+ push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
+ return false;
}
- _add_warning(GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->line, callee_name, original_type.to_string());
- _mark_line_as_unsafe(p_call->line);
-#endif // DEBUG_ENABLED
- return DataType();
- }
-
-#ifdef DEBUG_ENABLED
- if (current_function && !for_completion && !is_static && p_call->arguments[0]->type == Node::TYPE_SELF && current_function->_static) {
- _set_error("Can't call non-static function from a static function.", p_call->line);
- return DataType();
- }
-
- if (check_types && !is_static && !is_initializer && base_type.is_meta_type) {
- _set_error("Non-static function \"" + String(callee_name) + "\" can only be called from an instance.", p_call->line);
- return DataType();
- }
-
- // Check signal emission for warnings
- if (callee_name == "emit_signal" && p_call->op == OperatorNode::OP_CALL && p_call->arguments[0]->type == Node::TYPE_SELF && p_call->arguments.size() >= 3 && p_call->arguments[2]->type == Node::TYPE_CONSTANT) {
- ConstantNode *sig = static_cast<ConstantNode *>(p_call->arguments[2]);
- String emitted = sig->value.get_type() == Variant::STRING ? sig->value.operator String() : "";
- for (int i = 0; i < current_class->_signals.size(); i++) {
- if (current_class->_signals[i].name == emitted) {
- current_class->_signals.write[i].emissions += 1;
- break;
- }
+ Callable::CallError error;
+ const Variant *args = &value;
+ p_annotation->resolved_arguments.push_back(Variant::construct(parameter.type, &(args), 1, error));
+ if (error.error != Callable::CallError::CALL_OK) {
+ push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
+ p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1);
+ return false;
}
+ break;
}
-#endif // DEBUG_ENABLED
- } break;
- }
-
-#ifdef DEBUG_ENABLED
- if (!check_types) {
- return return_type;
+ }
}
- if (arg_count < arg_types.size() - default_args_count) {
- _set_error("Too few arguments for \"" + callee_name + "()\" call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line);
- return return_type;
- }
- if (!is_vararg && arg_count > arg_types.size()) {
- _set_error("Too many arguments for \"" + callee_name + "()\" call. Expected at most " + itos(arg_types.size()) + ".", p_call->line);
- return return_type;
- }
+ return true;
+}
- int arg_diff = p_call->arguments.size() - arg_count;
- for (int i = arg_diff; i < p_call->arguments.size(); i++) {
- DataType par_type = _reduce_node_type(p_call->arguments[i]);
+bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) {
+ this->_is_tool = true;
+ return true;
+}
- if ((i - arg_diff) >= arg_types.size()) {
- continue;
- }
+bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) {
+ ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)");
+ ClassNode *p_class = static_cast<ClassNode *>(p_node);
+ p_class->icon_path = p_annotation->resolved_arguments[0];
+ return true;
+}
- DataType arg_type = arg_types[i - arg_diff];
+bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) {
+ ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
- if (!par_type.has_type) {
- _mark_line_as_unsafe(p_call->line);
- if (par_type.may_yield && p_call->arguments[i]->type == Node::TYPE_OPERATOR) {
- _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i])));
- }
- } else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) {
- // Supertypes are acceptable for dynamic compliance
- if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) {
- _set_error("At \"" + callee_name + "()\" call, argument " + itos(i - arg_diff + 1) + ". The passed argument's type (" +
- par_type.to_string() + ") doesn't match the function's expected argument type (" +
- arg_types[i - arg_diff].to_string() + ").",
- p_call->line);
- return DataType();
- } else {
- _mark_line_as_unsafe(p_call->line);
- }
- } else {
- if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_type.kind == DataType::BUILTIN && par_type.builtin_type == Variant::FLOAT) {
- _add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, callee_name);
- }
- }
+ VariableNode *variable = static_cast<VariableNode *>(p_node);
+ if (variable->onready) {
+ push_error(R"("@onready" annotation can only be used once per variable.)");
+ return false;
}
-
-#endif // DEBUG_ENABLED
-
- return return_type;
+ variable->onready = true;
+ current_class->onready_used = true;
+ return true;
}
-bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type, bool *r_is_const) const {
- DataType base_type = p_base_type;
+template <PropertyHint t_hint, Variant::Type t_type>
+bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_node) {
+ ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
- // Check classes in current file
- ClassNode *base = nullptr;
- if (base_type.kind == DataType::CLASS) {
- base = base_type.class_type;
+ VariableNode *variable = static_cast<VariableNode *>(p_node);
+ if (variable->exported) {
+ push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);
+ return false;
}
- while (base) {
- if (base->constant_expressions.has(p_member)) {
- if (r_is_const) {
- *r_is_const = true;
- }
- r_member_type = base->constant_expressions[p_member].expression->get_datatype();
- return true;
- }
+ variable->exported = true;
+ // TODO: Improving setting type, especially for range hints, which can be int or float.
+ variable->export_info.type = t_type;
+ variable->export_info.hint = t_hint;
- if (!base_type.is_meta_type) {
- for (int i = 0; i < base->variables.size(); i++) {
- if (base->variables[i].identifier == p_member) {
- r_member_type = base->variables[i].data_type;
- base->variables.write[i].usages += 1;
- return true;
- }
+ if (p_annotation->name == "@export") {
+ if (variable->datatype_specifier == nullptr) {
+ if (variable->initializer == nullptr) {
+ push_error(R"(Cannot use "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation);
+ return false;
}
- } else {
- for (int i = 0; i < base->subclasses.size(); i++) {
- ClassNode *c = base->subclasses[i];
- if (c->name == p_member) {
- DataType class_type;
- class_type.has_type = true;
- class_type.is_constant = true;
- class_type.is_meta_type = true;
- class_type.kind = DataType::CLASS;
- class_type.class_type = c;
- r_member_type = class_type;
- return true;
- }
+ if (variable->initializer->type != Node::LITERAL) {
+ push_error(R"(To use "@export" annotation with type-less variable, the default value must be a literal.)", p_annotation);
+ return false;
}
- }
-
- base_type = base->base_type;
- if (base_type.kind == DataType::CLASS) {
- base = base_type.class_type;
- } else {
- break;
- }
- }
-
- Ref<GDScript> gds;
- if (base_type.kind == DataType::GDSCRIPT) {
- gds = base_type.script_type;
- if (gds.is_null() || !gds->is_valid()) {
- // GDScript wasn't properly compíled, don't bother trying
- return false;
- }
- }
-
- Ref<Script> scr;
- if (base_type.kind == DataType::SCRIPT) {
- scr = base_type.script_type;
- }
-
- StringName native;
- if (base_type.kind == DataType::NATIVE) {
- native = base_type.native_type;
+ variable->export_info.type = static_cast<LiteralNode *>(variable->initializer)->value.get_type();
+ } // else: Actual type will be set by the analyzer, which can infer the proper type.
}
- // Check GDScripts
- while (gds.is_valid()) {
- if (gds->get_constants().has(p_member)) {
- Variant c = gds->get_constants()[p_member];
- r_member_type = _type_from_variant(c);
- return true;
- }
-
- if (!base_type.is_meta_type) {
- if (gds->get_members().has(p_member)) {
- r_member_type = _type_from_gdtype(gds->get_member_type(p_member));
- return true;
- }
- }
-
- native = gds->get_instance_base_type();
- if (gds->get_base_script().is_valid()) {
- gds = gds->get_base_script();
- scr = gds->get_base_script();
- bool is_meta = base_type.is_meta_type;
- base_type = _type_from_variant(scr.operator Variant());
- base_type.is_meta_type = is_meta;
- } else {
- break;
+ String hint_string;
+ for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
+ if (i > 0) {
+ hint_string += ",";
}
+ hint_string += String(p_annotation->resolved_arguments[i]);
}
-#define IS_USAGE_MEMBER(m_usage) (!(m_usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_CATEGORY)))
-
- // Check other script types
- while (scr.is_valid()) {
- Map<StringName, Variant> constants;
- scr->get_constants(&constants);
- if (constants.has(p_member)) {
- r_member_type = _type_from_variant(constants[p_member]);
- return true;
- }
-
- List<PropertyInfo> properties;
- scr->get_script_property_list(&properties);
- for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
- if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) {
- r_member_type = _type_from_property(E->get());
- return true;
- }
- }
-
- base_type = _type_from_variant(scr.operator Variant());
- native = scr->get_instance_base_type();
- scr = scr->get_base_script();
- }
+ variable->export_info.hint_string = hint_string;
- if (native == StringName()) {
- // Empty native class, might happen in some Script implementations
- // Just ignore it
- return false;
- }
+ return true;
+}
- // Check ClassDB
- if (!ClassDB::class_exists(native)) {
- native = "_" + native.operator String();
- }
- if (!ClassDB::class_exists(native)) {
- if (!check_types) {
- return false;
- }
- ERR_FAIL_V_MSG(false, "Parser bug: Class \"" + String(native) + "\" not found.");
- }
+bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) {
+ ERR_FAIL_V_MSG(false, "Not implemented.");
+}
- bool valid = false;
- ClassDB::get_integer_constant(native, p_member, &valid);
- if (valid) {
- DataType ct;
- ct.has_type = true;
- ct.is_constant = true;
- ct.kind = DataType::BUILTIN;
- ct.builtin_type = Variant::INT;
- r_member_type = ct;
- return true;
- }
+template <MultiplayerAPI::RPCMode t_mode>
+bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) {
+ ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name));
- if (!base_type.is_meta_type) {
- List<PropertyInfo> properties;
- ClassDB::get_property_list(native, &properties);
- for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
- if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) {
- // Check if a getter exists
- StringName getter_name = ClassDB::get_property_getter(native, p_member);
- if (getter_name != StringName()) {
- // Use the getter return type
-#ifdef DEBUG_METHODS_ENABLED
- MethodBind *getter_method = ClassDB::get_method(native, getter_name);
- if (getter_method) {
- r_member_type = _type_from_property(getter_method->get_return_info());
- } else {
- r_member_type = DataType();
- }
-#else
- r_member_type = DataType();
-#endif
- } else {
- r_member_type = _type_from_property(E->get());
- }
- return true;
+ switch (p_node->type) {
+ case Node::VARIABLE: {
+ VariableNode *variable = static_cast<VariableNode *>(p_node);
+ if (variable->rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) {
+ push_error(R"(RPC annotations can only be used once per variable.)", p_annotation);
}
+ variable->rpc_mode = t_mode;
+ break;
}
- }
-
- // If the base is a script, it might be trying to access members of the Script class itself
- if (p_base_type.is_meta_type && (p_base_type.kind == DataType::SCRIPT || p_base_type.kind == DataType::GDSCRIPT)) {
- native = p_base_type.script_type->get_class_name();
- ClassDB::get_integer_constant(native, p_member, &valid);
- if (valid) {
- DataType ct;
- ct.has_type = true;
- ct.is_constant = true;
- ct.kind = DataType::BUILTIN;
- ct.builtin_type = Variant::INT;
- r_member_type = ct;
- return true;
- }
-
- List<PropertyInfo> properties;
- ClassDB::get_property_list(native, &properties);
- for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
- if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) {
- // Check if a getter exists
- StringName getter_name = ClassDB::get_property_getter(native, p_member);
- if (getter_name != StringName()) {
- // Use the getter return type
-#ifdef DEBUG_METHODS_ENABLED
- MethodBind *getter_method = ClassDB::get_method(native, getter_name);
- if (getter_method) {
- r_member_type = _type_from_property(getter_method->get_return_info());
- } else {
- r_member_type = DataType();
- }
-#else
- r_member_type = DataType();
-#endif
- } else {
- r_member_type = _type_from_property(E->get());
- }
- return true;
+ case Node::FUNCTION: {
+ FunctionNode *function = static_cast<FunctionNode *>(p_node);
+ if (function->rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) {
+ push_error(R"(RPC annotations can only be used once per function.)", p_annotation);
}
+ function->rpc_mode = t_mode;
+ break;
}
+ default:
+ return false; // Unreachable.
}
-#undef IS_USAGE_MEMBER
- return false;
+ return true;
}
-GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line, bool p_is_indexing) {
- if (p_base_type && !p_base_type->has_type) {
- return DataType();
- }
-
- DataType base_type;
- DataType member_type;
-
- if (!p_base_type) {
- base_type.has_type = true;
- base_type.is_constant = true;
- base_type.kind = DataType::CLASS;
- base_type.class_type = current_class;
- } else {
- base_type = DataType(*p_base_type);
- }
-
- bool is_const = false;
- if (_get_member_type(base_type, p_identifier, member_type, &is_const)) {
- if (!p_base_type && current_function && current_function->_static && !is_const) {
- _set_error("Can't access member variable (\"" + p_identifier.operator String() + "\") from a static function.", p_line);
+GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {
+ switch (type) {
+ case CONSTANT:
+ return constant->get_datatype();
+ case VARIABLE:
+ return variable->get_datatype();
+ case PARAMETER:
+ return parameter->get_datatype();
+ case FOR_VARIABLE:
+ case PATTERN_BIND:
+ return bind->get_datatype();
+ case UNDEFINED:
return DataType();
- }
- return member_type;
}
+ return DataType();
+}
- if (p_is_indexing) {
- // Don't look for globals since this is an indexed identifier
- return DataType();
+String GDScriptParser::SuiteNode::Local::get_name() const {
+ String name;
+ switch (type) {
+ case SuiteNode::Local::PARAMETER:
+ name = "parameter";
+ break;
+ case SuiteNode::Local::CONSTANT:
+ name = "constant";
+ break;
+ case SuiteNode::Local::VARIABLE:
+ name = "variable";
+ break;
+ case SuiteNode::Local::FOR_VARIABLE:
+ name = "for loop iterator";
+ break;
+ case SuiteNode::Local::PATTERN_BIND:
+ name = "pattern_bind";
+ break;
+ case SuiteNode::Local::UNDEFINED:
+ name = "<undefined>";
+ break;
}
+ return name;
+}
- if (!p_base_type) {
- // Possibly this is a global, check before failing
-
- if (ClassDB::class_exists(p_identifier) || ClassDB::class_exists("_" + p_identifier.operator String())) {
- DataType result;
- result.has_type = true;
- result.is_constant = true;
- result.is_meta_type = true;
- if (Engine::get_singleton()->has_singleton(p_identifier) || Engine::get_singleton()->has_singleton("_" + p_identifier.operator String())) {
- result.is_meta_type = false;
+String GDScriptParser::DataType::to_string() const {
+ switch (kind) {
+ case VARIANT:
+ return "Variant";
+ case BUILTIN:
+ if (builtin_type == Variant::NIL) {
+ return "null";
}
- result.kind = DataType::NATIVE;
- result.native_type = p_identifier;
- return result;
- }
-
- ClassNode *outer_class = current_class;
- while (outer_class) {
- if (outer_class->name == p_identifier) {
- DataType result;
- result.has_type = true;
- result.is_constant = true;
- result.is_meta_type = true;
- result.kind = DataType::CLASS;
- result.class_type = outer_class;
- return result;
+ return Variant::get_type_name(builtin_type);
+ case NATIVE:
+ if (is_meta_type) {
+ return GDScriptNativeClass::get_class_static();
}
- if (outer_class->constant_expressions.has(p_identifier)) {
- return outer_class->constant_expressions[p_identifier].type;
+ return native_type.operator String();
+ case CLASS:
+ if (is_meta_type) {
+ return GDScript::get_class_static();
}
- for (int i = 0; i < outer_class->subclasses.size(); i++) {
- if (outer_class->subclasses[i] == current_class) {
- continue;
- }
- if (outer_class->subclasses[i]->name == p_identifier) {
- DataType result;
- result.has_type = true;
- result.is_constant = true;
- result.is_meta_type = true;
- result.kind = DataType::CLASS;
- result.class_type = outer_class->subclasses[i];
- return result;
- }
+ if (class_type->identifier != nullptr) {
+ return class_type->identifier->name.operator String();
}
- outer_class = outer_class->owner;
- }
-
- if (ScriptServer::is_global_class(p_identifier)) {
- Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier));
- if (scr.is_valid()) {
- DataType result;
- result.has_type = true;
- result.script_type = scr;
- result.is_constant = true;
- result.is_meta_type = true;
- Ref<GDScript> gds = scr;
- if (gds.is_valid()) {
- if (!gds->is_valid()) {
- _set_error("The class \"" + p_identifier + "\" couldn't be fully loaded (script error or cyclic dependency).");
- return DataType();
- }
- result.kind = DataType::GDSCRIPT;
- } else {
- result.kind = DataType::SCRIPT;
- }
- return result;
+ return class_type->fqcn;
+ case SCRIPT: {
+ if (is_meta_type) {
+ return script_type->get_class_name().operator String();
}
- _set_error("The class \"" + p_identifier + "\" was found in global scope, but its script couldn't be loaded.");
- return DataType();
- }
-
- if (GDScriptLanguage::get_singleton()->get_global_map().has(p_identifier)) {
- int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier];
- Variant g = GDScriptLanguage::get_singleton()->get_global_array()[idx];
- return _type_from_variant(g);
- }
-
- if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) {
- Variant g = GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier];
- return _type_from_variant(g);
- }
-
- // Non-tool singletons aren't loaded, check project settings
- List<PropertyInfo> props;
- ProjectSettings::get_singleton()->get_property_list(&props);
-
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- String s = E->get().name;
- if (!s.begins_with("autoload/")) {
- continue;
+ String name = script_type->get_name();
+ if (!name.empty()) {
+ return name;
}
- String name = s.get_slice("/", 1);
- if (name == p_identifier) {
- String script = ProjectSettings::get_singleton()->get(s);
- if (script.begins_with("*")) {
- script = script.right(1);
- }
- if (!script.begins_with("res://")) {
- script = "res://" + script;
- }
- Ref<Script> singleton = ResourceLoader::load(script);
- if (singleton.is_valid()) {
- DataType result;
- result.has_type = true;
- result.is_constant = true;
- result.script_type = singleton;
-
- Ref<GDScript> gds = singleton;
- if (gds.is_valid()) {
- if (!gds->is_valid()) {
- _set_error("Couldn't fully load the singleton script \"" + p_identifier + "\" (possible cyclic reference or parse error).", p_line);
- return DataType();
- }
- result.kind = DataType::GDSCRIPT;
- } else {
- result.kind = DataType::SCRIPT;
- }
- }
+ name = script_path;
+ if (!name.empty()) {
+ return name;
}
+ return native_type.operator String();
}
-
- // This means looking in the current class, which type is always known
- _set_error("The identifier \"" + p_identifier.operator String() + "\" isn't declared in the current scope.", p_line);
- }
-
-#ifdef DEBUG_ENABLED
- {
- DataType tmp_type;
- List<DataType> arg_types;
- int argcount;
- bool _static;
- bool vararg;
- if (_get_function_signature(base_type, p_identifier, tmp_type, arg_types, argcount, _static, vararg)) {
- _add_warning(GDScriptWarning::FUNCTION_USED_AS_PROPERTY, p_line, p_identifier.operator String(), base_type.to_string());
- }
+ case ENUM:
+ return enum_type.operator String() + " (enum)";
+ case ENUM_VALUE:
+ return enum_type.operator String() + " (enum value)";
+ case UNRESOLVED:
+ return "<unresolved type>";
}
-#endif // DEBUG_ENABLED
- _mark_line_as_unsafe(p_line);
- return DataType();
+ ERR_FAIL_V_MSG("<unresolved type", "Kind set outside the enum range.");
}
-void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
- // Names of internal object properties that we check to avoid overriding them.
- // "__meta__" could also be in here, but since it doesn't really affect object metadata,
- // it is okay to override it on script.
- StringName script_name = CoreStringNames::get_singleton()->_script;
+/*---------- PRETTY PRINT FOR DEBUG ----------*/
- _mark_line_as_safe(p_class->line);
-
- // Constants
- for (Map<StringName, ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
- ClassNode::Constant &c = E->get();
- _mark_line_as_safe(c.expression->line);
- DataType cont = _resolve_type(c.type, c.expression->line);
- DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line);
-
- if (check_types && !_is_type_compatible(cont, expr)) {
- _set_error("The constant value type (" + expr.to_string() + ") isn't compatible with declared type (" + cont.to_string() + ").",
- c.expression->line);
- return;
- }
-
- expr.is_constant = true;
- c.type = expr;
- c.expression->set_datatype(expr);
+#ifdef DEBUG_ENABLED
- DataType tmp;
- const StringName &constant_name = E->key();
- if (constant_name == script_name || _get_member_type(p_class->base_type, constant_name, tmp)) {
- _set_error("The member \"" + String(constant_name) + "\" already exists in a parent class.", c.expression->line);
- return;
+void GDScriptParser::TreePrinter::increase_indent() {
+ indent_level++;
+ indent = "";
+ for (int i = 0; i < indent_level * 4; i++) {
+ if (i % 4 == 0) {
+ indent += "|";
+ } else {
+ indent += " ";
}
}
+}
- // Function declarations
- for (int i = 0; i < p_class->static_functions.size(); i++) {
- _check_function_types(p_class->static_functions[i]);
- if (error_set) {
- return;
+void GDScriptParser::TreePrinter::decrease_indent() {
+ indent_level--;
+ indent = "";
+ for (int i = 0; i < indent_level * 4; i++) {
+ if (i % 4 == 0) {
+ indent += "|";
+ } else {
+ indent += " ";
}
}
+}
- for (int i = 0; i < p_class->functions.size(); i++) {
- _check_function_types(p_class->functions[i]);
- if (error_set) {
- return;
- }
+void GDScriptParser::TreePrinter::push_line(const String &p_line) {
+ if (!p_line.empty()) {
+ push_text(p_line);
}
+ printed += "\n";
+ pending_indent = true;
+}
- // Class variables
- for (int i = 0; i < p_class->variables.size(); i++) {
- ClassNode::Member &v = p_class->variables.write[i];
-
- DataType tmp;
- if (v.identifier == script_name || _get_member_type(p_class->base_type, v.identifier, tmp)) {
- _set_error("The member \"" + String(v.identifier) + "\" already exists in a parent class.", v.line);
- return;
- }
-
- _mark_line_as_safe(v.line);
- v.data_type = _resolve_type(v.data_type, v.line);
- v.initial_assignment->arguments[0]->set_datatype(v.data_type);
-
- if (v.expression) {
- DataType expr_type = _reduce_node_type(v.expression);
-
- if (check_types && !_is_type_compatible(v.data_type, expr_type)) {
- // Try supertype test
- if (_is_type_compatible(expr_type, v.data_type)) {
- _mark_line_as_unsafe(v.line);
- } else {
- // Try with implicit conversion
- if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) {
- _set_error("The assigned expression's type (" + expr_type.to_string() + ") doesn't match the variable's type (" +
- v.data_type.to_string() + ").",
- v.line);
- return;
- }
-
- // Replace assignment with implicit conversion
- BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
- convert->line = v.line;
- convert->function = GDScriptFunctions::TYPE_CONVERT;
-
- ConstantNode *tgt_type = alloc_node<ConstantNode>();
- tgt_type->line = v.line;
- tgt_type->value = (int64_t)v.data_type.builtin_type;
-
- OperatorNode *convert_call = alloc_node<OperatorNode>();
- convert_call->line = v.line;
- convert_call->op = OperatorNode::OP_CALL;
- convert_call->arguments.push_back(convert);
- convert_call->arguments.push_back(v.expression);
- convert_call->arguments.push_back(tgt_type);
-
- v.expression = convert_call;
- v.initial_assignment->arguments.write[1] = convert_call;
- }
- }
-
- if (v.data_type.infer_type) {
- if (!expr_type.has_type) {
- _set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", v.line);
- return;
- }
- if (expr_type.kind == DataType::BUILTIN && expr_type.builtin_type == Variant::NIL) {
- _set_error("The variable type cannot be inferred because its value is \"null\".", v.line);
- return;
- }
- v.data_type = expr_type;
- v.data_type.is_constant = false;
- }
- }
-
- // Check export hint
- if (v.data_type.has_type && v._export.type != Variant::NIL) {
- DataType export_type = _type_from_property(v._export);
- if (!_is_type_compatible(v.data_type, export_type, true)) {
- _set_error("The export hint's type (" + export_type.to_string() + ") doesn't match the variable's type (" +
- v.data_type.to_string() + ").",
- v.line);
- return;
- }
- }
-
- // Setter and getter
- if (v.setter == StringName() && v.getter == StringName()) {
- continue;
- }
+void GDScriptParser::TreePrinter::push_text(const String &p_text) {
+ if (pending_indent) {
+ printed += indent;
+ pending_indent = false;
+ }
+ printed += p_text;
+}
- bool found_getter = false;
- bool found_setter = false;
- for (int j = 0; j < p_class->functions.size(); j++) {
- if (v.setter == p_class->functions[j]->name) {
- found_setter = true;
- FunctionNode *setter = p_class->functions[j];
-
- if (setter->get_required_argument_count() != 1 &&
- !(setter->get_required_argument_count() == 0 && setter->default_values.size() > 0)) {
- _set_error("The setter function needs to receive exactly 1 argument. See \"" + setter->name +
- "()\" definition at line " + itos(setter->line) + ".",
- v.line);
- return;
- }
- if (!_is_type_compatible(v.data_type, setter->argument_types[0])) {
- _set_error("The setter argument's type (" + setter->argument_types[0].to_string() +
- ") doesn't match the variable's type (" + v.data_type.to_string() + "). See \"" +
- setter->name + "()\" definition at line " + itos(setter->line) + ".",
- v.line);
- return;
- }
- continue;
- }
- if (v.getter == p_class->functions[j]->name) {
- found_getter = true;
- FunctionNode *getter = p_class->functions[j];
-
- if (getter->get_required_argument_count() != 0) {
- _set_error("The getter function can't receive arguments. See \"" + getter->name +
- "()\" definition at line " + itos(getter->line) + ".",
- v.line);
- return;
- }
- if (!_is_type_compatible(v.data_type, getter->get_datatype())) {
- _set_error("The getter return type (" + getter->get_datatype().to_string() +
- ") doesn't match the variable's type (" + v.data_type.to_string() +
- "). See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".",
- v.line);
- return;
- }
- }
- if (found_getter && found_setter) {
- break;
- }
+void GDScriptParser::TreePrinter::print_annotation(AnnotationNode *p_annotation) {
+ push_text(p_annotation->name);
+ push_text(" (");
+ for (int i = 0; i < p_annotation->arguments.size(); i++) {
+ if (i > 0) {
+ push_text(" , ");
}
+ print_expression(p_annotation->arguments[i]);
+ }
+ push_line(")");
+}
- if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) {
- continue;
+void GDScriptParser::TreePrinter::print_array(ArrayNode *p_array) {
+ push_text("[ ");
+ for (int i = 0; i < p_array->elements.size(); i++) {
+ if (i > 0) {
+ push_text(" , ");
}
+ print_expression(p_array->elements[i]);
+ }
+ push_text(" ]");
+}
- // Check for static functions
- for (int j = 0; j < p_class->static_functions.size(); j++) {
- if (v.setter == p_class->static_functions[j]->name) {
- FunctionNode *setter = p_class->static_functions[j];
- _set_error("The setter can't be a static function. See \"" + setter->name + "()\" definition at line " + itos(setter->line) + ".", v.line);
- return;
- }
- if (v.getter == p_class->static_functions[j]->name) {
- FunctionNode *getter = p_class->static_functions[j];
- _set_error("The getter can't be a static function. See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", v.line);
- return;
- }
- }
+void GDScriptParser::TreePrinter::print_assert(AssertNode *p_assert) {
+ push_text("Assert ( ");
+ print_expression(p_assert->condition);
+ push_line(" )");
+}
- if (!found_setter && v.setter != StringName()) {
- _set_error("The setter function isn't defined.", v.line);
- return;
- }
+void GDScriptParser::TreePrinter::print_assignment(AssignmentNode *p_assignment) {
+ switch (p_assignment->assignee->type) {
+ case Node::IDENTIFIER:
+ print_identifier(static_cast<IdentifierNode *>(p_assignment->assignee));
+ break;
+ case Node::SUBSCRIPT:
+ print_subscript(static_cast<SubscriptNode *>(p_assignment->assignee));
+ break;
+ default:
+ break; // Unreachable.
+ }
- if (!found_getter && v.getter != StringName()) {
- _set_error("The getter function isn't defined.", v.line);
- return;
- }
+ push_text(" ");
+ switch (p_assignment->operation) {
+ case AssignmentNode::OP_ADDITION:
+ push_text("+");
+ break;
+ case AssignmentNode::OP_SUBTRACTION:
+ push_text("-");
+ break;
+ case AssignmentNode::OP_MULTIPLICATION:
+ push_text("*");
+ break;
+ case AssignmentNode::OP_DIVISION:
+ push_text("/");
+ break;
+ case AssignmentNode::OP_MODULO:
+ push_text("%");
+ break;
+ case AssignmentNode::OP_BIT_SHIFT_LEFT:
+ push_text("<<");
+ break;
+ case AssignmentNode::OP_BIT_SHIFT_RIGHT:
+ push_text(">>");
+ break;
+ case AssignmentNode::OP_BIT_AND:
+ push_text("&");
+ break;
+ case AssignmentNode::OP_BIT_OR:
+ push_text("|");
+ break;
+ case AssignmentNode::OP_BIT_XOR:
+ push_text("^");
+ break;
+ case AssignmentNode::OP_NONE:
+ break;
}
+ push_text("= ");
+ print_expression(p_assignment->assigned_value);
+ push_line();
+}
- // Signals
- DataType base = p_class->base_type;
+void GDScriptParser::TreePrinter::print_await(AwaitNode *p_await) {
+ push_text("Await ");
+ print_expression(p_await->to_await);
+}
- while (base.kind == DataType::CLASS) {
- ClassNode *base_class = base.class_type;
- for (int i = 0; i < p_class->_signals.size(); i++) {
- for (int j = 0; j < base_class->_signals.size(); j++) {
- if (p_class->_signals[i].name == base_class->_signals[j].name) {
- _set_error("The signal \"" + p_class->_signals[i].name + "\" already exists in a parent class.", p_class->_signals[i].line);
- return;
- }
- }
- }
- base = base_class->base_type;
- }
-
- StringName native;
- if (base.kind == DataType::GDSCRIPT || base.kind == DataType::SCRIPT) {
- Ref<Script> scr = base.script_type;
- if (scr.is_valid() && scr->is_valid()) {
- native = scr->get_instance_base_type();
- for (int i = 0; i < p_class->_signals.size(); i++) {
- if (scr->has_script_signal(p_class->_signals[i].name)) {
- _set_error("The signal \"" + p_class->_signals[i].name + "\" already exists in a parent class.", p_class->_signals[i].line);
- return;
- }
- }
- }
- } else if (base.kind == DataType::NATIVE) {
- native = base.native_type;
+void GDScriptParser::TreePrinter::print_binary_op(BinaryOpNode *p_binary_op) {
+ // Surround in parenthesis for disambiguation.
+ push_text("(");
+ print_expression(p_binary_op->left_operand);
+ switch (p_binary_op->operation) {
+ case BinaryOpNode::OP_ADDITION:
+ push_text(" + ");
+ break;
+ case BinaryOpNode::OP_SUBTRACTION:
+ push_text(" - ");
+ break;
+ case BinaryOpNode::OP_MULTIPLICATION:
+ push_text(" * ");
+ break;
+ case BinaryOpNode::OP_DIVISION:
+ push_text(" / ");
+ break;
+ case BinaryOpNode::OP_MODULO:
+ push_text(" % ");
+ break;
+ case BinaryOpNode::OP_BIT_LEFT_SHIFT:
+ push_text(" << ");
+ break;
+ case BinaryOpNode::OP_BIT_RIGHT_SHIFT:
+ push_text(" >> ");
+ break;
+ case BinaryOpNode::OP_BIT_AND:
+ push_text(" & ");
+ break;
+ case BinaryOpNode::OP_BIT_OR:
+ push_text(" | ");
+ break;
+ case BinaryOpNode::OP_BIT_XOR:
+ push_text(" ^ ");
+ break;
+ case BinaryOpNode::OP_LOGIC_AND:
+ push_text(" AND ");
+ break;
+ case BinaryOpNode::OP_LOGIC_OR:
+ push_text(" OR ");
+ break;
+ case BinaryOpNode::OP_TYPE_TEST:
+ push_text(" IS ");
+ break;
+ case BinaryOpNode::OP_CONTENT_TEST:
+ push_text(" IN ");
+ break;
+ case BinaryOpNode::OP_COMP_EQUAL:
+ push_text(" == ");
+ break;
+ case BinaryOpNode::OP_COMP_NOT_EQUAL:
+ push_text(" != ");
+ break;
+ case BinaryOpNode::OP_COMP_LESS:
+ push_text(" < ");
+ break;
+ case BinaryOpNode::OP_COMP_LESS_EQUAL:
+ push_text(" <= ");
+ break;
+ case BinaryOpNode::OP_COMP_GREATER:
+ push_text(" > ");
+ break;
+ case BinaryOpNode::OP_COMP_GREATER_EQUAL:
+ push_text(" >= ");
+ break;
}
+ print_expression(p_binary_op->right_operand);
+ // Surround in parenthesis for disambiguation.
+ push_text(")");
+}
- if (native != StringName()) {
- for (int i = 0; i < p_class->_signals.size(); i++) {
- if (ClassDB::has_signal(native, p_class->_signals[i].name)) {
- _set_error("The signal \"" + p_class->_signals[i].name + "\" already exists in a parent class.", p_class->_signals[i].line);
- return;
- }
+void GDScriptParser::TreePrinter::print_call(CallNode *p_call) {
+ if (p_call->is_super) {
+ push_text("super");
+ if (p_call->callee != nullptr) {
+ push_text(".");
+ print_expression(p_call->callee);
}
+ } else {
+ print_expression(p_call->callee);
}
-
- // Inner classes
- for (int i = 0; i < p_class->subclasses.size(); i++) {
- current_class = p_class->subclasses[i];
- _check_class_level_types(current_class);
- if (error_set) {
- return;
+ push_text("( ");
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ if (i > 0) {
+ push_text(" , ");
}
- current_class = p_class;
+ print_expression(p_call->arguments[i]);
}
+ push_text(" )");
}
-void GDScriptParser::_check_function_types(FunctionNode *p_function) {
- p_function->return_type = _resolve_type(p_function->return_type, p_function->line);
-
- // Arguments
- int defaults_ofs = p_function->arguments.size() - p_function->default_values.size();
- for (int i = 0; i < p_function->arguments.size(); i++) {
- if (i < defaults_ofs) {
- p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line);
- } else {
- if (p_function->default_values[i - defaults_ofs]->type != Node::TYPE_OPERATOR) {
- _set_error("Parser bug: invalid argument default value.", p_function->line, p_function->column);
- return;
- }
-
- OperatorNode *op = static_cast<OperatorNode *>(p_function->default_values[i - defaults_ofs]);
-
- if (op->op != OperatorNode::OP_ASSIGN || op->arguments.size() != 2) {
- _set_error("Parser bug: invalid argument default value operation.", p_function->line);
- return;
- }
+void GDScriptParser::TreePrinter::print_cast(CastNode *p_cast) {
+ print_expression(p_cast->operand);
+ push_text(" AS ");
+ print_type(p_cast->cast_type);
+}
- DataType def_type = _reduce_node_type(op->arguments[1]);
+void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) {
+ push_text("Class ");
+ if (p_class->identifier == nullptr) {
+ push_text("<unnamed>");
+ } else {
+ print_identifier(p_class->identifier);
+ }
- if (p_function->argument_types[i].infer_type) {
- def_type.is_constant = false;
- p_function->argument_types.write[i] = def_type;
+ if (p_class->extends_used) {
+ bool first = true;
+ push_text(" Extends ");
+ if (!p_class->extends_path.empty()) {
+ push_text(vformat(R"("%s")", p_class->extends_path));
+ first = false;
+ }
+ for (int i = 0; i < p_class->extends.size(); i++) {
+ if (!first) {
+ push_text(".");
} else {
- p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line);
-
- if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) {
- String arg_name = p_function->arguments[i];
- _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" +
- arg_name + "' (" + p_function->argument_types[i].to_string() + ").",
- p_function->line);
- }
- }
- }
-#ifdef DEBUG_ENABLED
- if (p_function->arguments_usage[i] == 0 && !p_function->arguments[i].operator String().begins_with("_")) {
- _add_warning(GDScriptWarning::UNUSED_ARGUMENT, p_function->line, p_function->name, p_function->arguments[i].operator String());
- }
- for (int j = 0; j < current_class->variables.size(); j++) {
- if (current_class->variables[j].identifier == p_function->arguments[i]) {
- _add_warning(GDScriptWarning::SHADOWED_VARIABLE, p_function->line, p_function->arguments[i], itos(current_class->variables[j].line));
+ first = false;
}
+ push_text(p_class->extends[i]);
}
-#endif // DEBUG_ENABLED
}
- if (!(p_function->name == "_init")) {
- // Signature for the initializer may vary
-#ifdef DEBUG_ENABLED
- DataType return_type;
- List<DataType> arg_types;
- int default_arg_count = 0;
- bool _static = false;
- bool vararg = false;
-
- DataType base_type = current_class->base_type;
- if (_get_function_signature(base_type, p_function->name, return_type, arg_types, default_arg_count, _static, vararg)) {
- bool valid = _static == p_function->_static;
- valid = valid && return_type == p_function->return_type;
- int argsize_diff = p_function->arguments.size() - arg_types.size();
- valid = valid && argsize_diff >= 0;
- valid = valid && p_function->default_values.size() >= default_arg_count + argsize_diff;
- int i = 0;
- for (List<DataType>::Element *E = arg_types.front(); valid && E; E = E->next()) {
- valid = valid && E->get() == p_function->argument_types[i++];
- }
+ push_line(" :");
- if (!valid) {
- String parent_signature = return_type.has_type ? return_type.to_string() : "Variant";
- if (parent_signature == "null") {
- parent_signature = "void";
- }
- parent_signature += " " + p_function->name + "(";
- if (arg_types.size()) {
- int j = 0;
- for (List<DataType>::Element *E = arg_types.front(); E; E = E->next()) {
- if (E != arg_types.front()) {
- parent_signature += ", ";
- }
- String arg = E->get().to_string();
- if (arg == "null" || arg == "var") {
- arg = "Variant";
- }
- parent_signature += arg;
- if (j == arg_types.size() - default_arg_count) {
- parent_signature += "=default";
- }
+ increase_indent();
- j++;
- }
- }
- parent_signature += ")";
- _set_error("The function signature doesn't match the parent. Parent signature is: \"" + parent_signature + "\".", p_function->line);
- return;
- }
- }
-#endif // DEBUG_ENABLED
- } else {
- if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
- _set_error("The constructor can't return a value.", p_function->line);
- return;
- }
- }
+ for (int i = 0; i < p_class->members.size(); i++) {
+ const ClassNode::Member &m = p_class->members[i];
- if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
- if (!p_function->body->has_return) {
- _set_error("A non-void function must return a value in all possible paths.", p_function->line);
- return;
+ switch (m.type) {
+ case ClassNode::Member::CLASS:
+ print_class(m.m_class);
+ break;
+ case ClassNode::Member::VARIABLE:
+ print_variable(m.variable);
+ break;
+ case ClassNode::Member::CONSTANT:
+ print_constant(m.constant);
+ break;
+ case ClassNode::Member::SIGNAL:
+ print_signal(m.signal);
+ break;
+ case ClassNode::Member::FUNCTION:
+ print_function(m.function);
+ break;
+ case ClassNode::Member::ENUM:
+ print_enum(m.m_enum);
+ break;
+ case ClassNode::Member::ENUM_VALUE:
+ break; // Nothing. Will be printed by enum.
+ case ClassNode::Member::UNDEFINED:
+ push_line("<unknown member>");
+ break;
}
}
- if (p_function->has_yield) {
- // yield() will make the function return a GDScriptFunctionState, so the type is ambiguous
- p_function->return_type.has_type = false;
- p_function->return_type.may_yield = true;
- }
+ decrease_indent();
}
-void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
- // Function blocks
- for (int i = 0; i < p_class->static_functions.size(); i++) {
- current_function = p_class->static_functions[i];
- current_block = current_function->body;
- _mark_line_as_safe(current_function->line);
- _check_block_types(current_block);
- current_block = nullptr;
- current_function = nullptr;
- if (error_set) {
- return;
- }
- }
+void GDScriptParser::TreePrinter::print_constant(ConstantNode *p_constant) {
+ push_text("Constant ");
+ print_identifier(p_constant->identifier);
- for (int i = 0; i < p_class->functions.size(); i++) {
- current_function = p_class->functions[i];
- current_block = current_function->body;
- _mark_line_as_safe(current_function->line);
- _check_block_types(current_block);
- current_block = nullptr;
- current_function = nullptr;
- if (error_set) {
- return;
- }
- }
+ increase_indent();
-#ifdef DEBUG_ENABLED
- // Warnings
- for (int i = 0; i < p_class->variables.size(); i++) {
- if (p_class->variables[i].usages == 0) {
- _add_warning(GDScriptWarning::UNUSED_CLASS_VARIABLE, p_class->variables[i].line, p_class->variables[i].identifier);
- }
- }
- for (int i = 0; i < p_class->_signals.size(); i++) {
- if (p_class->_signals[i].emissions == 0) {
- _add_warning(GDScriptWarning::UNUSED_SIGNAL, p_class->_signals[i].line, p_class->_signals[i].name);
- }
+ push_line();
+ push_text("= ");
+ if (p_constant->initializer == nullptr) {
+ push_text("<missing value>");
+ } else {
+ print_expression(p_constant->initializer);
}
-#endif // DEBUG_ENABLED
+ decrease_indent();
+ push_line();
+}
- // Inner classes
- for (int i = 0; i < p_class->subclasses.size(); i++) {
- current_class = p_class->subclasses[i];
- _check_class_blocks_types(current_class);
- if (error_set) {
- return;
+void GDScriptParser::TreePrinter::print_dictionary(DictionaryNode *p_dictionary) {
+ push_line("{");
+ increase_indent();
+ for (int i = 0; i < p_dictionary->elements.size(); i++) {
+ print_expression(p_dictionary->elements[i].key);
+ if (p_dictionary->style == DictionaryNode::PYTHON_DICT) {
+ push_text(" : ");
+ } else {
+ push_text(" = ");
}
- current_class = p_class;
+ print_expression(p_dictionary->elements[i].value);
+ push_line(" ,");
}
+ decrease_indent();
+ push_text("}");
}
-#ifdef DEBUG_ENABLED
-static String _find_function_name(const GDScriptParser::OperatorNode *p_call) {
- switch (p_call->arguments[0]->type) {
- case GDScriptParser::Node::TYPE_TYPE: {
- return Variant::get_type_name(static_cast<GDScriptParser::TypeNode *>(p_call->arguments[0])->vtype);
- } break;
- case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: {
- return GDScriptFunctions::get_func_name(static_cast<GDScriptParser::BuiltInFunctionNode *>(p_call->arguments[0])->function);
- } break;
- default: {
- int id_index = p_call->op == GDScriptParser::OperatorNode::OP_PARENT_CALL ? 0 : 1;
- if (p_call->arguments.size() > id_index && p_call->arguments[id_index]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
- return static_cast<GDScriptParser::IdentifierNode *>(p_call->arguments[id_index])->name;
- }
- } break;
+void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) {
+ switch (p_expression->type) {
+ case Node::ARRAY:
+ print_array(static_cast<ArrayNode *>(p_expression));
+ break;
+ case Node::ASSIGNMENT:
+ print_assignment(static_cast<AssignmentNode *>(p_expression));
+ break;
+ case Node::AWAIT:
+ print_await(static_cast<AwaitNode *>(p_expression));
+ break;
+ case Node::BINARY_OPERATOR:
+ print_binary_op(static_cast<BinaryOpNode *>(p_expression));
+ break;
+ case Node::CALL:
+ print_call(static_cast<CallNode *>(p_expression));
+ break;
+ case Node::CAST:
+ print_cast(static_cast<CastNode *>(p_expression));
+ break;
+ case Node::DICTIONARY:
+ print_dictionary(static_cast<DictionaryNode *>(p_expression));
+ break;
+ case Node::GET_NODE:
+ print_get_node(static_cast<GetNodeNode *>(p_expression));
+ break;
+ case Node::IDENTIFIER:
+ print_identifier(static_cast<IdentifierNode *>(p_expression));
+ break;
+ case Node::LITERAL:
+ print_literal(static_cast<LiteralNode *>(p_expression));
+ break;
+ case Node::PRELOAD:
+ print_preload(static_cast<PreloadNode *>(p_expression));
+ break;
+ case Node::SELF:
+ print_self(static_cast<SelfNode *>(p_expression));
+ break;
+ case Node::SUBSCRIPT:
+ print_subscript(static_cast<SubscriptNode *>(p_expression));
+ break;
+ case Node::TERNARY_OPERATOR:
+ print_ternary_op(static_cast<TernaryOpNode *>(p_expression));
+ break;
+ case Node::UNARY_OPERATOR:
+ print_unary_op(static_cast<UnaryOpNode *>(p_expression));
+ break;
+ default:
+ push_text(vformat("<unknown expression %d>", p_expression->type));
+ break;
}
- return String();
}
-#endif // DEBUG_ENABLED
-
-void GDScriptParser::_check_block_types(BlockNode *p_block) {
- Node *last_var_assign = nullptr;
- // Check each statement
- for (List<Node *>::Element *E = p_block->statements.front(); E; E = E->next()) {
- Node *statement = E->get();
- switch (statement->type) {
- case Node::TYPE_NEWLINE:
- case Node::TYPE_BREAKPOINT: {
- // Nothing to do
- } break;
- case Node::TYPE_ASSERT: {
- AssertNode *an = static_cast<AssertNode *>(statement);
- _mark_line_as_safe(an->line);
- _reduce_node_type(an->condition);
- _reduce_node_type(an->message);
- } break;
- case Node::TYPE_LOCAL_VAR: {
- LocalVarNode *lv = static_cast<LocalVarNode *>(statement);
- lv->datatype = _resolve_type(lv->datatype, lv->line);
- _mark_line_as_safe(lv->line);
-
- last_var_assign = lv->assign;
- if (lv->assign) {
- lv->assign_op->arguments[0]->set_datatype(lv->datatype);
- DataType assign_type = _reduce_node_type(lv->assign);
-#ifdef DEBUG_ENABLED
- if (assign_type.has_type && assign_type.kind == DataType::BUILTIN && assign_type.builtin_type == Variant::NIL) {
- if (lv->assign->type == Node::TYPE_OPERATOR) {
- OperatorNode *call = static_cast<OperatorNode *>(lv->assign);
- if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) {
- _add_warning(GDScriptWarning::VOID_ASSIGNMENT, lv->line, _find_function_name(call));
- }
- }
- }
- if (lv->datatype.has_type && assign_type.may_yield && lv->assign->type == Node::TYPE_OPERATOR) {
- _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, lv->line, _find_function_name(static_cast<OperatorNode *>(lv->assign)));
- }
- for (int i = 0; i < current_class->variables.size(); i++) {
- if (current_class->variables[i].identifier == lv->name) {
- _add_warning(GDScriptWarning::SHADOWED_VARIABLE, lv->line, lv->name, itos(current_class->variables[i].line));
- }
- }
-#endif // DEBUG_ENABLED
-
- if (!_is_type_compatible(lv->datatype, assign_type)) {
- // Try supertype test
- if (_is_type_compatible(assign_type, lv->datatype)) {
- _mark_line_as_unsafe(lv->line);
- } else {
- // Try implicit conversion
- if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) {
- _set_error("The assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" +
- lv->datatype.to_string() + ").",
- lv->line);
- return;
- }
- // Replace assignment with implicit conversion
- BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
- convert->line = lv->line;
- convert->function = GDScriptFunctions::TYPE_CONVERT;
-
- ConstantNode *tgt_type = alloc_node<ConstantNode>();
- tgt_type->line = lv->line;
- tgt_type->value = (int64_t)lv->datatype.builtin_type;
- tgt_type->datatype = _type_from_variant(tgt_type->value);
-
- OperatorNode *convert_call = alloc_node<OperatorNode>();
- convert_call->line = lv->line;
- convert_call->op = OperatorNode::OP_CALL;
- convert_call->arguments.push_back(convert);
- convert_call->arguments.push_back(lv->assign);
- convert_call->arguments.push_back(tgt_type);
-
- lv->assign = convert_call;
- lv->assign_op->arguments.write[1] = convert_call;
-#ifdef DEBUG_ENABLED
- if (lv->datatype.builtin_type == Variant::INT && assign_type.builtin_type == Variant::FLOAT) {
- _add_warning(GDScriptWarning::NARROWING_CONVERSION, lv->line);
- }
-#endif // DEBUG_ENABLED
- }
- }
- if (lv->datatype.infer_type) {
- if (!assign_type.has_type) {
- _set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", lv->line);
- return;
- }
- if (assign_type.kind == DataType::BUILTIN && assign_type.builtin_type == Variant::NIL) {
- _set_error("The variable type cannot be inferred because its value is \"null\".", lv->line);
- return;
- }
- lv->datatype = assign_type;
- lv->datatype.is_constant = false;
- }
- if (lv->datatype.has_type && !assign_type.has_type) {
- _mark_line_as_unsafe(lv->line);
- }
- }
- } break;
- case Node::TYPE_OPERATOR: {
- OperatorNode *op = static_cast<OperatorNode *>(statement);
-
- switch (op->op) {
- case OperatorNode::OP_ASSIGN:
- case OperatorNode::OP_ASSIGN_ADD:
- case OperatorNode::OP_ASSIGN_SUB:
- case OperatorNode::OP_ASSIGN_MUL:
- case OperatorNode::OP_ASSIGN_DIV:
- case OperatorNode::OP_ASSIGN_MOD:
- case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
- case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
- case OperatorNode::OP_ASSIGN_BIT_AND:
- case OperatorNode::OP_ASSIGN_BIT_OR:
- case OperatorNode::OP_ASSIGN_BIT_XOR: {
- if (op->arguments.size() < 2) {
- _set_error("Parser bug: operation without enough arguments.", op->line, op->column);
- return;
- }
-
- if (op->arguments[1] == last_var_assign) {
- // Assignment was already checked
- break;
- }
-
- _mark_line_as_safe(op->line);
-
- DataType lh_type = _reduce_node_type(op->arguments[0]);
-
- if (error_set) {
- return;
- }
-
- if (check_types) {
- if (!lh_type.has_type) {
- if (op->arguments[0]->type == Node::TYPE_OPERATOR) {
- _mark_line_as_unsafe(op->line);
- }
- }
- if (lh_type.is_constant) {
- _set_error("Can't assign a new value to a constant.", op->line);
- return;
- }
- }
-
- DataType rh_type;
- if (op->op != OperatorNode::OP_ASSIGN) {
- // Validate operation
- DataType arg_type = _reduce_node_type(op->arguments[1]);
- if (!arg_type.has_type) {
- _mark_line_as_unsafe(op->line);
- break;
- }
-
- Variant::Operator oper = _get_variant_operation(op->op);
- bool valid = false;
- rh_type = _get_operation_type(oper, lh_type, arg_type, valid);
+void GDScriptParser::TreePrinter::print_enum(EnumNode *p_enum) {
+ push_text("Enum ");
+ if (p_enum->identifier != nullptr) {
+ print_identifier(p_enum->identifier);
+ } else {
+ push_text("<unnamed>");
+ }
- if (check_types && !valid) {
- _set_error("Invalid operand types (\"" + lh_type.to_string() + "\" and \"" + arg_type.to_string() +
- "\") to assignment operator \"" + Variant::get_operator_name(oper) + "\".",
- op->line);
- return;
- }
- } else {
- rh_type = _reduce_node_type(op->arguments[1]);
- }
-#ifdef DEBUG_ENABLED
- if (rh_type.has_type && rh_type.kind == DataType::BUILTIN && rh_type.builtin_type == Variant::NIL) {
- if (op->arguments[1]->type == Node::TYPE_OPERATOR) {
- OperatorNode *call = static_cast<OperatorNode *>(op->arguments[1]);
- if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) {
- _add_warning(GDScriptWarning::VOID_ASSIGNMENT, op->line, _find_function_name(call));
- }
- }
- }
- if (lh_type.has_type && rh_type.may_yield && op->arguments[1]->type == Node::TYPE_OPERATOR) {
- _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, op->line, _find_function_name(static_cast<OperatorNode *>(op->arguments[1])));
- }
+ push_line(" {");
+ increase_indent();
+ for (int i = 0; i < p_enum->values.size(); i++) {
+ const EnumNode::Value &item = p_enum->values[i];
+ print_identifier(item.identifier);
+ push_text(" = ");
+ push_text(itos(item.value));
+ push_line(" ,");
+ }
+ decrease_indent();
+ push_line("}");
+}
-#endif // DEBUG_ENABLED
- bool type_match = lh_type.has_type && rh_type.has_type;
- if (check_types && !_is_type_compatible(lh_type, rh_type)) {
- type_match = false;
+void GDScriptParser::TreePrinter::print_for(ForNode *p_for) {
+ push_text("For ");
+ print_identifier(p_for->variable);
+ push_text(" IN ");
+ print_expression(p_for->list);
+ push_line(" :");
- // Try supertype test
- if (_is_type_compatible(rh_type, lh_type)) {
- _mark_line_as_unsafe(op->line);
- } else {
- // Try implicit conversion
- if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) {
- _set_error("The assigned value's type (" + rh_type.to_string() + ") doesn't match the variable's type (" +
- lh_type.to_string() + ").",
- op->line);
- return;
- }
- if (op->op == OperatorNode::OP_ASSIGN) {
- // Replace assignment with implicit conversion
- BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
- convert->line = op->line;
- convert->function = GDScriptFunctions::TYPE_CONVERT;
-
- ConstantNode *tgt_type = alloc_node<ConstantNode>();
- tgt_type->line = op->line;
- tgt_type->value = (int)lh_type.builtin_type;
- tgt_type->datatype = _type_from_variant(tgt_type->value);
-
- OperatorNode *convert_call = alloc_node<OperatorNode>();
- convert_call->line = op->line;
- convert_call->op = OperatorNode::OP_CALL;
- convert_call->arguments.push_back(convert);
- convert_call->arguments.push_back(op->arguments[1]);
- convert_call->arguments.push_back(tgt_type);
-
- op->arguments.write[1] = convert_call;
-
- type_match = true; // Since we are converting, the type is matching
- }
-#ifdef DEBUG_ENABLED
- if (lh_type.builtin_type == Variant::INT && rh_type.builtin_type == Variant::FLOAT) {
- _add_warning(GDScriptWarning::NARROWING_CONVERSION, op->line);
- }
-#endif // DEBUG_ENABLED
- }
- }
-#ifdef DEBUG_ENABLED
- if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) {
- _mark_line_as_unsafe(op->line);
- }
-#endif // DEBUG_ENABLED
- op->datatype.has_type = type_match;
- } break;
- case OperatorNode::OP_CALL:
- case OperatorNode::OP_PARENT_CALL: {
- _mark_line_as_safe(op->line);
- DataType func_type = _reduce_function_call_type(op);
-#ifdef DEBUG_ENABLED
- if (func_type.has_type && (func_type.kind != DataType::BUILTIN || func_type.builtin_type != Variant::NIL)) {
- // Figure out function name for warning
- String func_name = _find_function_name(op);
- if (func_name.empty()) {
- func_name = "<undetected name>";
- }
- _add_warning(GDScriptWarning::RETURN_VALUE_DISCARDED, op->line, func_name);
- }
-#endif // DEBUG_ENABLED
- if (error_set) {
- return;
- }
- } break;
- case OperatorNode::OP_YIELD: {
- _mark_line_as_safe(op->line);
- _reduce_node_type(op);
- } break;
- default: {
- _mark_line_as_safe(op->line);
- _reduce_node_type(op); // Test for safety anyway
-#ifdef DEBUG_ENABLED
- if (op->op == OperatorNode::OP_TERNARY_IF) {
- _add_warning(GDScriptWarning::STANDALONE_TERNARY, statement->line);
- } else {
- _add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line);
- }
-#endif // DEBUG_ENABLED
- }
- }
- } break;
- case Node::TYPE_CONTROL_FLOW: {
- ControlFlowNode *cf = static_cast<ControlFlowNode *>(statement);
- _mark_line_as_safe(cf->line);
-
- switch (cf->cf_type) {
- case ControlFlowNode::CF_RETURN: {
- DataType function_type = current_function->get_datatype();
-
- DataType ret_type;
- if (cf->arguments.size() > 0) {
- ret_type = _reduce_node_type(cf->arguments[0]);
- if (error_set) {
- return;
- }
- }
+ increase_indent();
- if (!function_type.has_type) {
- break;
- }
+ print_suite(p_for->loop);
- if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) {
- // Return void, should not have arguments
- if (cf->arguments.size() > 0) {
- _set_error("A void function cannot return a value.", cf->line, cf->column);
- return;
- }
- } else {
- // Return something, cannot be empty
- if (cf->arguments.size() == 0) {
- _set_error("A non-void function must return a value.", cf->line, cf->column);
- return;
- }
+ decrease_indent();
+}
- if (!_is_type_compatible(function_type, ret_type)) {
- _set_error("The returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" +
- function_type.to_string() + ").",
- cf->line, cf->column);
- return;
- }
- }
- } break;
- case ControlFlowNode::CF_MATCH: {
- MatchNode *match_node = cf->match;
- _transform_match_statment(match_node);
- } break;
- default: {
- if (cf->body_else) {
- _mark_line_as_safe(cf->body_else->line);
- }
- for (int i = 0; i < cf->arguments.size(); i++) {
- _reduce_node_type(cf->arguments[i]);
- }
- } break;
- }
- } break;
- case Node::TYPE_CONSTANT: {
- ConstantNode *cn = static_cast<ConstantNode *>(statement);
- // Strings are fine since they can be multiline comments
- if (cn->value.get_type() == Variant::STRING) {
- break;
- }
- [[fallthrough]];
- }
- default: {
- _mark_line_as_safe(statement->line);
- _reduce_node_type(statement); // Test for safety anyway
-#ifdef DEBUG_ENABLED
- _add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line);
-#endif // DEBUG_ENABLED
- }
- }
+void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function) {
+ for (const List<AnnotationNode *>::Element *E = p_function->annotations.front(); E != nullptr; E = E->next()) {
+ print_annotation(E->get());
}
-
- // Parse sub blocks
- for (int i = 0; i < p_block->sub_blocks.size(); i++) {
- current_block = p_block->sub_blocks[i];
- _check_block_types(current_block);
- current_block = p_block;
- if (error_set) {
- return;
+ push_text("Function ");
+ print_identifier(p_function->identifier);
+ push_text("( ");
+ for (int i = 0; i < p_function->parameters.size(); i++) {
+ if (i > 0) {
+ push_text(" , ");
}
+ print_parameter(p_function->parameters[i]);
}
+ push_line(" ) :");
+ increase_indent();
+ print_suite(p_function->body);
+ decrease_indent();
+}
-#ifdef DEBUG_ENABLED
- // Warnings check
- for (Map<StringName, LocalVarNode *>::Element *E = p_block->variables.front(); E; E = E->next()) {
- LocalVarNode *lv = E->get();
- if (!lv->name.operator String().begins_with("_")) {
- if (lv->usages == 0) {
- _add_warning(GDScriptWarning::UNUSED_VARIABLE, lv->line, lv->name);
- } else if (lv->assignments == 0) {
- _add_warning(GDScriptWarning::UNASSIGNED_VARIABLE, lv->line, lv->name);
+void GDScriptParser::TreePrinter::print_get_node(GetNodeNode *p_get_node) {
+ push_text("$");
+ if (p_get_node->string != nullptr) {
+ print_literal(p_get_node->string);
+ } else {
+ for (int i = 0; i < p_get_node->chain.size(); i++) {
+ if (i > 0) {
+ push_text("/");
}
+ print_identifier(p_get_node->chain[i]);
}
}
-#endif // DEBUG_ENABLED
}
-void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column) {
- if (error_set) {
- return; //allow no further errors
- }
-
- error = p_error;
- error_line = p_line < 0 ? tokenizer->get_token_line() : p_line;
- error_column = p_column < 0 ? tokenizer->get_token_column() : p_column;
- error_set = true;
+void GDScriptParser::TreePrinter::print_identifier(IdentifierNode *p_identifier) {
+ push_text(p_identifier->name);
}
-#ifdef DEBUG_ENABLED
-void GDScriptParser::_add_warning(int p_code, int p_line, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) {
- Vector<String> symbols;
- if (!p_symbol1.empty()) {
- symbols.push_back(p_symbol1);
- }
- if (!p_symbol2.empty()) {
- symbols.push_back(p_symbol2);
- }
- if (!p_symbol3.empty()) {
- symbols.push_back(p_symbol3);
- }
- if (!p_symbol4.empty()) {
- symbols.push_back(p_symbol4);
+void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) {
+ if (p_is_elif) {
+ push_text("Elif ");
+ } else {
+ push_text("If ");
}
- _add_warning(p_code, p_line, symbols);
-}
+ print_expression(p_if->condition);
+ push_line(" :");
-void GDScriptParser::_add_warning(int p_code, int p_line, const Vector<String> &p_symbols) {
- if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && base_path.begins_with("res://addons/")) {
- return;
- }
- if (tokenizer->is_ignoring_warnings() || !GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) {
- return;
- }
- String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower();
- if (tokenizer->get_warning_global_skips().has(warn_name)) {
- return;
- }
- if (!GLOBAL_GET("debug/gdscript/warnings/" + warn_name)) {
- return;
- }
+ increase_indent();
+ print_suite(p_if->true_block);
+ decrease_indent();
- GDScriptWarning warn;
- warn.code = (GDScriptWarning::Code)p_code;
- warn.symbols = p_symbols;
- warn.line = p_line == -1 ? tokenizer->get_token_line() : p_line;
+ // FIXME: Properly detect "elif" blocks.
+ if (p_if->false_block != nullptr) {
+ push_line("Else :");
+ increase_indent();
+ print_suite(p_if->false_block);
+ decrease_indent();
+ }
+}
- List<GDScriptWarning>::Element *before = nullptr;
- for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) {
- if (E->get().line > warn.line) {
+void GDScriptParser::TreePrinter::print_literal(LiteralNode *p_literal) {
+ // Prefix for string types.
+ switch (p_literal->value.get_type()) {
+ case Variant::NODE_PATH:
+ push_text("^\"");
+ break;
+ case Variant::STRING:
+ push_text("\"");
+ break;
+ case Variant::STRING_NAME:
+ push_text("&\"");
+ break;
+ default:
break;
- }
- before = E;
}
- if (before) {
- warnings.insert_after(before, warn);
- } else {
- warnings.push_front(warn);
+ push_text(p_literal->value);
+ // Suffix for string types.
+ switch (p_literal->value.get_type()) {
+ case Variant::NODE_PATH:
+ case Variant::STRING:
+ case Variant::STRING_NAME:
+ push_text("\"");
+ break;
+ default:
+ break;
}
}
-#endif // DEBUG_ENABLED
-
-String GDScriptParser::get_error() const {
- return error;
-}
-int GDScriptParser::get_error_line() const {
- return error_line;
-}
+void GDScriptParser::TreePrinter::print_match(MatchNode *p_match) {
+ push_text("Match ");
+ print_expression(p_match->test);
+ push_line(" :");
-int GDScriptParser::get_error_column() const {
- return error_column;
-}
-
-bool GDScriptParser::has_error() const {
- return error_set;
+ increase_indent();
+ for (int i = 0; i < p_match->branches.size(); i++) {
+ print_match_branch(p_match->branches[i]);
+ }
+ decrease_indent();
}
-Error GDScriptParser::_parse(const String &p_base_path) {
- base_path = p_base_path;
-
- //assume class
- ClassNode *main_class = alloc_node<ClassNode>();
- main_class->initializer = alloc_node<BlockNode>();
- main_class->initializer->parent_class = main_class;
- main_class->ready = alloc_node<BlockNode>();
- main_class->ready->parent_class = main_class;
- current_class = main_class;
-
- _parse_class(main_class);
-
- if (tokenizer->get_token() == GDScriptTokenizer::TK_ERROR) {
- error_set = false;
- _set_error("Parse error: " + tokenizer->get_token_error());
+void GDScriptParser::TreePrinter::print_match_branch(MatchBranchNode *p_match_branch) {
+ for (int i = 0; i < p_match_branch->patterns.size(); i++) {
+ if (i > 0) {
+ push_text(" , ");
+ }
+ print_match_pattern(p_match_branch->patterns[i]);
}
- bool for_completion_error_set = false;
- if (error_set && for_completion) {
- for_completion_error_set = true;
- error_set = false;
- }
+ push_line(" :");
- if (error_set) {
- return ERR_PARSE_ERROR;
- }
+ increase_indent();
+ print_suite(p_match_branch->block);
+ decrease_indent();
+}
- if (dependencies_only) {
- return OK;
+void GDScriptParser::TreePrinter::print_match_pattern(PatternNode *p_match_pattern) {
+ switch (p_match_pattern->pattern_type) {
+ case PatternNode::PT_LITERAL:
+ print_literal(p_match_pattern->literal);
+ break;
+ case PatternNode::PT_WILDCARD:
+ push_text("_");
+ break;
+ case PatternNode::PT_REST:
+ push_text("..");
+ break;
+ case PatternNode::PT_BIND:
+ push_text("Var ");
+ print_identifier(p_match_pattern->bind);
+ break;
+ case PatternNode::PT_EXPRESSION:
+ print_expression(p_match_pattern->expression);
+ break;
+ case PatternNode::PT_ARRAY:
+ push_text("[ ");
+ for (int i = 0; i < p_match_pattern->array.size(); i++) {
+ if (i > 0) {
+ push_text(" , ");
+ }
+ print_match_pattern(p_match_pattern->array[i]);
+ }
+ push_text(" ]");
+ break;
+ case PatternNode::PT_DICTIONARY:
+ push_text("{ ");
+ for (int i = 0; i < p_match_pattern->dictionary.size(); i++) {
+ if (i > 0) {
+ push_text(" , ");
+ }
+ if (p_match_pattern->dictionary[i].key != nullptr) {
+ // Key can be null for rest pattern.
+ print_expression(p_match_pattern->dictionary[i].key);
+ push_text(" : ");
+ }
+ print_match_pattern(p_match_pattern->dictionary[i].value_pattern);
+ }
+ push_text(" }");
+ break;
}
+}
- _determine_inheritance(main_class);
-
- if (error_set) {
- return ERR_PARSE_ERROR;
+void GDScriptParser::TreePrinter::print_parameter(ParameterNode *p_parameter) {
+ print_identifier(p_parameter->identifier);
+ if (p_parameter->datatype_specifier != nullptr) {
+ push_text(" : ");
+ print_type(p_parameter->datatype_specifier);
}
-
- current_class = main_class;
- current_function = nullptr;
- current_block = nullptr;
-
- if (for_completion) {
- check_types = false;
+ if (p_parameter->default_value != nullptr) {
+ push_text(" = ");
+ print_expression(p_parameter->default_value);
}
+}
- // Resolve all class-level stuff before getting into function blocks
- _check_class_level_types(main_class);
+void GDScriptParser::TreePrinter::print_preload(PreloadNode *p_preload) {
+ push_text(R"(Preload ( ")");
+ push_text(p_preload->resolved_path);
+ push_text(R"(" )");
+}
- if (error_set) {
- return ERR_PARSE_ERROR;
+void GDScriptParser::TreePrinter::print_return(ReturnNode *p_return) {
+ push_text("Return");
+ if (p_return->return_value != nullptr) {
+ push_text(" ");
+ print_expression(p_return->return_value);
}
+ push_line();
+}
- // Resolve the function blocks
- _check_class_blocks_types(main_class);
-
- if (for_completion_error_set) {
- error_set = true;
+void GDScriptParser::TreePrinter::print_self(SelfNode *p_self) {
+ push_text("Self(");
+ if (p_self->current_class->identifier != nullptr) {
+ print_identifier(p_self->current_class->identifier);
+ } else {
+ push_text("<main class>");
}
+ push_text(")");
+}
- if (error_set) {
- return ERR_PARSE_ERROR;
+void GDScriptParser::TreePrinter::print_signal(SignalNode *p_signal) {
+ push_text("Signal ");
+ print_identifier(p_signal->identifier);
+ push_text("( ");
+ for (int i = 0; i < p_signal->parameters.size(); i++) {
+ print_parameter(p_signal->parameters[i]);
}
+ push_line(" )");
+}
-#ifdef DEBUG_ENABLED
-
- // Resolve warning ignores
- Vector<Pair<int, String>> warning_skips = tokenizer->get_warning_skips();
- bool warning_is_error = GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors").booleanize();
- for (List<GDScriptWarning>::Element *E = warnings.front(); E;) {
- GDScriptWarning &w = E->get();
- int skip_index = -1;
- for (int i = 0; i < warning_skips.size(); i++) {
- if (warning_skips[i].first >= w.line) {
- break;
- }
- skip_index = i;
- }
- List<GDScriptWarning>::Element *next = E->next();
- bool erase = false;
- if (skip_index != -1) {
- if (warning_skips[skip_index].second == GDScriptWarning::get_name_from_code(w.code).to_lower()) {
- erase = true;
- }
- warning_skips.remove(skip_index);
- }
- if (erase) {
- warnings.erase(E);
- } else if (warning_is_error) {
- _set_error(w.get_message() + " (warning treated as error)", w.line);
- return ERR_PARSE_ERROR;
- }
- E = next;
+void GDScriptParser::TreePrinter::print_subscript(SubscriptNode *p_subscript) {
+ print_expression(p_subscript->base);
+ if (p_subscript->is_attribute) {
+ push_text(".");
+ print_identifier(p_subscript->attribute);
+ } else {
+ push_text("[ ");
+ print_expression(p_subscript->index);
+ push_text(" ]");
}
-#endif // DEBUG_ENABLED
-
- return OK;
}
-Error GDScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path, const String &p_self_path) {
- clear();
-
- self_path = p_self_path;
- GDScriptTokenizerBuffer *tb = memnew(GDScriptTokenizerBuffer);
- tb->set_code_buffer(p_bytecode);
- tokenizer = tb;
- Error ret = _parse(p_base_path);
- memdelete(tb);
- tokenizer = nullptr;
- return ret;
+void GDScriptParser::TreePrinter::print_statement(Node *p_statement) {
+ switch (p_statement->type) {
+ case Node::ASSERT:
+ print_assert(static_cast<AssertNode *>(p_statement));
+ break;
+ case Node::VARIABLE:
+ print_variable(static_cast<VariableNode *>(p_statement));
+ break;
+ case Node::CONSTANT:
+ print_constant(static_cast<ConstantNode *>(p_statement));
+ break;
+ case Node::IF:
+ print_if(static_cast<IfNode *>(p_statement));
+ break;
+ case Node::FOR:
+ print_for(static_cast<ForNode *>(p_statement));
+ break;
+ case Node::WHILE:
+ print_while(static_cast<WhileNode *>(p_statement));
+ break;
+ case Node::MATCH:
+ print_match(static_cast<MatchNode *>(p_statement));
+ break;
+ case Node::RETURN:
+ print_return(static_cast<ReturnNode *>(p_statement));
+ break;
+ case Node::BREAK:
+ push_line("Break");
+ break;
+ case Node::CONTINUE:
+ push_line("Continue");
+ break;
+ case Node::PASS:
+ push_line("Pass");
+ break;
+ case Node::BREAKPOINT:
+ push_line("Breakpoint");
+ break;
+ case Node::ASSIGNMENT:
+ print_assignment(static_cast<AssignmentNode *>(p_statement));
+ break;
+ default:
+ if (p_statement->is_expression()) {
+ print_expression(static_cast<ExpressionNode *>(p_statement));
+ push_line();
+ } else {
+ push_line(vformat("<unknown statement %d>", p_statement->type));
+ }
+ break;
+ }
}
-Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion, Set<int> *r_safe_lines, bool p_dependencies_only) {
- clear();
-
- self_path = p_self_path;
- GDScriptTokenizerText *tt = memnew(GDScriptTokenizerText);
- tt->set_code(p_code);
-
- validating = p_just_validate;
- for_completion = p_for_completion;
- dependencies_only = p_dependencies_only;
-#ifdef DEBUG_ENABLED
- safe_lines = r_safe_lines;
-#endif // DEBUG_ENABLED
- tokenizer = tt;
- Error ret = _parse(p_base_path);
- memdelete(tt);
- tokenizer = nullptr;
- return ret;
+void GDScriptParser::TreePrinter::print_suite(SuiteNode *p_suite) {
+ for (int i = 0; i < p_suite->statements.size(); i++) {
+ print_statement(p_suite->statements[i]);
+ }
}
-bool GDScriptParser::is_tool_script() const {
- return (head && head->type == Node::TYPE_CLASS && static_cast<const ClassNode *>(head)->tool);
+void GDScriptParser::TreePrinter::print_ternary_op(TernaryOpNode *p_ternary_op) {
+ // Surround in parenthesis for disambiguation.
+ push_text("(");
+ print_expression(p_ternary_op->true_expr);
+ push_text(") IF (");
+ print_expression(p_ternary_op->condition);
+ push_text(") ELSE (");
+ print_expression(p_ternary_op->false_expr);
+ push_text(")");
}
-const GDScriptParser::Node *GDScriptParser::get_parse_tree() const {
- return head;
+void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) {
+ if (p_type->type_chain.empty()) {
+ push_text("Void");
+ } else {
+ for (int i = 0; i < p_type->type_chain.size(); i++) {
+ if (i > 0) {
+ push_text(".");
+ }
+ print_identifier(p_type->type_chain[i]);
+ }
+ }
}
-void GDScriptParser::clear() {
- while (list) {
- Node *l = list;
- list = list->next;
- memdelete(l);
+void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) {
+ // Surround in parenthesis for disambiguation.
+ push_text("(");
+ switch (p_unary_op->operation) {
+ case UnaryOpNode::OP_POSITIVE:
+ push_text("+");
+ break;
+ case UnaryOpNode::OP_NEGATIVE:
+ push_text("-");
+ break;
+ case UnaryOpNode::OP_LOGIC_NOT:
+ push_text("NOT");
+ break;
+ case UnaryOpNode::OP_COMPLEMENT:
+ push_text("~");
+ break;
}
-
- head = nullptr;
- list = nullptr;
-
- completion_type = COMPLETION_NONE;
- completion_node = nullptr;
- completion_class = nullptr;
- completion_function = nullptr;
- completion_block = nullptr;
- current_block = nullptr;
- current_class = nullptr;
-
- completion_found = false;
- rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
-
- current_function = nullptr;
-
- validating = false;
- for_completion = false;
- error_set = false;
- indent_level.clear();
- indent_level.push_back(IndentLevel(0, 0));
- error_line = 0;
- error_column = 0;
- pending_newline = -1;
- parenthesis = 0;
- current_export.type = Variant::NIL;
- check_types = true;
- dependencies_only = false;
- dependencies.clear();
- error = "";
-#ifdef DEBUG_ENABLED
- safe_lines = nullptr;
-#endif // DEBUG_ENABLED
+ print_expression(p_unary_op->operand);
+ // Surround in parenthesis for disambiguation.
+ push_text(")");
}
-GDScriptParser::CompletionType GDScriptParser::get_completion_type() {
- return completion_type;
-}
+void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {
+ for (const List<AnnotationNode *>::Element *E = p_variable->annotations.front(); E != nullptr; E = E->next()) {
+ print_annotation(E->get());
+ }
-StringName GDScriptParser::get_completion_cursor() {
- return completion_cursor;
-}
+ push_text("Variable ");
+ print_identifier(p_variable->identifier);
-int GDScriptParser::get_completion_line() {
- return completion_line;
-}
+ push_text(" : ");
+ if (p_variable->datatype_specifier != nullptr) {
+ print_type(p_variable->datatype_specifier);
+ } else if (p_variable->infer_datatype) {
+ push_text("<inferred type>");
+ } else {
+ push_text("Variant");
+ }
-Variant::Type GDScriptParser::get_completion_built_in_constant() {
- return completion_built_in_constant;
-}
+ increase_indent();
-GDScriptParser::Node *GDScriptParser::get_completion_node() {
- return completion_node;
-}
+ push_line();
+ push_text("= ");
+ if (p_variable->initializer == nullptr) {
+ push_text("<default value>");
+ } else {
+ print_expression(p_variable->initializer);
+ }
+ push_line();
+
+ if (p_variable->property != VariableNode::PROP_NONE) {
+ if (p_variable->getter != nullptr) {
+ push_text("Get");
+ if (p_variable->property == VariableNode::PROP_INLINE) {
+ push_line(":");
+ increase_indent();
+ print_suite(p_variable->getter);
+ decrease_indent();
+ } else {
+ push_line(" =");
+ increase_indent();
+ print_identifier(p_variable->getter_pointer);
+ push_line();
+ decrease_indent();
+ }
+ }
+ if (p_variable->setter != nullptr) {
+ push_text("Set (");
+ if (p_variable->property == VariableNode::PROP_INLINE) {
+ if (p_variable->setter_parameter != nullptr) {
+ print_identifier(p_variable->setter_parameter);
+ } else {
+ push_text("<missing>");
+ }
+ push_line("):");
+ increase_indent();
+ print_suite(p_variable->setter);
+ decrease_indent();
+ } else {
+ push_line(" =");
+ increase_indent();
+ print_identifier(p_variable->setter_pointer);
+ push_line();
+ decrease_indent();
+ }
+ }
+ }
-GDScriptParser::BlockNode *GDScriptParser::get_completion_block() {
- return completion_block;
+ decrease_indent();
+ push_line();
}
-GDScriptParser::ClassNode *GDScriptParser::get_completion_class() {
- return completion_class;
-}
+void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) {
+ push_text("While ");
+ print_expression(p_while->condition);
+ push_line(" :");
-GDScriptParser::FunctionNode *GDScriptParser::get_completion_function() {
- return completion_function;
+ increase_indent();
+ print_suite(p_while->loop);
+ decrease_indent();
}
-int GDScriptParser::get_completion_argument_index() {
- return completion_argument;
-}
+void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) {
+ ERR_FAIL_COND_MSG(p_parser.get_tree() == nullptr, "Parse the code before printing the parse tree.");
-bool GDScriptParser::get_completion_identifier_is_function() {
- return completion_ident_is_call;
-}
+ if (p_parser.is_tool()) {
+ push_line("@tool");
+ }
+ if (!p_parser.get_tree()->icon_path.empty()) {
+ push_text(R"(@icon (")");
+ push_text(p_parser.get_tree()->icon_path);
+ push_line("\")");
+ }
+ print_class(p_parser.get_tree());
-GDScriptParser::GDScriptParser() {
- head = nullptr;
- list = nullptr;
- tokenizer = nullptr;
- pending_newline = -1;
- clear();
+ print_line(printed);
}
-GDScriptParser::~GDScriptParser() {
- clear();
-}
+#endif // DEBUG_ENABLED
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 7dedb6d6f9..a741ae0cc7 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -31,665 +31,1310 @@
#ifndef GDSCRIPT_PARSER_H
#define GDSCRIPT_PARSER_H
+#include "core/hash_map.h"
+#include "core/io/multiplayer_api.h"
+#include "core/list.h"
#include "core/map.h"
-#include "core/object.h"
+#include "core/reference.h"
+#include "core/resource.h"
#include "core/script_language.h"
+#include "core/string_name.h"
+#include "core/ustring.h"
+#include "core/variant.h"
+#include "core/vector.h"
+#include "gdscript_cache.h"
#include "gdscript_functions.h"
#include "gdscript_tokenizer.h"
-struct GDScriptDataType;
-struct GDScriptWarning;
+#ifdef DEBUG_ENABLED
+#include "core/string_builder.h"
+#include "gdscript_warning.h"
+#endif // DEBUG_ENABLED
class GDScriptParser {
+ struct AnnotationInfo;
+
public:
+ // Forward-declare all parser nodes, to avoid ordering issues.
+ struct AnnotationNode;
+ struct ArrayNode;
+ struct AssertNode;
+ struct AssignmentNode;
+ struct AwaitNode;
+ struct BinaryOpNode;
+ struct BreakNode;
+ struct BreakpointNode;
+ struct CallNode;
+ struct CastNode;
struct ClassNode;
+ struct ConstantNode;
+ struct ContinueNode;
+ struct DictionaryNode;
+ struct EnumNode;
+ struct ExpressionNode;
+ struct ForNode;
+ struct FunctionNode;
+ struct GetNodeNode;
+ struct IdentifierNode;
+ struct IfNode;
+ struct LiteralNode;
+ struct MatchNode;
+ struct MatchBranchNode;
+ struct ParameterNode;
+ struct PassNode;
+ struct PatternNode;
+ struct PreloadNode;
+ struct ReturnNode;
+ struct SelfNode;
+ struct SignalNode;
+ struct SubscriptNode;
+ struct SuiteNode;
+ struct TernaryOpNode;
+ struct TypeNode;
+ struct UnaryOpNode;
+ struct VariableNode;
+ struct WhileNode;
struct DataType {
enum Kind {
BUILTIN,
NATIVE,
SCRIPT,
- GDSCRIPT,
- CLASS,
- UNRESOLVED
+ CLASS, // GDScript.
+ ENUM, // Full enumeration.
+ ENUM_VALUE, // Value from enumeration.
+ VARIANT, // Can be any type.
+ UNRESOLVED,
+ // TODO: Enum
};
-
Kind kind = UNRESOLVED;
- bool has_type = false;
+ enum TypeSource {
+ UNDETECTED, // Can be any type.
+ INFERRED, // Has inferred type, but still dynamic.
+ ANNOTATED_EXPLICIT, // Has a specific type annotated.
+ ANNOTATED_INFERRED, // Has a static type but comes from the assigned value.
+ };
+ TypeSource type_source = UNDETECTED;
+
bool is_constant = false;
- bool is_meta_type = false; // Whether the value can be used as a type
- bool infer_type = false;
- bool may_yield = false; // For function calls
+ bool is_meta_type = false;
+ bool is_coroutine = false; // For function calls.
Variant::Type builtin_type = Variant::NIL;
StringName native_type;
+ StringName enum_type; // Enum name or the value name in an enum.
Ref<Script> script_type;
+ String script_path;
ClassNode *class_type = nullptr;
+ MethodInfo method_info; // For callable/signals.
+ HashMap<StringName, int> enum_values; // For enums.
+
+ _FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; }
+ _FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; }
+ _FORCE_INLINE_ bool is_variant() const { return kind == VARIANT || kind == UNRESOLVED; }
+ _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; }
String to_string() const;
- bool operator==(const DataType &other) const {
- if (!has_type || !other.has_type) {
- return true; // Can be considered equal for parsing purpose
+ bool operator==(const DataType &p_other) const {
+ if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) {
+ return true; // Can be consireded equal for parsing purposes.
+ }
+
+ if (type_source == INFERRED || p_other.type_source == INFERRED) {
+ return true; // Can be consireded equal for parsing purposes.
}
- if (kind != other.kind) {
+
+ if (kind != p_other.kind) {
return false;
}
+
switch (kind) {
- case BUILTIN: {
- return builtin_type == other.builtin_type;
- } break;
- case NATIVE: {
- return native_type == other.native_type;
- } break;
- case GDSCRIPT:
- case SCRIPT: {
- return script_type == other.script_type;
- } break;
- case CLASS: {
- return class_type == other.class_type;
- } break;
- case UNRESOLVED: {
- } break;
+ case VARIANT:
+ return true; // All variants are the same.
+ case BUILTIN:
+ return builtin_type == p_other.builtin_type;
+ case NATIVE:
+ case ENUM:
+ return native_type == p_other.native_type;
+ case ENUM_VALUE:
+ return native_type == p_other.native_type && enum_type == p_other.enum_type;
+ case SCRIPT:
+ return script_type == p_other.script_type;
+ case CLASS:
+ return class_type == p_other.class_type;
+ case UNRESOLVED:
+ break;
}
+
return false;
}
- DataType() {}
+ bool operator!=(const DataType &p_other) const {
+ return !(this->operator==(p_other));
+ }
+ };
+
+ struct ParserError {
+ // TODO: Do I really need a "type"?
+ // enum Type {
+ // NO_ERROR,
+ // EMPTY_FILE,
+ // CLASS_NAME_USED_TWICE,
+ // EXTENDS_USED_TWICE,
+ // EXPECTED_END_STATEMENT,
+ // };
+ // Type type = NO_ERROR;
+ String message;
+ int line = 0, column = 0;
};
struct Node {
enum Type {
- TYPE_CLASS,
- TYPE_FUNCTION,
- TYPE_BUILT_IN_FUNCTION,
- TYPE_BLOCK,
- TYPE_IDENTIFIER,
- TYPE_TYPE,
- TYPE_CONSTANT,
- TYPE_ARRAY,
- TYPE_DICTIONARY,
- TYPE_SELF,
- TYPE_OPERATOR,
- TYPE_CONTROL_FLOW,
- TYPE_LOCAL_VAR,
- TYPE_CAST,
- TYPE_ASSERT,
- TYPE_BREAKPOINT,
- TYPE_NEWLINE,
+ NONE,
+ ANNOTATION,
+ ARRAY,
+ ASSERT,
+ ASSIGNMENT,
+ AWAIT,
+ BINARY_OPERATOR,
+ BREAK,
+ BREAKPOINT,
+ CALL,
+ CAST,
+ CLASS,
+ CONSTANT,
+ CONTINUE,
+ DICTIONARY,
+ ENUM,
+ FOR,
+ FUNCTION,
+ GET_NODE,
+ IDENTIFIER,
+ IF,
+ LITERAL,
+ MATCH,
+ MATCH_BRANCH,
+ PARAMETER,
+ PASS,
+ PATTERN,
+ PRELOAD,
+ RETURN,
+ SELF,
+ SIGNAL,
+ SUBSCRIPT,
+ SUITE,
+ TERNARY_OPERATOR,
+ TYPE,
+ UNARY_OPERATOR,
+ VARIABLE,
+ WHILE,
};
- Node *next;
- int line;
- int column;
- Type type;
+ Type type = NONE;
+ int start_line = 0, end_line = 0;
+ int start_column = 0, end_column = 0;
+ int leftmost_column = 0, rightmost_column = 0;
+ Node *next = nullptr;
+ List<AnnotationNode *> annotations;
+
+ DataType datatype;
- virtual DataType get_datatype() const { return DataType(); }
- virtual void set_datatype(const DataType &p_datatype) {}
+ virtual DataType get_datatype() const { return datatype; }
+ virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+
+ virtual bool is_expression() const { return false; }
virtual ~Node() {}
};
- struct FunctionNode;
- struct BlockNode;
- struct ConstantNode;
- struct LocalVarNode;
- struct OperatorNode;
+ struct ExpressionNode : public Node {
+ // Base type for all expression kinds.
+ bool reduced = false;
+ bool is_constant = false;
+ Variant reduced_value;
- struct ClassNode : public Node {
- bool tool;
+ virtual bool is_expression() const { return true; }
+ virtual ~ExpressionNode() {}
+
+ protected:
+ ExpressionNode() {}
+ };
+
+ struct AnnotationNode : public Node {
StringName name;
- bool extends_used;
- bool classname_used;
- StringName extends_file;
- Vector<StringName> extends_class;
- DataType base_type;
- String icon_path;
+ Vector<ExpressionNode *> arguments;
+ Vector<Variant> resolved_arguments;
- struct Member {
- PropertyInfo _export;
-#ifdef TOOLS_ENABLED
- Variant default_value;
-#endif
- StringName identifier;
- DataType data_type;
- StringName setter;
- StringName getter;
- int line;
- Node *expression;
- OperatorNode *initial_assignment;
- MultiplayerAPI::RPCMode rpc_mode;
- int usages;
- };
+ AnnotationInfo *info = nullptr;
- struct Constant {
- Node *expression;
- DataType type;
- };
+ bool apply(GDScriptParser *p_this, Node *p_target) const;
+ bool applies_to(uint32_t p_target_kinds) const;
- struct Signal {
- StringName name;
- Vector<StringName> arguments;
- int emissions;
- int line;
- };
+ AnnotationNode() {
+ type = ANNOTATION;
+ }
+ };
- Vector<ClassNode *> subclasses;
- Vector<Member> variables;
- Map<StringName, Constant> constant_expressions;
- Vector<FunctionNode *> functions;
- Vector<FunctionNode *> static_functions;
- Vector<Signal> _signals;
- BlockNode *initializer;
- BlockNode *ready;
- ClassNode *owner;
- //Vector<Node*> initializers;
- int end_line;
+ struct ArrayNode : public ExpressionNode {
+ Vector<ExpressionNode *> elements;
- ClassNode() {
- tool = false;
- type = TYPE_CLASS;
- extends_used = false;
- classname_used = false;
- end_line = -1;
- owner = nullptr;
+ ArrayNode() {
+ type = ARRAY;
}
};
- struct FunctionNode : public Node {
- bool _static;
- MultiplayerAPI::RPCMode rpc_mode;
- bool has_yield;
- bool has_unreachable_code;
- StringName name;
- DataType return_type;
- Vector<StringName> arguments;
- Vector<DataType> argument_types;
- Vector<Node *> default_values;
- BlockNode *body;
-#ifdef DEBUG_ENABLED
- Vector<int> arguments_usage;
-#endif // DEBUG_ENABLED
+ struct AssertNode : public Node {
+ ExpressionNode *condition = nullptr;
+ LiteralNode *message = nullptr;
- virtual DataType get_datatype() const { return return_type; }
- virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; }
- int get_required_argument_count() { return arguments.size() - default_values.size(); }
+ AssertNode() {
+ type = ASSERT;
+ }
+ };
- FunctionNode() {
- type = TYPE_FUNCTION;
- _static = false;
- rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
- has_yield = false;
- has_unreachable_code = false;
+ struct AssignmentNode : public ExpressionNode {
+ // Assignment is not really an expression but it's easier to parse as if it were.
+ enum Operation {
+ OP_NONE,
+ OP_ADDITION,
+ OP_SUBTRACTION,
+ OP_MULTIPLICATION,
+ OP_DIVISION,
+ OP_MODULO,
+ OP_BIT_SHIFT_LEFT,
+ OP_BIT_SHIFT_RIGHT,
+ OP_BIT_AND,
+ OP_BIT_OR,
+ OP_BIT_XOR,
+ };
+
+ Operation operation = OP_NONE;
+ Variant::Operator variant_op = Variant::OP_MAX;
+ ExpressionNode *assignee = nullptr;
+ ExpressionNode *assigned_value = nullptr;
+
+ AssignmentNode() {
+ type = ASSIGNMENT;
}
};
- struct BlockNode : public Node {
- ClassNode *parent_class = nullptr;
- BlockNode *parent_block = nullptr;
- List<Node *> statements;
- Map<StringName, LocalVarNode *> variables;
- bool has_return = false;
- bool can_break = false;
- bool can_continue = false;
+ struct AwaitNode : public ExpressionNode {
+ ExpressionNode *to_await = nullptr;
- Node *if_condition = nullptr; //tiny hack to improve code completion on if () blocks
+ AwaitNode() {
+ type = AWAIT;
+ }
+ };
+
+ struct BinaryOpNode : public ExpressionNode {
+ enum OpType {
+ OP_ADDITION,
+ OP_SUBTRACTION,
+ OP_MULTIPLICATION,
+ OP_DIVISION,
+ OP_MODULO,
+ OP_BIT_LEFT_SHIFT,
+ OP_BIT_RIGHT_SHIFT,
+ OP_BIT_AND,
+ OP_BIT_OR,
+ OP_BIT_XOR,
+ OP_LOGIC_AND,
+ OP_LOGIC_OR,
+ OP_TYPE_TEST,
+ OP_CONTENT_TEST,
+ OP_COMP_EQUAL,
+ OP_COMP_NOT_EQUAL,
+ OP_COMP_LESS,
+ OP_COMP_LESS_EQUAL,
+ OP_COMP_GREATER,
+ OP_COMP_GREATER_EQUAL,
+ };
- //the following is useful for code completion
- List<BlockNode *> sub_blocks;
- int end_line = -1;
+ OpType operation;
+ Variant::Operator variant_op = Variant::OP_MAX;
+ ExpressionNode *left_operand = nullptr;
+ ExpressionNode *right_operand = nullptr;
- BlockNode() {
- type = TYPE_BLOCK;
+ BinaryOpNode() {
+ type = BINARY_OPERATOR;
}
};
- struct TypeNode : public Node {
- Variant::Type vtype;
+ struct BreakNode : public Node {
+ BreakNode() {
+ type = BREAK;
+ }
+ };
- TypeNode() {
- type = TYPE_TYPE;
+ struct BreakpointNode : public Node {
+ BreakpointNode() {
+ type = BREAKPOINT;
}
};
- struct BuiltInFunctionNode : public Node {
- GDScriptFunctions::Function function;
+ struct CallNode : public ExpressionNode {
+ ExpressionNode *callee = nullptr;
+ Vector<ExpressionNode *> arguments;
+ StringName function_name;
+ bool is_super = false;
- BuiltInFunctionNode() {
- type = TYPE_BUILT_IN_FUNCTION;
+ CallNode() {
+ type = CALL;
}
};
- struct IdentifierNode : public Node {
- StringName name;
- BlockNode *declared_block = nullptr; // Simplify lookup by checking if it is declared locally
- DataType datatype;
- virtual DataType get_datatype() const { return datatype; }
- virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+ struct CastNode : public ExpressionNode {
+ ExpressionNode *operand = nullptr;
+ TypeNode *cast_type = nullptr;
- IdentifierNode() {
- type = TYPE_IDENTIFIER;
+ CastNode() {
+ type = CAST;
}
};
- struct LocalVarNode : public Node {
- StringName name;
- Node *assign = nullptr;
- OperatorNode *assign_op = nullptr;
- int assignments = 0;
- int usages = 0;
- DataType datatype;
- virtual DataType get_datatype() const { return datatype; }
- virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+ struct EnumNode : public Node {
+ struct Value {
+ IdentifierNode *identifier = nullptr;
+ LiteralNode *custom_value = nullptr;
+ int value = 0;
+ int line = 0;
+ int leftmost_column = 0;
+ int rightmost_column = 0;
+ };
+ IdentifierNode *identifier = nullptr;
+ Vector<Value> values;
+
+ EnumNode() {
+ type = ENUM;
+ }
+ };
+
+ struct ClassNode : public Node {
+ struct Member {
+ enum Type {
+ UNDEFINED,
+ CLASS,
+ CONSTANT,
+ FUNCTION,
+ SIGNAL,
+ VARIABLE,
+ ENUM,
+ ENUM_VALUE, // For unnamed enums.
+ };
+
+ Type type = UNDEFINED;
+
+ union {
+ ClassNode *m_class = nullptr;
+ ConstantNode *constant;
+ FunctionNode *function;
+ SignalNode *signal;
+ VariableNode *variable;
+ EnumNode *m_enum;
+ };
+ EnumNode::Value enum_value;
+
+ String get_type_name() const {
+ switch (type) {
+ case UNDEFINED:
+ return "???";
+ case CLASS:
+ return "class";
+ case CONSTANT:
+ return "constant";
+ case FUNCTION:
+ return "function";
+ case SIGNAL:
+ return "signal";
+ case VARIABLE:
+ return "variable";
+ case ENUM:
+ return "enum";
+ case ENUM_VALUE:
+ return "enum value";
+ }
+ return "";
+ }
+
+ int get_line() const {
+ switch (type) {
+ case CLASS:
+ return m_class->start_line;
+ case CONSTANT:
+ return constant->start_line;
+ case FUNCTION:
+ return function->start_line;
+ case VARIABLE:
+ return variable->start_line;
+ case ENUM_VALUE:
+ return enum_value.line;
+ case ENUM:
+ return m_enum->start_line;
+ case SIGNAL:
+ return signal->start_line;
+ case UNDEFINED:
+ ERR_FAIL_V_MSG(-1, "Reaching undefined member type.");
+ }
+ ERR_FAIL_V_MSG(-1, "Reaching unhandled type.");
+ }
+
+ DataType get_datatype() const {
+ switch (type) {
+ case CLASS:
+ return m_class->get_datatype();
+ case CONSTANT:
+ return constant->get_datatype();
+ case FUNCTION:
+ return function->get_datatype();
+ case VARIABLE:
+ return variable->get_datatype();
+ case ENUM:
+ return m_enum->get_datatype();
+ case ENUM_VALUE: {
+ // Always integer.
+ DataType type;
+ type.type_source = DataType::ANNOTATED_EXPLICIT;
+ type.kind = DataType::BUILTIN;
+ type.builtin_type = Variant::INT;
+ return type;
+ }
+ case SIGNAL: {
+ DataType type;
+ type.type_source = DataType::ANNOTATED_EXPLICIT;
+ type.kind = DataType::BUILTIN;
+ type.builtin_type = Variant::SIGNAL;
+ // TODO: Add parameter info.
+ return type;
+ }
+ case UNDEFINED:
+ return DataType();
+ }
+ ERR_FAIL_V_MSG(DataType(), "Reaching unhandled type.");
+ }
+
+ Member() {}
+
+ Member(ClassNode *p_class) {
+ type = CLASS;
+ m_class = p_class;
+ }
+ Member(ConstantNode *p_constant) {
+ type = CONSTANT;
+ constant = p_constant;
+ }
+ Member(VariableNode *p_variable) {
+ type = VARIABLE;
+ variable = p_variable;
+ }
+ Member(SignalNode *p_signal) {
+ type = SIGNAL;
+ signal = p_signal;
+ }
+ Member(FunctionNode *p_function) {
+ type = FUNCTION;
+ function = p_function;
+ }
+ Member(EnumNode *p_enum) {
+ type = ENUM;
+ m_enum = p_enum;
+ }
+ Member(const EnumNode::Value &p_enum_value) {
+ type = ENUM_VALUE;
+ enum_value = p_enum_value;
+ }
+ };
+
+ IdentifierNode *identifier = nullptr;
+ String icon_path;
+ Vector<Member> members;
+ HashMap<StringName, int> members_indices;
+ ClassNode *outer = nullptr;
+ bool extends_used = false;
+ bool onready_used = false;
+ String extends_path;
+ Vector<StringName> extends; // List for indexing: extends A.B.C
+ DataType base_type;
+ String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project.
+
+ bool resolved_interface = false;
+ bool resolved_body = false;
- LocalVarNode() {
- type = TYPE_LOCAL_VAR;
+ Member get_member(const StringName &p_name) const {
+ return members[members_indices[p_name]];
+ }
+ bool has_member(const StringName &p_name) const {
+ return members_indices.has(p_name);
+ }
+ bool has_function(const StringName &p_name) const {
+ return has_member(p_name) && members[members_indices[p_name]].type == Member::FUNCTION;
+ }
+ template <class T>
+ void add_member(T *p_member_node) {
+ members_indices[p_member_node->identifier->name] = members.size();
+ members.push_back(Member(p_member_node));
+ }
+ void add_member(const EnumNode::Value &p_enum_value) {
+ members_indices[p_enum_value.identifier->name] = members.size();
+ members.push_back(Member(p_enum_value));
+ }
+
+ ClassNode() {
+ type = CLASS;
}
};
struct ConstantNode : public Node {
- Variant value;
- DataType datatype;
- virtual DataType get_datatype() const { return datatype; }
- virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+ IdentifierNode *identifier = nullptr;
+ ExpressionNode *initializer = nullptr;
+ TypeNode *datatype_specifier = nullptr;
+ bool infer_datatype = false;
+ int usages = 0;
ConstantNode() {
- type = TYPE_CONSTANT;
+ type = CONSTANT;
}
};
- struct ArrayNode : public Node {
- Vector<Node *> elements;
- DataType datatype;
- virtual DataType get_datatype() const { return datatype; }
- virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
-
- ArrayNode() {
- type = TYPE_ARRAY;
- datatype.has_type = true;
- datatype.kind = DataType::BUILTIN;
- datatype.builtin_type = Variant::ARRAY;
+ struct ContinueNode : public Node {
+ ContinueNode() {
+ type = CONTINUE;
}
};
- struct DictionaryNode : public Node {
+ struct DictionaryNode : public ExpressionNode {
struct Pair {
- Node *key;
- Node *value;
+ ExpressionNode *key = nullptr;
+ ExpressionNode *value = nullptr;
};
-
Vector<Pair> elements;
- DataType datatype;
- virtual DataType get_datatype() const { return datatype; }
- virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
+
+ enum Style {
+ LUA_TABLE,
+ PYTHON_DICT,
+ };
+ Style style = PYTHON_DICT;
DictionaryNode() {
- type = TYPE_DICTIONARY;
- datatype.has_type = true;
- datatype.kind = DataType::BUILTIN;
- datatype.builtin_type = Variant::DICTIONARY;
+ type = DICTIONARY;
}
};
- struct SelfNode : public Node {
- SelfNode() {
- type = TYPE_SELF;
- }
- };
-
- struct OperatorNode : public Node {
- enum Operator {
- //call/constructor operator
- OP_CALL,
- OP_PARENT_CALL,
- OP_YIELD,
- OP_IS,
- OP_IS_BUILTIN,
- //indexing operator
- OP_INDEX,
- OP_INDEX_NAMED,
- //unary operators
- OP_NEG,
- OP_POS,
- OP_NOT,
- OP_BIT_INVERT,
- //binary operators (in precedence order)
- OP_IN,
- OP_EQUAL,
- OP_NOT_EQUAL,
- OP_LESS,
- OP_LESS_EQUAL,
- OP_GREATER,
- OP_GREATER_EQUAL,
- OP_AND,
- OP_OR,
- OP_ADD,
- OP_SUB,
- OP_MUL,
- OP_DIV,
- OP_MOD,
- OP_SHIFT_LEFT,
- OP_SHIFT_RIGHT,
- OP_INIT_ASSIGN,
- OP_ASSIGN,
- OP_ASSIGN_ADD,
- OP_ASSIGN_SUB,
- OP_ASSIGN_MUL,
- OP_ASSIGN_DIV,
- OP_ASSIGN_MOD,
- OP_ASSIGN_SHIFT_LEFT,
- OP_ASSIGN_SHIFT_RIGHT,
- OP_ASSIGN_BIT_AND,
- OP_ASSIGN_BIT_OR,
- OP_ASSIGN_BIT_XOR,
- OP_BIT_AND,
- OP_BIT_OR,
- OP_BIT_XOR,
- //ternary operators
- OP_TERNARY_IF,
- OP_TERNARY_ELSE,
- };
+ struct ForNode : public Node {
+ IdentifierNode *variable = nullptr;
+ ExpressionNode *list = nullptr;
+ SuiteNode *loop = nullptr;
- Operator op;
+ ForNode() {
+ type = FOR;
+ }
+ };
- Vector<Node *> arguments;
- DataType datatype;
- virtual DataType get_datatype() const { return datatype; }
- virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; }
- OperatorNode() {
- type = TYPE_OPERATOR;
+ struct FunctionNode : public Node {
+ IdentifierNode *identifier = nullptr;
+ Vector<ParameterNode *> parameters;
+ HashMap<StringName, int> parameters_indices;
+ TypeNode *return_type = nullptr;
+ SuiteNode *body = nullptr;
+ bool is_static = false;
+ bool is_coroutine = false;
+ MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+ MethodInfo info;
+
+ bool resolved_signature = false;
+ bool resolved_body = false;
+
+ FunctionNode() {
+ type = FUNCTION;
}
};
- struct PatternNode : public Node {
- enum PatternType {
- PT_CONSTANT,
- PT_BIND,
- PT_DICTIONARY,
- PT_ARRAY,
- PT_IGNORE_REST,
- PT_WILDCARD
+ struct GetNodeNode : public ExpressionNode {
+ LiteralNode *string = nullptr;
+ Vector<IdentifierNode *> chain;
+
+ GetNodeNode() {
+ type = GET_NODE;
+ }
+ };
+
+ struct IdentifierNode : public ExpressionNode {
+ StringName name;
+
+ enum Source {
+ UNDEFINED_SOURCE,
+ FUNCTION_PARAMETER,
+ LOCAL_CONSTANT,
+ LOCAL_VARIABLE,
+ LOCAL_ITERATOR, // `for` loop iterator.
+ LOCAL_BIND, // Pattern bind.
+ MEMBER_VARIABLE,
+ MEMBER_CONSTANT,
};
+ Source source = UNDEFINED_SOURCE;
- PatternType pt_type;
+ union {
+ ParameterNode *parameter_source = nullptr;
+ ConstantNode *constant_source;
+ VariableNode *variable_source;
+ IdentifierNode *bind_source;
+ };
- Node *constant;
- StringName bind;
- Map<ConstantNode *, PatternNode *> dictionary;
- Vector<PatternNode *> array;
+ int usages = 0; // Useful for binds/iterator variable.
+
+ IdentifierNode() {
+ type = IDENTIFIER;
+ }
};
- struct PatternBranchNode : public Node {
- Vector<PatternNode *> patterns;
- BlockNode *body;
+ struct IfNode : public Node {
+ ExpressionNode *condition = nullptr;
+ SuiteNode *true_block = nullptr;
+ SuiteNode *false_block = nullptr;
+
+ IfNode() {
+ type = IF;
+ }
+ };
+
+ struct LiteralNode : public ExpressionNode {
+ Variant value;
+
+ LiteralNode() {
+ type = LITERAL;
+ }
};
struct MatchNode : public Node {
- Node *val_to_match;
- Vector<PatternBranchNode *> branches;
+ ExpressionNode *test = nullptr;
+ Vector<MatchBranchNode *> branches;
- struct CompiledPatternBranch {
- Node *compiled_pattern;
- BlockNode *body;
- };
+ MatchNode() {
+ type = MATCH;
+ }
+ };
+
+ struct MatchBranchNode : public Node {
+ Vector<PatternNode *> patterns;
+ SuiteNode *block;
+ bool has_wildcard = false;
+
+ MatchBranchNode() {
+ type = MATCH_BRANCH;
+ }
+ };
+
+ struct ParameterNode : public Node {
+ IdentifierNode *identifier = nullptr;
+ ExpressionNode *default_value = nullptr;
+ TypeNode *datatype_specifier = nullptr;
+ bool infer_datatype = false;
+ int usages = 0;
- Vector<CompiledPatternBranch> compiled_pattern_branches;
+ ParameterNode() {
+ type = PARAMETER;
+ }
};
- struct ControlFlowNode : public Node {
- enum CFType {
- CF_IF,
- CF_FOR,
- CF_WHILE,
- CF_BREAK,
- CF_CONTINUE,
- CF_RETURN,
- CF_MATCH
+ struct PassNode : public Node {
+ PassNode() {
+ type = PASS;
+ }
+ };
+
+ struct PatternNode : public Node {
+ enum Type {
+ PT_LITERAL,
+ PT_EXPRESSION,
+ PT_BIND,
+ PT_ARRAY,
+ PT_DICTIONARY,
+ PT_REST,
+ PT_WILDCARD,
+ };
+ Type pattern_type = PT_LITERAL;
+
+ union {
+ LiteralNode *literal = nullptr;
+ IdentifierNode *bind;
+ ExpressionNode *expression;
};
+ Vector<PatternNode *> array;
+ bool rest_used = false; // For array/dict patterns.
- CFType cf_type = CF_IF;
- Vector<Node *> arguments;
- BlockNode *body = nullptr;
- BlockNode *body_else = nullptr;
+ struct Pair {
+ ExpressionNode *key = nullptr;
+ PatternNode *value_pattern = nullptr;
+ };
+ Vector<Pair> dictionary;
- MatchNode *match;
+ HashMap<StringName, IdentifierNode *> binds;
- ControlFlowNode *_else; //used for if
+ bool has_bind(const StringName &p_name);
+ IdentifierNode *get_bind(const StringName &p_name);
- ControlFlowNode() {
- type = TYPE_CONTROL_FLOW;
+ PatternNode() {
+ type = PATTERN;
}
};
+ struct PreloadNode : public ExpressionNode {
+ ExpressionNode *path = nullptr;
+ String resolved_path;
+ Ref<Resource> resource;
- struct CastNode : public Node {
- Node *source_node;
- DataType cast_type;
- DataType return_type;
- virtual DataType get_datatype() const { return return_type; }
- virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; }
-
- CastNode() {
- type = TYPE_CAST;
+ PreloadNode() {
+ type = PRELOAD;
}
};
- struct AssertNode : public Node {
- Node *condition = nullptr;
- Node *message = nullptr;
+ struct ReturnNode : public Node {
+ ExpressionNode *return_value = nullptr;
- AssertNode() {
- type = TYPE_ASSERT;
+ ReturnNode() {
+ type = RETURN;
}
};
- struct BreakpointNode : public Node {
- BreakpointNode() {
- type = TYPE_BREAKPOINT;
+ struct SelfNode : public ExpressionNode {
+ ClassNode *current_class = nullptr;
+
+ SelfNode() {
+ type = SELF;
}
};
- struct NewLineNode : public Node {
- NewLineNode() {
- type = TYPE_NEWLINE;
+ struct SignalNode : public Node {
+ IdentifierNode *identifier = nullptr;
+ Vector<ParameterNode *> parameters;
+ HashMap<StringName, int> parameters_indices;
+
+ SignalNode() {
+ type = SIGNAL;
}
};
- struct Expression {
- bool is_op;
+ struct SubscriptNode : public ExpressionNode {
+ ExpressionNode *base = nullptr;
union {
- OperatorNode::Operator op;
- Node *node;
+ ExpressionNode *index = nullptr;
+ IdentifierNode *attribute;
};
- };
- enum CompletionType {
- COMPLETION_NONE,
- COMPLETION_BUILT_IN_TYPE_CONSTANT,
- COMPLETION_GET_NODE,
- COMPLETION_FUNCTION,
- COMPLETION_IDENTIFIER,
- COMPLETION_PARENT_FUNCTION,
- COMPLETION_METHOD,
- COMPLETION_CALL_ARGUMENTS,
- COMPLETION_RESOURCE_PATH,
- COMPLETION_INDEX,
- COMPLETION_VIRTUAL_FUNC,
- COMPLETION_YIELD,
- COMPLETION_ASSIGN,
- COMPLETION_TYPE_HINT,
- COMPLETION_TYPE_HINT_INDEX,
+ bool is_attribute = false;
+
+ SubscriptNode() {
+ type = SUBSCRIPT;
+ }
};
-private:
- GDScriptTokenizer *tokenizer;
+ struct SuiteNode : public Node {
+ SuiteNode *parent_block = nullptr;
+ Vector<Node *> statements;
+ struct Local {
+ enum Type {
+ UNDEFINED,
+ CONSTANT,
+ VARIABLE,
+ PARAMETER,
+ FOR_VARIABLE,
+ PATTERN_BIND,
+ };
+ Type type = UNDEFINED;
+ union {
+ ConstantNode *constant = nullptr;
+ VariableNode *variable;
+ ParameterNode *parameter;
+ IdentifierNode *bind;
+ };
+ StringName name;
- Node *head;
- Node *list;
- template <class T>
- T *alloc_node();
-
- bool validating;
- bool for_completion;
- int parenthesis;
- bool error_set;
- String error;
- int error_line;
- int error_column;
- bool check_types;
- bool dependencies_only;
- List<String> dependencies;
-#ifdef DEBUG_ENABLED
- Set<int> *safe_lines;
-#endif // DEBUG_ENABLED
+ int start_line = 0, end_line = 0;
+ int start_column = 0, end_column = 0;
+ int leftmost_column = 0, rightmost_column = 0;
+
+ DataType get_datatype() const;
+ String get_name() const;
+
+ Local() {}
+ Local(ConstantNode *p_constant) {
+ type = CONSTANT;
+ constant = p_constant;
+ name = p_constant->identifier->name;
+
+ start_line = p_constant->start_line;
+ end_line = p_constant->end_line;
+ start_column = p_constant->start_column;
+ end_column = p_constant->end_column;
+ leftmost_column = p_constant->leftmost_column;
+ rightmost_column = p_constant->rightmost_column;
+ }
+ Local(VariableNode *p_variable) {
+ type = VARIABLE;
+ variable = p_variable;
+ name = p_variable->identifier->name;
+
+ start_line = p_variable->start_line;
+ end_line = p_variable->end_line;
+ start_column = p_variable->start_column;
+ end_column = p_variable->end_column;
+ leftmost_column = p_variable->leftmost_column;
+ rightmost_column = p_variable->rightmost_column;
+ }
+ Local(ParameterNode *p_parameter) {
+ type = PARAMETER;
+ parameter = p_parameter;
+ name = p_parameter->identifier->name;
+
+ start_line = p_parameter->start_line;
+ end_line = p_parameter->end_line;
+ start_column = p_parameter->start_column;
+ end_column = p_parameter->end_column;
+ leftmost_column = p_parameter->leftmost_column;
+ rightmost_column = p_parameter->rightmost_column;
+ }
+ Local(IdentifierNode *p_identifier) {
+ type = FOR_VARIABLE;
+ bind = p_identifier;
+ name = p_identifier->name;
+
+ start_line = p_identifier->start_line;
+ end_line = p_identifier->end_line;
+ start_column = p_identifier->start_column;
+ end_column = p_identifier->end_column;
+ leftmost_column = p_identifier->leftmost_column;
+ rightmost_column = p_identifier->rightmost_column;
+ }
+ };
+ Local empty;
+ Vector<Local> locals;
+ HashMap<StringName, int> locals_indices;
-#ifdef DEBUG_ENABLED
- List<GDScriptWarning> warnings;
-#endif // DEBUG_ENABLED
+ FunctionNode *parent_function = nullptr;
+ ForNode *parent_for = nullptr;
+ IfNode *parent_if = nullptr;
+
+ bool has_return = false;
+ bool has_continue = false;
+ bool has_unreachable_code = false; // Just so warnings aren't given more than once per block.
+
+ bool has_local(const StringName &p_name) const;
+ const Local &get_local(const StringName &p_name) const;
+ template <class T>
+ void add_local(T *p_local) {
+ locals_indices[p_local->identifier->name] = locals.size();
+ locals.push_back(Local(p_local));
+ }
+ void add_local(const Local &p_local) {
+ locals_indices[p_local.name] = locals.size();
+ locals.push_back(p_local);
+ }
- int pending_newline;
+ SuiteNode() {
+ type = SUITE;
+ }
+ };
- struct IndentLevel {
- int indent = 0;
- int tabs = 0;
+ struct TernaryOpNode : public ExpressionNode {
+ // Only one ternary operation exists, so no abstraction here.
+ ExpressionNode *condition = nullptr;
+ ExpressionNode *true_expr = nullptr;
+ ExpressionNode *false_expr = nullptr;
- bool is_mixed(IndentLevel other) {
- return (
- (indent == other.indent && tabs != other.tabs) ||
- (indent > other.indent && tabs < other.tabs) ||
- (indent < other.indent && tabs > other.tabs));
+ TernaryOpNode() {
+ type = TERNARY_OPERATOR;
}
+ };
- IndentLevel() {}
+ struct TypeNode : public Node {
+ Vector<IdentifierNode *> type_chain;
- IndentLevel(int p_indent, int p_tabs) :
- indent(p_indent),
- tabs(p_tabs) {}
+ TypeNode() {
+ type = TYPE;
+ }
};
- List<IndentLevel> indent_level;
+ struct UnaryOpNode : public ExpressionNode {
+ enum OpType {
+ OP_POSITIVE,
+ OP_NEGATIVE,
+ OP_COMPLEMENT,
+ OP_LOGIC_NOT,
+ };
- String base_path;
- String self_path;
+ OpType operation;
+ Variant::Operator variant_op = Variant::OP_MAX;
+ ExpressionNode *operand = nullptr;
- ClassNode *current_class;
- FunctionNode *current_function;
- BlockNode *current_block;
+ UnaryOpNode() {
+ type = UNARY_OPERATOR;
+ }
+ };
- bool _get_completable_identifier(CompletionType p_type, StringName &identifier);
- void _make_completable_call(int p_arg);
+ struct VariableNode : public Node {
+ enum PropertyStyle {
+ PROP_NONE,
+ PROP_INLINE,
+ PROP_SETGET,
+ };
- CompletionType completion_type;
- StringName completion_cursor;
- Variant::Type completion_built_in_constant;
- Node *completion_node;
- ClassNode *completion_class;
- FunctionNode *completion_function;
- BlockNode *completion_block;
- int completion_line;
- int completion_argument;
- bool completion_found;
- bool completion_ident_is_call;
+ IdentifierNode *identifier = nullptr;
+ ExpressionNode *initializer = nullptr;
+ TypeNode *datatype_specifier = nullptr;
+ bool infer_datatype = false;
- PropertyInfo current_export;
+ PropertyStyle property = PROP_NONE;
+ union {
+ SuiteNode *setter = nullptr;
+ IdentifierNode *setter_pointer;
+ };
+ IdentifierNode *setter_parameter = nullptr;
+ union {
+ SuiteNode *getter = nullptr;
+ IdentifierNode *getter_pointer;
+ };
- MultiplayerAPI::RPCMode rpc_mode;
+ bool exported = false;
+ bool onready = false;
+ PropertyInfo export_info;
+ MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+ int assignments = 0;
+ int usages = 0;
- void _set_error(const String &p_error, int p_line = -1, int p_column = -1);
-#ifdef DEBUG_ENABLED
- void _add_warning(int p_code, int p_line = -1, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String());
- void _add_warning(int p_code, int p_line, const Vector<String> &p_symbols);
-#endif // DEBUG_ENABLED
- bool _recover_from_completion();
-
- bool _parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete = false, bool p_parsing_constant = false);
- bool _enter_indent_block(BlockNode *p_block = nullptr);
- bool _parse_newline();
- Node *_parse_expression(Node *p_parent, bool p_static, bool p_allow_assign = false, bool p_parsing_constant = false);
- Node *_reduce_expression(Node *p_node, bool p_to_const = false);
- Node *_parse_and_reduce_expression(Node *p_parent, bool p_static, bool p_reduce_const = false, bool p_allow_assign = false);
- bool _reduce_export_var_type(Variant &p_value, int p_line = 0);
-
- PatternNode *_parse_pattern(bool p_static);
- void _parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static);
- void _transform_match_statment(MatchNode *p_match_statement);
- void _generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings);
-
- void _parse_block(BlockNode *p_block, bool p_static);
- void _parse_extends(ClassNode *p_class);
- void _parse_class(ClassNode *p_class);
- bool _end_statement();
- void _set_end_statement_error(String p_name);
-
- void _determine_inheritance(ClassNode *p_class, bool p_recursive = true);
- bool _parse_type(DataType &r_type, bool p_can_be_void = false);
- DataType _resolve_type(const DataType &p_source, int p_line);
- DataType _type_from_variant(const Variant &p_value) const;
- DataType _type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant = true) const;
- DataType _type_from_gdtype(const GDScriptDataType &p_gdtype) const;
- DataType _get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const;
- Variant::Operator _get_variant_operation(const OperatorNode::Operator &p_op) const;
- bool _get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const;
- bool _get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type, bool *r_is_const = nullptr) const;
- bool _is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion = false) const;
- Node *_get_default_value_for_type(const DataType &p_type, int p_line = -1);
-
- DataType _reduce_node_type(Node *p_node);
- DataType _reduce_function_call_type(const OperatorNode *p_call);
- DataType _reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line, bool p_is_indexing);
- void _check_class_level_types(ClassNode *p_class);
- void _check_class_blocks_types(ClassNode *p_class);
- void _check_function_types(FunctionNode *p_function);
- void _check_block_types(BlockNode *p_block);
- _FORCE_INLINE_ void _mark_line_as_safe(int p_line) const {
-#ifdef DEBUG_ENABLED
- if (safe_lines) {
- safe_lines->insert(p_line);
+ VariableNode() {
+ type = VARIABLE;
}
-#endif // DEBUG_ENABLED
- }
- _FORCE_INLINE_ void _mark_line_as_unsafe(int p_line) const {
-#ifdef DEBUG_ENABLED
- if (safe_lines) {
- safe_lines->erase(p_line);
+ };
+
+ struct WhileNode : public Node {
+ ExpressionNode *condition = nullptr;
+ SuiteNode *loop = nullptr;
+
+ WhileNode() {
+ type = WHILE;
}
-#endif // DEBUG_ENABLED
- }
+ };
- Error _parse(const String &p_base_path);
+ enum CompletionType {
+ COMPLETION_NONE,
+ COMPLETION_ANNOTATION, // Annotation (following @).
+ COMPLETION_ANNOTATION_ARGUMENTS, // Annotation arguments hint.
+ COMPLETION_ASSIGN, // Assignment based on type (e.g. enum values).
+ COMPLETION_ATTRIBUTE, // After id.| to look for members.
+ COMPLETION_ATTRIBUTE_METHOD, // After id.| to look for methods.
+ COMPLETION_BUILT_IN_TYPE_CONSTANT, // Constants inside a built-in type (e.g. Color.blue).
+ COMPLETION_CALL_ARGUMENTS, // Complete with nodes, input actions, enum values (or usual expressions).
+ // TODO: COMPLETION_DECLARATION, // Potential declaration (var, const, func).
+ COMPLETION_GET_NODE, // Get node with $ notation.
+ COMPLETION_IDENTIFIER, // List available identifiers in scope.
+ COMPLETION_INHERIT_TYPE, // Type after extends. Exclude non-viable types (built-ins, enums, void). Includes subtypes using the argument index.
+ COMPLETION_METHOD, // List available methods in scope.
+ COMPLETION_OVERRIDE_METHOD, // Override implementation, also for native virtuals.
+ COMPLETION_PROPERTY_DECLARATION, // Property declaration (get, set).
+ COMPLETION_PROPERTY_DECLARATION_OR_TYPE, // Property declaration (get, set) or a type hint.
+ COMPLETION_PROPERTY_METHOD, // Property setter or getter (list available methods).
+ COMPLETION_RESOURCE_PATH, // For load/preload.
+ COMPLETION_SUBSCRIPT, // Inside id[|].
+ COMPLETION_SUPER_METHOD, // After super.
+ COMPLETION_TYPE_ATTRIBUTE, // Attribute in type name (Type.|).
+ COMPLETION_TYPE_NAME, // Name of type (after :).
+ COMPLETION_TYPE_NAME_OR_VOID, // Same as TYPE_NAME, but allows void (in function return type).
+ };
-public:
- bool has_error() const;
- String get_error() const;
- int get_error_line() const;
- int get_error_column() const;
+ struct CompletionContext {
+ CompletionType type = COMPLETION_NONE;
+ ClassNode *current_class = nullptr;
+ FunctionNode *current_function = nullptr;
+ SuiteNode *current_suite = nullptr;
+ int current_line = -1;
+ int current_argument = -1;
+ Variant::Type builtin_type = Variant::VARIANT_MAX;
+ Node *node = nullptr;
+ Object *base = nullptr;
+ List<Ref<GDScriptParserRef>> dependent_parsers;
+ };
+
+ struct CompletionCall {
+ Node *call = nullptr;
+ int argument = -1;
+ };
+
+private:
+ friend class GDScriptAnalyzer;
+
+ bool _is_tool = false;
+ String script_path;
+ bool for_completion = false;
+ bool panic_mode = false;
+ bool can_break = false;
+ bool can_continue = false;
+ bool is_ignoring_warnings = false;
+ List<bool> multiline_stack;
+
+ ClassNode *head = nullptr;
+ Node *list = nullptr;
+ List<ParserError> errors;
#ifdef DEBUG_ENABLED
- const List<GDScriptWarning> &get_warnings() const { return warnings; }
-#endif // DEBUG_ENABLED
- Error parse(const String &p_code, const String &p_base_path = "", bool p_just_validate = false, const String &p_self_path = "", bool p_for_completion = false, Set<int> *r_safe_lines = nullptr, bool p_dependencies_only = false);
- Error parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path = "", const String &p_self_path = "");
+ List<GDScriptWarning> warnings;
+ Set<String> ignored_warnings;
+ Set<int> unsafe_lines;
+#endif
- bool is_tool_script() const;
- const Node *get_parse_tree() const;
+ GDScriptTokenizer tokenizer;
+ GDScriptTokenizer::Token previous;
+ GDScriptTokenizer::Token current;
+
+ ClassNode *current_class = nullptr;
+ FunctionNode *current_function = nullptr;
+ SuiteNode *current_suite = nullptr;
+
+ CompletionContext completion_context;
+ CompletionCall completion_call;
+ List<CompletionCall> completion_call_stack;
+ bool passed_cursor = false;
+
+ typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target);
+ struct AnnotationInfo {
+ enum TargetKind {
+ NONE = 0,
+ SCRIPT = 1 << 0,
+ CLASS = 1 << 1,
+ VARIABLE = 1 << 2,
+ CONSTANT = 1 << 3,
+ SIGNAL = 1 << 4,
+ FUNCTION = 1 << 5,
+ STATEMENT = 1 << 6,
+ CLASS_LEVEL = CLASS | VARIABLE | FUNCTION,
+ };
+ uint32_t target_kind = 0; // Flags.
+ AnnotationAction apply = nullptr;
+ MethodInfo info;
+ };
+ HashMap<StringName, AnnotationInfo> valid_annotations;
+ List<AnnotationNode *> annotation_stack;
+
+ typedef ExpressionNode *(GDScriptParser::*ParseFunction)(ExpressionNode *p_previous_operand, bool p_can_assign);
+ // Higher value means higher precedence (i.e. is evaluated first).
+ enum Precedence {
+ PREC_NONE,
+ PREC_ASSIGNMENT,
+ PREC_CAST,
+ PREC_TERNARY,
+ PREC_LOGIC_OR,
+ PREC_LOGIC_AND,
+ PREC_LOGIC_NOT,
+ PREC_CONTENT_TEST,
+ PREC_COMPARISON,
+ PREC_BIT_OR,
+ PREC_BIT_XOR,
+ PREC_BIT_AND,
+ PREC_BIT_SHIFT,
+ PREC_SUBTRACTION,
+ PREC_ADDITION,
+ PREC_FACTOR,
+ PREC_SIGN,
+ PREC_BIT_NOT,
+ PREC_TYPE_TEST,
+ PREC_AWAIT,
+ PREC_CALL,
+ PREC_ATTRIBUTE,
+ PREC_SUBSCRIPT,
+ PREC_PRIMARY,
+ };
+ struct ParseRule {
+ ParseFunction prefix = nullptr;
+ ParseFunction infix = nullptr;
+ Precedence precedence = PREC_NONE;
+ };
+ static ParseRule *get_rule(GDScriptTokenizer::Token::Type p_token_type);
- //completion info
+ template <class T>
+ T *alloc_node() {
+ T *node = memnew(T);
- CompletionType get_completion_type();
- StringName get_completion_cursor();
- int get_completion_line();
- Variant::Type get_completion_built_in_constant();
- Node *get_completion_node();
- ClassNode *get_completion_class();
- BlockNode *get_completion_block();
- FunctionNode *get_completion_function();
- int get_completion_argument_index();
- bool get_completion_identifier_is_function();
+ node->next = list;
+ list = node;
- const List<String> &get_dependencies() const { return dependencies; }
+ // TODO: Properly set positions for all nodes.
+ node->start_line = previous.start_line;
+ node->end_line = previous.end_line;
+ node->start_column = previous.start_column;
+ node->end_column = previous.end_column;
+ node->leftmost_column = previous.leftmost_column;
+ node->rightmost_column = previous.rightmost_column;
+ return node;
+ }
void clear();
+ void push_error(const String &p_message, const Node *p_origin = nullptr);
+#ifdef DEBUG_ENABLED
+ void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String());
+ void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols);
+#endif
+
+ void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false);
+ void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false);
+ void push_completion_call(Node *p_call);
+ void pop_completion_call();
+ void set_last_completion_call_arg(int p_argument);
+
+ GDScriptTokenizer::Token advance();
+ bool match(GDScriptTokenizer::Token::Type p_token_type);
+ bool check(GDScriptTokenizer::Token::Type p_token_type);
+ bool consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message);
+ bool is_at_end();
+ bool is_statement_end();
+ void end_statement(const String &p_context);
+ void synchronize();
+ void push_multiline(bool p_state);
+ void pop_multiline();
+
+ // Main blocks.
+ void parse_program();
+ ClassNode *parse_class();
+ void parse_class_name();
+ void parse_extends();
+ void parse_class_body();
+ template <class T>
+ void parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind);
+ SignalNode *parse_signal();
+ EnumNode *parse_enum();
+ ParameterNode *parse_parameter();
+ FunctionNode *parse_function();
+ SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr);
+ // Annotations
+ AnnotationNode *parse_annotation(uint32_t p_valid_targets);
+ bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, int p_optional_arguments = 0, bool p_is_vararg = false);
+ bool validate_annotation_arguments(AnnotationNode *p_annotation);
+ void clear_unused_annotations();
+ bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target);
+ bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target);
+ bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target);
+ template <PropertyHint t_hint, Variant::Type t_type>
+ bool export_annotations(const AnnotationNode *p_annotation, Node *p_target);
+ bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target);
+ template <MultiplayerAPI::RPCMode t_mode>
+ bool network_annotations(const AnnotationNode *p_annotation, Node *p_target);
+ // Statements.
+ Node *parse_statement();
+ VariableNode *parse_variable();
+ VariableNode *parse_variable(bool p_allow_property);
+ VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent);
+ void parse_property_getter(VariableNode *p_variable);
+ void parse_property_setter(VariableNode *p_variable);
+ ConstantNode *parse_constant();
+ AssertNode *parse_assert();
+ BreakNode *parse_break();
+ ContinueNode *parse_continue();
+ ForNode *parse_for();
+ IfNode *parse_if(const String &p_token = "if");
+ MatchNode *parse_match();
+ MatchBranchNode *parse_match_branch();
+ PatternNode *parse_match_pattern(PatternNode *p_root_pattern = nullptr);
+ WhileNode *parse_while();
+ // Expressions.
+ ExpressionNode *parse_expression(bool p_can_assign, bool p_stop_on_assign = false);
+ ExpressionNode *parse_precedence(Precedence p_precedence, bool p_can_assign, bool p_stop_on_assign = false);
+ ExpressionNode *parse_literal(ExpressionNode *p_previous_operand, bool p_can_assign);
+ LiteralNode *parse_literal();
+ ExpressionNode *parse_self(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign);
+ IdentifierNode *parse_identifier();
+ ExpressionNode *parse_builtin_constant(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_unary_operator(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_ternary_operator(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_assignment(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_array(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_dictionary(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_call(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_preload(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_grouping(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_await(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign);
+ ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign);
+ TypeNode *parse_type(bool p_allow_void = false);
+
+public:
+ Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion);
+ ClassNode *get_tree() const { return head; }
+ bool is_tool() const { return _is_tool; }
+ static Variant::Type get_builtin_type(const StringName &p_type);
+ static GDScriptFunctions::Function get_builtin_function(const StringName &p_name);
+
+ CompletionContext get_completion_context() const { return completion_context; }
+ CompletionCall get_completion_call() const { return completion_call; }
+ void get_annotation_list(List<MethodInfo> *r_annotations) const;
+
+ const List<ParserError> &get_errors() const { return errors; }
+ const List<String> get_dependencies() const {
+ // TODO: Keep track of deps.
+ return List<String>();
+ }
+#ifdef DEBUG_ENABLED
+ const List<GDScriptWarning> &get_warnings() const { return warnings; }
+ const Set<int> &get_unsafe_lines() const { return unsafe_lines; }
+ int get_last_line_number() const { return current.end_line; }
+#endif
+
GDScriptParser();
~GDScriptParser();
+
+#ifdef DEBUG_ENABLED
+ class TreePrinter {
+ int indent_level = 0;
+ String indent;
+ StringBuilder printed;
+ bool pending_indent = false;
+
+ void increase_indent();
+ void decrease_indent();
+ void push_line(const String &p_line = String());
+ void push_text(const String &p_text);
+
+ void print_annotation(AnnotationNode *p_annotation);
+ void print_array(ArrayNode *p_array);
+ void print_assert(AssertNode *p_assert);
+ void print_assignment(AssignmentNode *p_assignment);
+ void print_await(AwaitNode *p_await);
+ void print_binary_op(BinaryOpNode *p_binary_op);
+ void print_call(CallNode *p_call);
+ void print_cast(CastNode *p_cast);
+ void print_class(ClassNode *p_class);
+ void print_constant(ConstantNode *p_constant);
+ void print_dictionary(DictionaryNode *p_dictionary);
+ void print_expression(ExpressionNode *p_expression);
+ void print_enum(EnumNode *p_enum);
+ void print_for(ForNode *p_for);
+ void print_function(FunctionNode *p_function);
+ void print_get_node(GetNodeNode *p_get_node);
+ void print_if(IfNode *p_if, bool p_is_elif = false);
+ void print_identifier(IdentifierNode *p_identifier);
+ void print_literal(LiteralNode *p_literal);
+ void print_match(MatchNode *p_match);
+ void print_match_branch(MatchBranchNode *p_match_branch);
+ void print_match_pattern(PatternNode *p_match_pattern);
+ void print_parameter(ParameterNode *p_parameter);
+ void print_preload(PreloadNode *p_preload);
+ void print_return(ReturnNode *p_return);
+ void print_self(SelfNode *p_self);
+ void print_signal(SignalNode *p_signal);
+ void print_statement(Node *p_statement);
+ void print_subscript(SubscriptNode *p_subscript);
+ void print_suite(SuiteNode *p_suite);
+ void print_type(TypeNode *p_type);
+ void print_ternary_op(TernaryOpNode *p_ternary_op);
+ void print_unary_op(UnaryOpNode *p_unary_op);
+ void print_variable(VariableNode *p_variable);
+ void print_while(WhileNode *p_while);
+
+ public:
+ void print_tree(const GDScriptParser &p_parser);
+ };
+#endif // DEBUG_ENABLED
};
#endif // GDSCRIPT_PARSER_H
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 82def3f877..d230173e9a 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -30,1476 +30,1294 @@
#include "gdscript_tokenizer.h"
-#include "core/io/marshalls.h"
-#include "core/map.h"
-#include "core/print_string.h"
-#include "gdscript_functions.h"
-
-const char *GDScriptTokenizer::token_names[TK_MAX] = {
- "Empty",
- "Identifier",
- "Constant",
- "Self",
- "Built-In Type",
- "Built-In Func",
- "In",
- "'=='",
- "'!='",
- "'<'",
- "'<='",
- "'>'",
- "'>='",
- "'and'",
- "'or'",
- "'not'",
- "'+'",
- "'-'",
- "'*'",
- "'/'",
- "'%'",
- "'<<'",
- "'>>'",
- "'='",
- "'+='",
- "'-='",
- "'*='",
- "'/='",
- "'%='",
- "'<<='",
- "'>>='",
- "'&='",
- "'|='",
- "'^='",
- "'&'",
- "'|'",
- "'^'",
- "'~'",
- //"Plus Plus",
- //"Minus Minus",
- "if",
- "elif",
- "else",
- "for",
- "while",
- "break",
- "continue",
- "pass",
- "return",
- "match",
- "func",
- "class",
- "class_name",
- "extends",
- "is",
- "onready",
- "tool",
- "static",
- "export",
- "setget",
- "const",
- "var",
- "as",
- "void",
- "enum",
- "preload",
- "assert",
- "yield",
- "signal",
- "breakpoint",
- "remote",
- "master",
- "puppet",
- "remotesync",
- "mastersync",
- "puppetsync",
- "'['",
- "']'",
- "'{'",
- "'}'",
- "'('",
- "')'",
- "','",
- "';'",
- "'.'",
- "'?'",
- "':'",
- "'$'",
- "'->'",
- "'\\n'",
- "PI",
- "TAU",
- "_",
- "INF",
- "NAN",
- "Error",
- "EOF",
- "Cursor"
+#include "core/error_macros.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif
+
+static const char *token_names[] = {
+ "Empty", // EMPTY,
+ // Basic
+ "Annotation", // ANNOTATION
+ "Identifier", // IDENTIFIER,
+ "Literal", // LITERAL,
+ // Comparison
+ "<", // LESS,
+ "<=", // LESS_EQUAL,
+ ">", // GREATER,
+ ">=", // GREATER_EQUAL,
+ "==", // EQUAL_EQUAL,
+ "!=", // BANG_EQUAL,
+ // Logical
+ "and", // AND,
+ "or", // OR,
+ "not", // NOT,
+ "&&", // AMPERSAND_AMPERSAND,
+ "||", // PIPE_PIPE,
+ "!", // BANG,
+ // Bitwise
+ "&", // AMPERSAND,
+ "|", // PIPE,
+ "~", // TILDE,
+ "^", // CARET,
+ "<<", // LESS_LESS,
+ ">>", // GREATER_GREATER,
+ // Math
+ "+", // PLUS,
+ "-", // MINUS,
+ "*", // STAR,
+ "/", // SLASH,
+ "%", // PERCENT,
+ // Assignment
+ "=", // EQUAL,
+ "+=", // PLUS_EQUAL,
+ "-=", // MINUS_EQUAL,
+ "*=", // STAR_EQUAL,
+ "/=", // SLASH_EQUAL,
+ "%=", // PERCENT_EQUAL,
+ "<<=", // LESS_LESS_EQUAL,
+ ">>=", // GREATER_GREATER_EQUAL,
+ "&=", // AMPERSAND_EQUAL,
+ "|=", // PIPE_EQUAL,
+ "^=", // CARET_EQUAL,
+ // Control flow
+ "if", // IF,
+ "elif", // ELIF,
+ "else", // ELSE,
+ "for", // FOR,
+ "while", // WHILE,
+ "break", // BREAK,
+ "continue", // CONTINUE,
+ "pass", // PASS,
+ "return", // RETURN,
+ "match", // MATCH,
+ // Keywords
+ "as", // AS,
+ "assert", // ASSERT,
+ "await", // AWAIT,
+ "breakpoint", // BREAKPOINT,
+ "class", // CLASS,
+ "class_name", // CLASS_NAME,
+ "const", // CONST,
+ "enum", // ENUM,
+ "extends", // EXTENDS,
+ "func", // FUNC,
+ "in", // IN,
+ "is", // IS,
+ "namespace", // NAMESPACE
+ "preload", // PRELOAD,
+ "self", // SELF,
+ "signal", // SIGNAL,
+ "static", // STATIC,
+ "super", // SUPER,
+ "trait", // TRAIT,
+ "var", // VAR,
+ "void", // VOID,
+ "yield", // YIELD,
+ // Punctuation
+ "[", // BRACKET_OPEN,
+ "]", // BRACKET_CLOSE,
+ "{", // BRACE_OPEN,
+ "}", // BRACE_CLOSE,
+ "(", // PARENTHESIS_OPEN,
+ ")", // PARENTHESIS_CLOSE,
+ ",", // COMMA,
+ ";", // SEMICOLON,
+ ".", // PERIOD,
+ "..", // PERIOD_PERIOD,
+ ":", // COLON,
+ "$", // DOLLAR,
+ "->", // FORWARD_ARROW,
+ "_", // UNDERSCORE,
+ // Whitespace
+ "Newline", // NEWLINE,
+ "Indent", // INDENT,
+ "Dedent", // DEDENT,
+ // Constants
+ "PI", // CONST_PI,
+ "TAU", // CONST_TAU,
+ "INF", // CONST_INF,
+ "NaN", // CONST_NAN,
+ // Error message improvement
+ "VCS conflict marker", // VCS_CONFLICT_MARKER,
+ "`", // BACKTICK,
+ "?", // QUESTION_MARK,
+ // Special
+ "Error", // ERROR,
+ "End of file", // EOF,
};
-struct _bit {
- Variant::Type type;
- const char *text;
-};
-//built in types
-
-static const _bit _type_list[] = {
- //types
- { Variant::BOOL, "bool" },
- { Variant::INT, "int" },
- { Variant::FLOAT, "float" },
- { Variant::STRING, "String" },
- { Variant::VECTOR2, "Vector2" },
- { Variant::VECTOR2I, "Vector2i" },
- { Variant::RECT2, "Rect2" },
- { Variant::RECT2I, "Rect2i" },
- { Variant::TRANSFORM2D, "Transform2D" },
- { Variant::VECTOR3, "Vector3" },
- { Variant::VECTOR3I, "Vector3i" },
- { Variant::AABB, "AABB" },
- { Variant::PLANE, "Plane" },
- { Variant::QUAT, "Quat" },
- { Variant::BASIS, "Basis" },
- { Variant::TRANSFORM, "Transform" },
- { Variant::COLOR, "Color" },
- { Variant::_RID, "RID" },
- { Variant::OBJECT, "Object" },
- { Variant::STRING_NAME, "StringName" },
- { Variant::NODE_PATH, "NodePath" },
- { Variant::DICTIONARY, "Dictionary" },
- { Variant::CALLABLE, "Callable" },
- { Variant::SIGNAL, "Signal" },
- { Variant::ARRAY, "Array" },
- { Variant::PACKED_BYTE_ARRAY, "PackedByteArray" },
- { Variant::PACKED_INT32_ARRAY, "PackedInt32Array" },
- { Variant::PACKED_INT64_ARRAY, "PackedInt64Array" },
- { Variant::PACKED_FLOAT32_ARRAY, "PackedFloat32Array" },
- { Variant::PACKED_FLOAT64_ARRAY, "PackedFloat64Array" },
- { Variant::PACKED_STRING_ARRAY, "PackedStringArray" },
- { Variant::PACKED_VECTOR2_ARRAY, "PackedVector2Array" },
- { Variant::PACKED_VECTOR3_ARRAY, "PackedVector3Array" },
- { Variant::PACKED_COLOR_ARRAY, "PackedColorArray" },
- { Variant::VARIANT_MAX, nullptr },
-};
-
-struct _kws {
- GDScriptTokenizer::Token token;
- const char *text;
-};
-
-static const _kws _keyword_list[] = {
- //ops
- { GDScriptTokenizer::TK_OP_IN, "in" },
- { GDScriptTokenizer::TK_OP_NOT, "not" },
- { GDScriptTokenizer::TK_OP_OR, "or" },
- { GDScriptTokenizer::TK_OP_AND, "and" },
- //func
- { GDScriptTokenizer::TK_PR_FUNCTION, "func" },
- { GDScriptTokenizer::TK_PR_CLASS, "class" },
- { GDScriptTokenizer::TK_PR_CLASS_NAME, "class_name" },
- { GDScriptTokenizer::TK_PR_EXTENDS, "extends" },
- { GDScriptTokenizer::TK_PR_IS, "is" },
- { GDScriptTokenizer::TK_PR_ONREADY, "onready" },
- { GDScriptTokenizer::TK_PR_TOOL, "tool" },
- { GDScriptTokenizer::TK_PR_STATIC, "static" },
- { GDScriptTokenizer::TK_PR_EXPORT, "export" },
- { GDScriptTokenizer::TK_PR_SETGET, "setget" },
- { GDScriptTokenizer::TK_PR_VAR, "var" },
- { GDScriptTokenizer::TK_PR_AS, "as" },
- { GDScriptTokenizer::TK_PR_VOID, "void" },
- { GDScriptTokenizer::TK_PR_PRELOAD, "preload" },
- { GDScriptTokenizer::TK_PR_ASSERT, "assert" },
- { GDScriptTokenizer::TK_PR_YIELD, "yield" },
- { GDScriptTokenizer::TK_PR_SIGNAL, "signal" },
- { GDScriptTokenizer::TK_PR_BREAKPOINT, "breakpoint" },
- { GDScriptTokenizer::TK_PR_REMOTE, "remote" },
- { GDScriptTokenizer::TK_PR_MASTER, "master" },
- { GDScriptTokenizer::TK_PR_PUPPET, "puppet" },
- { GDScriptTokenizer::TK_PR_REMOTESYNC, "remotesync" },
- { GDScriptTokenizer::TK_PR_MASTERSYNC, "mastersync" },
- { GDScriptTokenizer::TK_PR_PUPPETSYNC, "puppetsync" },
- { GDScriptTokenizer::TK_PR_CONST, "const" },
- { GDScriptTokenizer::TK_PR_ENUM, "enum" },
- //controlflow
- { GDScriptTokenizer::TK_CF_IF, "if" },
- { GDScriptTokenizer::TK_CF_ELIF, "elif" },
- { GDScriptTokenizer::TK_CF_ELSE, "else" },
- { GDScriptTokenizer::TK_CF_FOR, "for" },
- { GDScriptTokenizer::TK_CF_WHILE, "while" },
- { GDScriptTokenizer::TK_CF_BREAK, "break" },
- { GDScriptTokenizer::TK_CF_CONTINUE, "continue" },
- { GDScriptTokenizer::TK_CF_RETURN, "return" },
- { GDScriptTokenizer::TK_CF_MATCH, "match" },
- { GDScriptTokenizer::TK_CF_PASS, "pass" },
- { GDScriptTokenizer::TK_SELF, "self" },
- { GDScriptTokenizer::TK_CONST_PI, "PI" },
- { GDScriptTokenizer::TK_CONST_TAU, "TAU" },
- { GDScriptTokenizer::TK_WILDCARD, "_" },
- { GDScriptTokenizer::TK_CONST_INF, "INF" },
- { GDScriptTokenizer::TK_CONST_NAN, "NAN" },
- { GDScriptTokenizer::TK_ERROR, nullptr }
-};
+// Avoid desync.
+static_assert(sizeof(token_names) / sizeof(token_names[0]) == GDScriptTokenizer::Token::TK_MAX, "Amount of token names don't match the amount of token types.");
-const char *GDScriptTokenizer::get_token_name(Token p_token) {
- ERR_FAIL_INDEX_V(p_token, TK_MAX, "<error>");
- return token_names[p_token];
+const char *GDScriptTokenizer::Token::get_name() const {
+ ERR_FAIL_INDEX_V_MSG(type, TK_MAX, "<error>", "Using token type out of the enum.");
+ return token_names[type];
}
-bool GDScriptTokenizer::is_token_literal(int p_offset, bool variable_safe) const {
- switch (get_token(p_offset)) {
- // Can always be literal:
- case TK_IDENTIFIER:
-
- case TK_PR_ONREADY:
- case TK_PR_TOOL:
- case TK_PR_STATIC:
- case TK_PR_EXPORT:
- case TK_PR_SETGET:
- case TK_PR_SIGNAL:
- case TK_PR_REMOTE:
- case TK_PR_MASTER:
- case TK_PR_PUPPET:
- case TK_PR_REMOTESYNC:
- case TK_PR_MASTERSYNC:
- case TK_PR_PUPPETSYNC:
- return true;
-
- // Literal for non-variables only:
- case TK_BUILT_IN_TYPE:
- case TK_BUILT_IN_FUNC:
-
- case TK_OP_IN:
- //case TK_OP_NOT:
- //case TK_OP_OR:
- //case TK_OP_AND:
-
- case TK_PR_CLASS:
- case TK_PR_CONST:
- case TK_PR_ENUM:
- case TK_PR_PRELOAD:
- case TK_PR_FUNCTION:
- case TK_PR_EXTENDS:
- case TK_PR_ASSERT:
- case TK_PR_YIELD:
- case TK_PR_VAR:
-
- case TK_CF_IF:
- case TK_CF_ELIF:
- case TK_CF_ELSE:
- case TK_CF_FOR:
- case TK_CF_WHILE:
- case TK_CF_BREAK:
- case TK_CF_CONTINUE:
- case TK_CF_RETURN:
- case TK_CF_MATCH:
- case TK_CF_PASS:
- case TK_SELF:
- case TK_CONST_PI:
- case TK_CONST_TAU:
- case TK_WILDCARD:
- case TK_CONST_INF:
- case TK_CONST_NAN:
- case TK_ERROR:
- return !variable_safe;
-
- case TK_CONSTANT: {
- switch (get_token_constant(p_offset).get_type()) {
- case Variant::NIL:
- case Variant::BOOL:
- return true;
- default:
- return false;
- }
- }
- default:
- return false;
- }
+String GDScriptTokenizer::get_token_name(Token::Type p_token_type) {
+ ERR_FAIL_INDEX_V_MSG(p_token_type, Token::TK_MAX, "<error>", "Using token type out of the enum.");
+ return token_names[p_token_type];
}
-StringName GDScriptTokenizer::get_token_literal(int p_offset) const {
- Token token = get_token(p_offset);
- switch (token) {
- case TK_IDENTIFIER:
- return get_token_identifier(p_offset);
- case TK_BUILT_IN_TYPE: {
- Variant::Type type = get_token_type(p_offset);
- int idx = 0;
-
- while (_type_list[idx].text) {
- if (type == _type_list[idx].type) {
- return _type_list[idx].text;
- }
- idx++;
- }
- } break; // Shouldn't get here, stuff happens
- case TK_BUILT_IN_FUNC:
- return GDScriptFunctions::get_func_name(get_token_built_in_func(p_offset));
- case TK_CONSTANT: {
- const Variant value = get_token_constant(p_offset);
-
- switch (value.get_type()) {
- case Variant::NIL:
- return "null";
- case Variant::BOOL:
- return value ? "true" : "false";
- default: {
- }
- }
- } break;
- case TK_OP_AND:
- case TK_OP_OR:
- break; // Don't get into default, since they can be non-literal
- default: {
- int idx = 0;
-
- while (_keyword_list[idx].text) {
- if (token == _keyword_list[idx].token) {
- return _keyword_list[idx].text;
- }
- idx++;
- }
- }
+void GDScriptTokenizer::set_source_code(const String &p_source_code) {
+ source = p_source_code;
+ if (source.empty()) {
+ _source = L"";
+ } else {
+ _source = source.ptr();
}
- ERR_FAIL_V_MSG("", "Failed to get token literal.");
+ _current = _source;
+ line = 1;
+ column = 1;
+ length = p_source_code.length();
+ position = 0;
}
-static bool _is_text_char(CharType c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
+void GDScriptTokenizer::set_cursor_position(int p_line, int p_column) {
+ cursor_line = p_line;
+ cursor_column = p_column;
}
-static bool _is_number(CharType c) {
- return (c >= '0' && c <= '9');
+void GDScriptTokenizer::set_multiline_mode(bool p_state) {
+ multiline_mode = p_state;
}
-static bool _is_hex(CharType c) {
- return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+int GDScriptTokenizer::get_cursor_line() const {
+ return cursor_line;
}
-static bool _is_bin(CharType c) {
- return (c == '0' || c == '1');
+int GDScriptTokenizer::get_cursor_column() const {
+ return cursor_column;
}
-void GDScriptTokenizerText::_make_token(Token p_type) {
- TokenData &tk = tk_rb[tk_rb_pos];
-
- tk.type = p_type;
- tk.line = line;
- tk.col = column;
-
- tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+bool GDScriptTokenizer::is_past_cursor() const {
+ if (line < cursor_line) {
+ return false;
+ }
+ if (line > cursor_line) {
+ return true;
+ }
+ if (column < cursor_column) {
+ return false;
+ }
+ return true;
}
-void GDScriptTokenizerText::_make_identifier(const StringName &p_identifier) {
- TokenData &tk = tk_rb[tk_rb_pos];
-
- tk.type = TK_IDENTIFIER;
- tk.identifier = p_identifier;
- tk.line = line;
- tk.col = column;
-
- tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+CharType GDScriptTokenizer::_advance() {
+ if (unlikely(_is_at_end())) {
+ return '\0';
+ }
+ _current++;
+ column++;
+ position++;
+ if (column > rightmost_column) {
+ rightmost_column = column;
+ }
+ if (unlikely(_is_at_end())) {
+ // Add extra newline even if it's not there, to satisfy the parser.
+ newline(true);
+ // Also add needed unindent.
+ check_indent();
+ }
+ return _peek(-1);
}
-void GDScriptTokenizerText::_make_built_in_func(GDScriptFunctions::Function p_func) {
- TokenData &tk = tk_rb[tk_rb_pos];
-
- tk.type = TK_BUILT_IN_FUNC;
- tk.func = p_func;
- tk.line = line;
- tk.col = column;
-
- tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+void GDScriptTokenizer::push_paren(CharType p_char) {
+ paren_stack.push_back(p_char);
}
-void GDScriptTokenizerText::_make_constant(const Variant &p_constant) {
- TokenData &tk = tk_rb[tk_rb_pos];
-
- tk.type = TK_CONSTANT;
- tk.constant = p_constant;
- tk.line = line;
- tk.col = column;
+bool GDScriptTokenizer::pop_paren(CharType p_expected) {
+ if (paren_stack.empty()) {
+ return false;
+ }
+ CharType actual = paren_stack.back()->get();
+ paren_stack.pop_back();
- tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+ return actual == p_expected;
}
-void GDScriptTokenizerText::_make_type(const Variant::Type &p_type) {
- TokenData &tk = tk_rb[tk_rb_pos];
-
- tk.type = TK_BUILT_IN_TYPE;
- tk.vtype = p_type;
- tk.line = line;
- tk.col = column;
-
- tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+GDScriptTokenizer::Token GDScriptTokenizer::pop_error() {
+ Token error = error_stack.back()->get();
+ error_stack.pop_back();
+ return error;
}
-void GDScriptTokenizerText::_make_error(const String &p_error) {
- error_flag = true;
- last_error = p_error;
-
- TokenData &tk = tk_rb[tk_rb_pos];
- tk.type = TK_ERROR;
- tk.constant = p_error;
- tk.line = line;
- tk.col = column;
- tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+static bool _is_alphanumeric(CharType c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
}
-void GDScriptTokenizerText::_make_newline(int p_indentation, int p_tabs) {
- TokenData &tk = tk_rb[tk_rb_pos];
- tk.type = TK_NEWLINE;
- tk.constant = Vector2(p_indentation, p_tabs);
- tk.line = line;
- tk.col = column;
- tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
+static bool _is_digit(CharType c) {
+ return (c >= '0' && c <= '9');
}
-void GDScriptTokenizerText::_advance() {
- if (error_flag) {
- //parser broke
- _make_error(last_error);
- return;
- }
-
- if (code_pos >= len) {
- _make_token(TK_EOF);
- return;
- }
-#define GETCHAR(m_ofs) ((m_ofs + code_pos) >= len ? 0 : _code[m_ofs + code_pos])
-#define INCPOS(m_amount) \
- { \
- code_pos += m_amount; \
- column += m_amount; \
- }
- while (true) {
- bool is_string_name = false;
- StringMode string_mode = STRING_DOUBLE_QUOTE;
-
- switch (GETCHAR(0)) {
- case 0:
- _make_token(TK_EOF);
- break;
- case '\\':
- INCPOS(1);
- if (GETCHAR(0) == '\r') {
- INCPOS(1);
- }
-
- if (GETCHAR(0) != '\n') {
- _make_error("Expected newline after '\\'.");
- return;
- }
-
- INCPOS(1);
- line++;
-
- while (GETCHAR(0) == ' ' || GETCHAR(0) == '\t') {
- INCPOS(1);
- }
-
- continue;
- case '\t':
- case '\r':
- case ' ':
- INCPOS(1);
- continue;
- case '#': { // line comment skip
-#ifdef DEBUG_ENABLED
- String comment;
-#endif // DEBUG_ENABLED
- while (GETCHAR(0) != '\n') {
-#ifdef DEBUG_ENABLED
- comment += GETCHAR(0);
-#endif // DEBUG_ENABLED
- code_pos++;
- if (GETCHAR(0) == 0) { //end of file
- //_make_error("Unterminated Comment");
- _make_token(TK_EOF);
- return;
- }
- }
-#ifdef DEBUG_ENABLED
- String comment_content = comment.trim_prefix("#").trim_prefix(" ");
- if (comment_content.begins_with("warning-ignore:")) {
- String code = comment_content.get_slice(":", 1);
- warning_skips.push_back(Pair<int, String>(line, code.strip_edges().to_lower()));
- } else if (comment_content.begins_with("warning-ignore-all:")) {
- String code = comment_content.get_slice(":", 1);
- warning_global_skips.insert(code.strip_edges().to_lower());
- } else if (comment_content.strip_edges() == "warnings-disable") {
- ignore_warnings = true;
- }
-#endif // DEBUG_ENABLED
- [[fallthrough]];
- }
- case '\n': {
- line++;
- INCPOS(1);
- bool used_spaces = false;
- int tabs = 0;
- column = 1;
- int i = 0;
- while (true) {
- if (GETCHAR(i) == ' ') {
- i++;
- used_spaces = true;
- } else if (GETCHAR(i) == '\t') {
- if (used_spaces) {
- _make_error("Spaces used before tabs on a line");
- return;
- }
- i++;
- tabs++;
- } else {
- break; // not indentation anymore
- }
- }
-
- _make_newline(i, tabs);
- return;
- }
- case '/': {
- switch (GETCHAR(1)) {
- case '=': { // diveq
-
- _make_token(TK_OP_ASSIGN_DIV);
- INCPOS(1);
-
- } break;
- default:
- _make_token(TK_OP_DIV);
- }
- } break;
- case '=': {
- if (GETCHAR(1) == '=') {
- _make_token(TK_OP_EQUAL);
- INCPOS(1);
-
- } else {
- _make_token(TK_OP_ASSIGN);
- }
-
- } break;
- case '<': {
- if (GETCHAR(1) == '=') {
- _make_token(TK_OP_LESS_EQUAL);
- INCPOS(1);
- } else if (GETCHAR(1) == '<') {
- if (GETCHAR(2) == '=') {
- _make_token(TK_OP_ASSIGN_SHIFT_LEFT);
- INCPOS(1);
- } else {
- _make_token(TK_OP_SHIFT_LEFT);
- }
- INCPOS(1);
- } else {
- _make_token(TK_OP_LESS);
- }
-
- } break;
- case '>': {
- if (GETCHAR(1) == '=') {
- _make_token(TK_OP_GREATER_EQUAL);
- INCPOS(1);
- } else if (GETCHAR(1) == '>') {
- if (GETCHAR(2) == '=') {
- _make_token(TK_OP_ASSIGN_SHIFT_RIGHT);
- INCPOS(1);
-
- } else {
- _make_token(TK_OP_SHIFT_RIGHT);
- }
- INCPOS(1);
- } else {
- _make_token(TK_OP_GREATER);
- }
-
- } break;
- case '!': {
- if (GETCHAR(1) == '=') {
- _make_token(TK_OP_NOT_EQUAL);
- INCPOS(1);
- } else {
- _make_token(TK_OP_NOT);
- }
-
- } break;
- //case '"' //string - no strings in shader
- //case '\'' //string - no strings in shader
- case '{':
- _make_token(TK_CURLY_BRACKET_OPEN);
- break;
- case '}':
- _make_token(TK_CURLY_BRACKET_CLOSE);
- break;
- case '[':
- _make_token(TK_BRACKET_OPEN);
- break;
- case ']':
- _make_token(TK_BRACKET_CLOSE);
- break;
- case '(':
- _make_token(TK_PARENTHESIS_OPEN);
- break;
- case ')':
- _make_token(TK_PARENTHESIS_CLOSE);
- break;
- case ',':
- _make_token(TK_COMMA);
- break;
- case ';':
- _make_token(TK_SEMICOLON);
- break;
- case '?':
- _make_token(TK_QUESTION_MARK);
- break;
- case ':':
- _make_token(TK_COLON); //for methods maybe but now useless.
- break;
- case '$':
- _make_token(TK_DOLLAR); //for the get_node() shortener
- break;
- case '^': {
- if (GETCHAR(1) == '=') {
- _make_token(TK_OP_ASSIGN_BIT_XOR);
- INCPOS(1);
- } else {
- _make_token(TK_OP_BIT_XOR);
- }
+static bool _is_hex_digit(CharType c) {
+ return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+}
- } break;
- case '~':
- _make_token(TK_OP_BIT_INVERT);
- break;
- case '&': {
- if (GETCHAR(1) == '&') {
- _make_token(TK_OP_AND);
- INCPOS(1);
- } else if (GETCHAR(1) == '=') {
- _make_token(TK_OP_ASSIGN_BIT_AND);
- INCPOS(1);
- } else {
- _make_token(TK_OP_BIT_AND);
- }
- } break;
- case '|': {
- if (GETCHAR(1) == '|') {
- _make_token(TK_OP_OR);
- INCPOS(1);
- } else if (GETCHAR(1) == '=') {
- _make_token(TK_OP_ASSIGN_BIT_OR);
- INCPOS(1);
- } else {
- _make_token(TK_OP_BIT_OR);
- }
- } break;
- case '*': {
- if (GETCHAR(1) == '=') {
- _make_token(TK_OP_ASSIGN_MUL);
- INCPOS(1);
- } else {
- _make_token(TK_OP_MUL);
- }
- } break;
- case '+': {
- if (GETCHAR(1) == '=') {
- _make_token(TK_OP_ASSIGN_ADD);
- INCPOS(1);
- /*
- } else if (GETCHAR(1)=='+') {
- _make_token(TK_OP_PLUS_PLUS);
- INCPOS(1);
- */
- } else {
- _make_token(TK_OP_ADD);
- }
+static bool _is_binary_digit(CharType c) {
+ return (c == '0' || c == '1');
+}
- } break;
- case '-': {
- if (GETCHAR(1) == '=') {
- _make_token(TK_OP_ASSIGN_SUB);
- INCPOS(1);
- } else if (GETCHAR(1) == '>') {
- _make_token(TK_FORWARD_ARROW);
- INCPOS(1);
+GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) {
+ Token token(p_type);
+ token.start_line = start_line;
+ token.end_line = line;
+ token.start_column = start_column;
+ token.end_column = column;
+ token.leftmost_column = leftmost_column;
+ token.rightmost_column = rightmost_column;
+ token.source = String(_start, _current - _start);
+
+ if (p_type != Token::ERROR && cursor_line > -1) {
+ // Also count whitespace after token.
+ int offset = 0;
+ while (_peek(offset) == ' ' || _peek(offset) == '\t') {
+ offset++;
+ }
+ int last_column = column + offset;
+ // Check cursor position in token.
+ if (start_line == line) {
+ // Single line token.
+ if (cursor_line == start_line && cursor_column >= start_column && cursor_column <= last_column) {
+ token.cursor_position = cursor_column - start_column;
+ if (cursor_column == start_column) {
+ token.cursor_place = CURSOR_BEGINNING;
+ } else if (cursor_column < column) {
+ token.cursor_place = CURSOR_MIDDLE;
} else {
- _make_token(TK_OP_SUB);
+ token.cursor_place = CURSOR_END;
}
- } break;
- case '%': {
- if (GETCHAR(1) == '=') {
- _make_token(TK_OP_ASSIGN_MOD);
- INCPOS(1);
+ }
+ } else {
+ // Multi line token.
+ if (cursor_line == start_line && cursor_column >= start_column) {
+ // Is in first line.
+ token.cursor_position = cursor_column - start_column;
+ if (cursor_column == start_column) {
+ token.cursor_place = CURSOR_BEGINNING;
} else {
- _make_token(TK_OP_MOD);
- }
- } break;
- case '@':
- if (CharType(GETCHAR(1)) != '"' && CharType(GETCHAR(1)) != '\'') {
- _make_error("Unexpected '@'");
- return;
- }
- INCPOS(1);
- is_string_name = true;
- [[fallthrough]];
- case '\'':
- case '"': {
- if (GETCHAR(0) == '\'') {
- string_mode = STRING_SINGLE_QUOTE;
- }
-
- int i = 1;
- if (string_mode == STRING_DOUBLE_QUOTE && GETCHAR(i) == '"' && GETCHAR(i + 1) == '"') {
- i += 2;
- string_mode = STRING_MULTILINE;
+ token.cursor_place = CURSOR_MIDDLE;
}
-
- String str;
- while (true) {
- if (CharType(GETCHAR(i)) == 0) {
- _make_error("Unterminated String");
- return;
- } else if (string_mode == STRING_DOUBLE_QUOTE && CharType(GETCHAR(i)) == '"') {
- break;
- } else if (string_mode == STRING_SINGLE_QUOTE && CharType(GETCHAR(i)) == '\'') {
- break;
- } else if (string_mode == STRING_MULTILINE && CharType(GETCHAR(i)) == '\"' && CharType(GETCHAR(i + 1)) == '\"' && CharType(GETCHAR(i + 2)) == '\"') {
- i += 2;
- break;
- } else if (string_mode != STRING_MULTILINE && CharType(GETCHAR(i)) == '\n') {
- _make_error("Unexpected EOL at String.");
- return;
- } else if (CharType(GETCHAR(i)) == 0xFFFF) {
- //string ends here, next will be TK
- i--;
- break;
- } else if (CharType(GETCHAR(i)) == '\\') {
- //escaped characters...
- i++;
- CharType next = GETCHAR(i);
- if (next == 0) {
- _make_error("Unterminated String");
- return;
- }
- CharType res = 0;
-
- switch (next) {
- case 'a':
- res = '\a';
- break;
- case 'b':
- res = '\b';
- break;
- case 't':
- res = '\t';
- break;
- case 'n':
- res = '\n';
- break;
- case 'v':
- res = '\v';
- break;
- case 'f':
- res = '\f';
- break;
- case 'r':
- res = '\r';
- break;
- case '\'':
- res = '\'';
- break;
- case '\"':
- res = '\"';
- break;
- case '\\':
- res = '\\';
- break;
-
- case 'u': {
- // hex number
- i += 1;
- for (int j = 0; j < 4; j++) {
- CharType c = GETCHAR(i + j);
- if (c == 0) {
- _make_error("Unterminated String");
- return;
- }
-
- CharType v = 0;
- if (c >= '0' && c <= '9') {
- v = c - '0';
- } else if (c >= 'a' && c <= 'f') {
- v = c - 'a';
- v += 10;
- } else if (c >= 'A' && c <= 'F') {
- v = c - 'A';
- v += 10;
- } else {
- _make_error("Malformed hex constant in string");
- return;
- }
-
- res <<= 4;
- res |= v;
- }
- i += 3;
-
- } break;
- case '\n': {
- line++;
- column = 1;
- } break;
- default: {
- _make_error("Invalid escape sequence");
- return;
- } break;
- }
-
- if (next != '\n') {
- str += res;
- }
-
- } else {
- if (CharType(GETCHAR(i)) == '\n') {
- line++;
- column = 1;
- }
-
- str += CharType(GETCHAR(i));
- }
- i++;
- }
- INCPOS(i);
-
- if (is_string_name) {
- _make_constant(StringName(str));
+ } else if (cursor_line == line && cursor_column <= last_column) {
+ // Is in last line.
+ token.cursor_position = cursor_column - start_column;
+ if (cursor_column < column) {
+ token.cursor_place = CURSOR_MIDDLE;
} else {
- _make_constant(str);
- }
-
- } break;
- case 0xFFFF: {
- _make_token(TK_CURSOR);
- } break;
- default: {
- if (_is_number(GETCHAR(0)) || (GETCHAR(0) == '.' && _is_number(GETCHAR(1)))) {
- // parse number
- bool period_found = false;
- bool exponent_found = false;
- bool hexa_found = false;
- bool bin_found = false;
- bool sign_found = false;
-
- String str;
- int i = 0;
-
- while (true) {
- if (GETCHAR(i) == '.') {
- if (period_found || exponent_found) {
- _make_error("Invalid numeric constant at '.'");
- return;
- } else if (bin_found) {
- _make_error("Invalid binary constant at '.'");
- return;
- } else if (hexa_found) {
- _make_error("Invalid hexadecimal constant at '.'");
- return;
- }
- period_found = true;
- } else if (GETCHAR(i) == 'x') {
- if (hexa_found || bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) {
- _make_error("Invalid numeric constant at 'x'");
- return;
- }
- hexa_found = true;
- } else if (hexa_found && _is_hex(GETCHAR(i))) {
- } else if (!hexa_found && GETCHAR(i) == 'b') {
- if (bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) {
- _make_error("Invalid numeric constant at 'b'");
- return;
- }
- bin_found = true;
- } else if (!hexa_found && GETCHAR(i) == 'e') {
- if (exponent_found || bin_found) {
- _make_error("Invalid numeric constant at 'e'");
- return;
- }
- exponent_found = true;
- } else if (_is_number(GETCHAR(i))) {
- //all ok
-
- } else if (bin_found && _is_bin(GETCHAR(i))) {
- } else if ((GETCHAR(i) == '-' || GETCHAR(i) == '+') && exponent_found) {
- if (sign_found) {
- _make_error("Invalid numeric constant at '-'");
- return;
- }
- sign_found = true;
- } else if (GETCHAR(i) == '_') {
- i++;
- continue; // Included for readability, shouldn't be a part of the string
- } else {
- break;
- }
-
- str += CharType(GETCHAR(i));
- i++;
- }
-
- if (!(_is_number(str[str.length() - 1]) || (hexa_found && _is_hex(str[str.length() - 1])))) {
- _make_error("Invalid numeric constant: " + str);
- return;
- }
-
- INCPOS(i);
- if (hexa_found) {
- int64_t val = str.hex_to_int();
- _make_constant(val);
- } else if (bin_found) {
- int64_t val = str.bin_to_int();
- _make_constant(val);
- } else if (period_found || exponent_found) {
- double val = str.to_double();
- _make_constant(val);
- } else {
- int64_t val = str.to_int();
- _make_constant(val);
- }
-
- return;
+ token.cursor_place = CURSOR_END;
}
+ } else if (cursor_line > start_line && cursor_line < line) {
+ // Is in middle line.
+ token.cursor_position = CURSOR_MIDDLE;
+ }
+ }
+ }
- if (GETCHAR(0) == '.') {
- //parse period
- _make_token(TK_PERIOD);
- break;
- }
-
- if (_is_text_char(GETCHAR(0))) {
- // parse identifier
- String str;
- str += CharType(GETCHAR(0));
-
- int i = 1;
- while (_is_text_char(GETCHAR(i))) {
- str += CharType(GETCHAR(i));
- i++;
- }
-
- bool identifier = false;
-
- if (str == "null") {
- _make_constant(Variant());
-
- } else if (str == "true") {
- _make_constant(true);
-
- } else if (str == "false") {
- _make_constant(false);
- } else {
- bool found = false;
-
- {
- int idx = 0;
-
- while (_type_list[idx].text) {
- if (str == _type_list[idx].text) {
- _make_type(_type_list[idx].type);
- found = true;
- break;
- }
- idx++;
- }
- }
+ return token;
+}
- if (!found) {
- //built in func?
+GDScriptTokenizer::Token GDScriptTokenizer::make_literal(const Variant &p_literal) {
+ Token token = make_token(Token::LITERAL);
+ token.literal = p_literal;
+ return token;
+}
- for (int j = 0; j < GDScriptFunctions::FUNC_MAX; j++) {
- if (str == GDScriptFunctions::get_func_name(GDScriptFunctions::Function(j))) {
- _make_built_in_func(GDScriptFunctions::Function(j));
- found = true;
- break;
- }
- }
- }
+GDScriptTokenizer::Token GDScriptTokenizer::make_identifier(const StringName &p_identifier) {
+ Token identifier = make_token(Token::IDENTIFIER);
+ identifier.literal = p_identifier;
+ return identifier;
+}
- if (!found) {
- //keyword
+GDScriptTokenizer::Token GDScriptTokenizer::make_error(const String &p_message) {
+ Token error = make_token(Token::ERROR);
+ error.literal = p_message;
- int idx = 0;
- found = false;
+ return error;
+}
- while (_keyword_list[idx].text) {
- if (str == _keyword_list[idx].text) {
- _make_token(_keyword_list[idx].token);
- found = true;
- break;
- }
- idx++;
- }
- }
+void GDScriptTokenizer::push_error(const String &p_message) {
+ Token error = make_error(p_message);
+ error_stack.push_back(error);
+}
- if (!found) {
- identifier = true;
- }
- }
+void GDScriptTokenizer::push_error(const Token &p_error) {
+ error_stack.push_back(p_error);
+}
- if (identifier) {
- _make_identifier(str);
- }
- INCPOS(str.length());
- return;
- }
+GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(CharType p_paren) {
+ if (paren_stack.empty()) {
+ return make_error(vformat("Closing \"%c\" doesn't have an opening counterpart.", p_paren));
+ }
+ Token error = make_error(vformat("Closing \"%c\" doesn't match the opening \"%c\".", p_paren, paren_stack.back()->get()));
+ paren_stack.pop_back(); // Remove opening one anyway.
+ return error;
+}
- _make_error("Unknown character");
- return;
+GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(CharType p_test, Token::Type p_double_type) {
+ const CharType *next = _current + 1;
+ int chars = 2; // Two already matched.
- } break;
+ // Test before consuming characters, since we don't want to consume more than needed.
+ while (*next == p_test) {
+ chars++;
+ next++;
+ }
+ if (chars >= 7) {
+ // It is a VCS conflict marker.
+ while (chars > 1) {
+ // Consume all characters (first was already consumed by scan()).
+ _advance();
+ chars--;
}
-
- INCPOS(1);
- break;
+ return make_token(Token::VCS_CONFLICT_MARKER);
+ } else {
+ // It is only a regular double character token, so we consume the second character.
+ _advance();
+ return make_token(p_double_type);
}
}
-void GDScriptTokenizerText::set_code(const String &p_code) {
- code = p_code;
- len = p_code.length();
- if (len) {
- _code = &code[0];
- } else {
- _code = nullptr;
+GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
+ if (!_is_alphanumeric(_peek())) {
+ push_error("Expected annotation identifier after \"@\".");
}
- code_pos = 0;
- line = 1; //it is stand-ar-ized that lines begin in 1 in code..
- column = 1; //the same holds for columns
- tk_rb_pos = 0;
- error_flag = false;
-#ifdef DEBUG_ENABLED
- ignore_warnings = false;
-#endif // DEBUG_ENABLED
- last_error = "";
- for (int i = 0; i < MAX_LOOKAHEAD + 1; i++) {
+ while (_is_alphanumeric(_peek())) {
+ // Consume all identifier characters.
_advance();
}
+ Token annotation = make_token(Token::ANNOTATION);
+ annotation.literal = StringName(annotation.source);
+ return annotation;
}
-GDScriptTokenizerText::Token GDScriptTokenizerText::get_token(int p_offset) const {
- ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, TK_ERROR);
- ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, TK_ERROR);
-
- int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
- return tk_rb[ofs].type;
-}
+GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
+#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \
+ KEYWORD_GROUP('a') \
+ KEYWORD("as", Token::AS) \
+ KEYWORD("and", Token::AND) \
+ KEYWORD("assert", Token::ASSERT) \
+ KEYWORD("await", Token::AWAIT) \
+ KEYWORD_GROUP('b') \
+ KEYWORD("break", Token::BREAK) \
+ KEYWORD("breakpoint", Token::BREAKPOINT) \
+ KEYWORD_GROUP('c') \
+ KEYWORD("class", Token::CLASS) \
+ KEYWORD("class_name", Token::CLASS_NAME) \
+ KEYWORD("const", Token::CONST) \
+ KEYWORD("continue", Token::CONTINUE) \
+ KEYWORD_GROUP('e') \
+ KEYWORD("elif", Token::ELIF) \
+ KEYWORD("else", Token::ELSE) \
+ KEYWORD("enum", Token::ENUM) \
+ KEYWORD("extends", Token::EXTENDS) \
+ KEYWORD_GROUP('f') \
+ KEYWORD("for", Token::FOR) \
+ KEYWORD("func", Token::FUNC) \
+ KEYWORD_GROUP('i') \
+ KEYWORD("if", Token::IF) \
+ KEYWORD("in", Token::IN) \
+ KEYWORD("is", Token::IS) \
+ KEYWORD_GROUP('m') \
+ KEYWORD("match", Token::MATCH) \
+ KEYWORD_GROUP('n') \
+ KEYWORD("namespace", Token::NAMESPACE) \
+ KEYWORD("not", Token::NOT) \
+ KEYWORD_GROUP('o') \
+ KEYWORD("or", Token::OR) \
+ KEYWORD_GROUP('p') \
+ KEYWORD("pass", Token::PASS) \
+ KEYWORD("preload", Token::PRELOAD) \
+ KEYWORD_GROUP('r') \
+ KEYWORD("return", Token::RETURN) \
+ KEYWORD_GROUP('s') \
+ KEYWORD("self", Token::SELF) \
+ KEYWORD("signal", Token::SIGNAL) \
+ KEYWORD("static", Token::STATIC) \
+ KEYWORD("super", Token::SUPER) \
+ KEYWORD_GROUP('t') \
+ KEYWORD("trait", Token::TRAIT) \
+ KEYWORD_GROUP('v') \
+ KEYWORD("var", Token::VAR) \
+ KEYWORD("void", Token::VOID) \
+ KEYWORD_GROUP('w') \
+ KEYWORD("while", Token::WHILE) \
+ KEYWORD_GROUP('y') \
+ KEYWORD("yield", Token::YIELD) \
+ KEYWORD_GROUP('I') \
+ KEYWORD("INF", Token::CONST_INF) \
+ KEYWORD_GROUP('N') \
+ KEYWORD("NAN", Token::CONST_NAN) \
+ KEYWORD_GROUP('P') \
+ KEYWORD("PI", Token::CONST_PI) \
+ KEYWORD_GROUP('T') \
+ KEYWORD("TAU", Token::CONST_TAU)
+
+#define MIN_KEYWORD_LENGTH 2
+#define MAX_KEYWORD_LENGTH 10
+
+ // Consume all alphanumeric characters.
+ while (_is_alphanumeric(_peek())) {
+ _advance();
+ }
-int GDScriptTokenizerText::get_token_line(int p_offset) const {
- ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, -1);
- ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, -1);
+ int length = _current - _start;
- int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
- return tk_rb[ofs].line;
-}
+ if (length == 1 && _peek(-1) == '_') {
+ // Lone underscore.
+ return make_token(Token::UNDERSCORE);
+ }
-int GDScriptTokenizerText::get_token_column(int p_offset) const {
- ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, -1);
- ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, -1);
+ String name(_start, length);
+ if (length < MIN_KEYWORD_LENGTH || length > MAX_KEYWORD_LENGTH) {
+ // Cannot be a keyword, as the length doesn't match any.
+ return make_identifier(name);
+ }
- int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
- return tk_rb[ofs].col;
-}
+ // Define some helper macros for the switch case.
+#define KEYWORD_GROUP_CASE(char) \
+ break; \
+ case char:
+#define KEYWORD(keyword, token_type) \
+ { \
+ const int keyword_length = sizeof(keyword) - 1; \
+ static_assert(keyword_length <= MAX_KEYWORD_LENGTH, "There's a keyword longer than the defined maximum length"); \
+ static_assert(keyword_length >= MIN_KEYWORD_LENGTH, "There's a keyword shorter than the defined minimum length"); \
+ if (keyword_length == length && name == keyword) { \
+ return make_token(token_type); \
+ } \
+ }
-const Variant &GDScriptTokenizerText::get_token_constant(int p_offset) const {
- ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, tk_rb[0].constant);
- ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, tk_rb[0].constant);
+ // Find if it's a keyword.
+ switch (_start[0]) {
+ default:
+ KEYWORDS(KEYWORD_GROUP_CASE, KEYWORD)
+ break;
+ }
- int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
- ERR_FAIL_COND_V(tk_rb[ofs].type != TK_CONSTANT, tk_rb[0].constant);
- return tk_rb[ofs].constant;
-}
+ // Check if it's a special literal
+ if (length == 4) {
+ if (name == "true") {
+ return make_literal(true);
+ } else if (name == "null") {
+ return make_literal(Variant());
+ }
+ } else if (length == 5) {
+ if (name == "false") {
+ return make_literal(false);
+ }
+ }
-StringName GDScriptTokenizerText::get_token_identifier(int p_offset) const {
- ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, StringName());
- ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, StringName());
+ // Not a keyword, so must be an identifier.
+ return make_identifier(name);
- int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
- ERR_FAIL_COND_V(tk_rb[ofs].type != TK_IDENTIFIER, StringName());
- return tk_rb[ofs].identifier;
+#undef KEYWORDS
+#undef MIN_KEYWORD_LENGTH
+#undef MAX_KEYWORD_LENGTH
+#undef KEYWORD_GROUP_CASE
+#undef KEYWORD
}
-GDScriptFunctions::Function GDScriptTokenizerText::get_token_built_in_func(int p_offset) const {
- ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, GDScriptFunctions::FUNC_MAX);
- ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, GDScriptFunctions::FUNC_MAX);
+void GDScriptTokenizer::newline(bool p_make_token) {
+ // Don't overwrite previous newline, nor create if we want a line contination.
+ if (p_make_token && !pending_newline && !line_continuation) {
+ Token newline(Token::NEWLINE);
+ newline.start_line = line;
+ newline.end_line = line;
+ newline.start_column = column - 1;
+ newline.end_column = column;
+ newline.leftmost_column = newline.start_column;
+ newline.rightmost_column = newline.end_column;
+ pending_newline = true;
+ last_newline = newline;
+ }
- int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
- ERR_FAIL_COND_V(tk_rb[ofs].type != TK_BUILT_IN_FUNC, GDScriptFunctions::FUNC_MAX);
- return tk_rb[ofs].func;
+ // Increment line/column counters.
+ line++;
+ column = 1;
+ leftmost_column = 1;
}
-Variant::Type GDScriptTokenizerText::get_token_type(int p_offset) const {
- ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, Variant::NIL);
- ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, Variant::NIL);
+GDScriptTokenizer::Token GDScriptTokenizer::number() {
+ int base = 10;
+ bool has_decimal = false;
+ bool has_exponent = false;
+ bool has_error = false;
+ bool (*digit_check_func)(CharType) = _is_digit;
+
+ if (_peek(-1) == '.') {
+ has_decimal = true;
+ } else if (_peek(-1) == '0') {
+ if (_peek() == 'x') {
+ // Hexadecimal.
+ base = 16;
+ digit_check_func = _is_hex_digit;
+ _advance();
+ } else if (_peek() == 'b') {
+ // Binary.
+ base = 2;
+ digit_check_func = _is_binary_digit;
+ _advance();
+ }
+ }
- int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
- ERR_FAIL_COND_V(tk_rb[ofs].type != TK_BUILT_IN_TYPE, Variant::NIL);
- return tk_rb[ofs].vtype;
-}
+ // Allow '_' to be used in a number, for readability.
+ while (digit_check_func(_peek()) || _peek() == '_') {
+ _advance();
+ }
-int GDScriptTokenizerText::get_token_line_indent(int p_offset) const {
- ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, 0);
- ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, 0);
+ // It might be a ".." token (instead of decimal point) so we check if it's not.
+ if (_peek() == '.' && _peek(1) != '.') {
+ if (base == 10 && !has_decimal) {
+ has_decimal = true;
+ } else if (base == 10) {
+ Token error = make_error("Cannot use a decimal point twice in a number.");
+ error.start_column = column;
+ error.leftmost_column = column;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ push_error(error);
+ has_error = true;
+ } else if (base == 16) {
+ Token error = make_error("Cannot use a decimal point in a hexadecimal number.");
+ error.start_column = column;
+ error.leftmost_column = column;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ push_error(error);
+ has_error = true;
+ } else {
+ Token error = make_error("Cannot use a decimal point in a binary number.");
+ error.start_column = column;
+ error.leftmost_column = column;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ push_error(error);
+ has_error = true;
+ }
+ if (!has_error) {
+ _advance();
- int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
- ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0);
- return tk_rb[ofs].constant.operator Vector2().x;
-}
+ // Consume decimal digits.
+ while (_is_digit(_peek()) || _peek() == '_') {
+ _advance();
+ }
+ }
+ }
+ if (base == 10) {
+ if (_peek() == 'e' || _peek() == 'E') {
+ has_exponent = true;
+ _advance();
+ if (_peek() == '+' || _peek() == '-') {
+ // Exponent sign.
+ _advance();
+ }
+ // Consume exponent digits.
+ while (_is_digit(_peek()) || _peek() == '_') {
+ _advance();
+ }
+ }
+ }
-int GDScriptTokenizerText::get_token_line_tab_indent(int p_offset) const {
- ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, 0);
- ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, 0);
+ // Detect extra decimal point.
+ if (!has_error && has_decimal && _peek() == '.' && _peek(1) != '.') {
+ Token error = make_error("Cannot use a decimal point twice in a number.");
+ error.start_column = column;
+ error.leftmost_column = column;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ push_error(error);
+ has_error = true;
+ } else if (_is_alphanumeric(_peek())) {
+ // Letter at the end of the number.
+ push_error("Invalid numeric notation.");
+ }
- int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
- ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0);
- return tk_rb[ofs].constant.operator Vector2().y;
+ // Create a string with the whole number.
+ int length = _current - _start;
+ String number = String(_start, length).replace("_", "");
+
+ // Convert to the appropriate literal type.
+ if (base == 16) {
+ int64_t value = number.hex_to_int();
+ return make_literal(value);
+ } else if (base == 2) {
+ int64_t value = number.bin_to_int();
+ return make_literal(value);
+ } else if (has_decimal || has_exponent) {
+ double value = number.to_double();
+ return make_literal(value);
+ } else {
+ int64_t value = number.to_int();
+ return make_literal(value);
+ }
}
-String GDScriptTokenizerText::get_token_error(int p_offset) const {
- ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, String());
- ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, String());
+GDScriptTokenizer::Token GDScriptTokenizer::string() {
+ enum StringType {
+ STRING_REGULAR,
+ STRING_NAME,
+ STRING_NODEPATH,
+ };
- int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
- ERR_FAIL_COND_V(tk_rb[ofs].type != TK_ERROR, String());
- return tk_rb[ofs].constant;
-}
+ bool is_multiline = false;
+ StringType type = STRING_REGULAR;
-void GDScriptTokenizerText::advance(int p_amount) {
- ERR_FAIL_COND(p_amount <= 0);
- for (int i = 0; i < p_amount; i++) {
+ if (_peek(-1) == '&') {
+ type = STRING_NAME;
+ _advance();
+ } else if (_peek(-1) == '^') {
+ type = STRING_NODEPATH;
_advance();
}
-}
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////
-
-#define BYTECODE_VERSION 13
-Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) {
- const uint8_t *buf = p_buffer.ptr();
- int total_len = p_buffer.size();
- ERR_FAIL_COND_V(p_buffer.size() < 24 || p_buffer[0] != 'G' || p_buffer[1] != 'D' || p_buffer[2] != 'S' || p_buffer[3] != 'C', ERR_INVALID_DATA);
+ CharType quote_char = _peek(-1);
- int version = decode_uint32(&buf[4]);
- ERR_FAIL_COND_V_MSG(version > BYTECODE_VERSION, ERR_INVALID_DATA, "Bytecode is too recent! Please use a newer engine version.");
-
- int identifier_count = decode_uint32(&buf[8]);
- int constant_count = decode_uint32(&buf[12]);
- int line_count = decode_uint32(&buf[16]);
- int token_count = decode_uint32(&buf[20]);
+ if (_peek() == quote_char && _peek(1) == quote_char) {
+ is_multiline = true;
+ // Consume all quotes.
+ _advance();
+ _advance();
+ }
- const uint8_t *b = &buf[24];
- total_len -= 24;
+ String result;
- identifiers.resize(identifier_count);
- for (int i = 0; i < identifier_count; i++) {
- int len = decode_uint32(b);
- ERR_FAIL_COND_V(len > total_len, ERR_INVALID_DATA);
- b += 4;
- Vector<uint8_t> cs;
- cs.resize(len);
- for (int j = 0; j < len; j++) {
- cs.write[j] = b[j] ^ 0xb6;
+ for (;;) {
+ // Consume actual string.
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
}
- cs.write[cs.size() - 1] = 0;
- String s;
- s.parse_utf8((const char *)cs.ptr());
- b += len;
- total_len -= len + 4;
- identifiers.write[i] = s;
- }
+ CharType ch = _peek();
- constants.resize(constant_count);
- for (int i = 0; i < constant_count; i++) {
- Variant v;
- int len;
- // An object cannot be constant, never decode objects
- Error err = decode_variant(v, b, total_len, &len, false);
- if (err) {
- return err;
- }
- b += len;
- total_len -= len;
- constants.write[i] = v;
- }
+ if (ch == '\\') {
+ // Escape pattern.
+ _advance();
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
- ERR_FAIL_COND_V(line_count * 8 > total_len, ERR_INVALID_DATA);
+ // Grab escape character.
+ CharType code = _peek();
+ _advance();
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
- for (int i = 0; i < line_count; i++) {
- uint32_t token = decode_uint32(b);
- b += 4;
- uint32_t linecol = decode_uint32(b);
- b += 4;
+ CharType escaped = 0;
+ bool valid_escape = true;
- lines.insert(token, linecol);
- total_len -= 8;
- }
+ switch (code) {
+ case 'a':
+ escaped = '\a';
+ break;
+ case 'b':
+ escaped = '\b';
+ break;
+ case 'f':
+ escaped = '\f';
+ break;
+ case 'n':
+ escaped = '\n';
+ break;
+ case 'r':
+ escaped = '\r';
+ break;
+ case 't':
+ escaped = '\t';
+ break;
+ case 'v':
+ escaped = '\v';
+ break;
+ case '\'':
+ escaped = '\'';
+ break;
+ case '\"':
+ escaped = '\"';
+ break;
+ case '\\':
+ escaped = '\\';
+ break;
+ case 'u':
+ // Hexadecimal sequence.
+ for (int i = 0; i < 4; i++) {
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
- tokens.resize(token_count);
+ CharType digit = _peek();
+ CharType value = 0;
+ if (digit >= '0' && digit <= '9') {
+ value = digit - '0';
+ } else if (digit >= 'a' && digit <= 'f') {
+ value = digit - 'a';
+ value += 10;
+ } else if (digit >= 'A' && digit <= 'F') {
+ value = digit - 'A';
+ value += 10;
+ } else {
+ // Make error, but keep parsing the string.
+ Token error = make_error("Invalid hexadecimal digit in unicode escape sequence.");
+ error.start_column = column;
+ error.leftmost_column = error.start_column;
+ error.end_column = column + 1;
+ error.rightmost_column = error.end_column;
+ push_error(error);
+ valid_escape = false;
+ break;
+ }
- for (int i = 0; i < token_count; i++) {
- ERR_FAIL_COND_V(total_len < 1, ERR_INVALID_DATA);
+ escaped <<= 4;
+ escaped |= value;
- if ((*b) & TOKEN_BYTE_MASK) { //little endian always
- ERR_FAIL_COND_V(total_len < 4, ERR_INVALID_DATA);
+ _advance();
+ }
+ break;
+ case '\r':
+ if (_peek() != '\n') {
+ // Carriage return without newline in string. (???)
+ // Just add it to the string and keep going.
+ result += ch;
+ _advance();
+ break;
+ }
+ [[fallthrough]];
+ case '\n':
+ // Escaping newline.
+ newline(false);
+ valid_escape = false; // Don't add to the string.
+ break;
+ default:
+ Token error = make_error("Invalid escape in string.");
+ error.start_column = column - 2;
+ error.leftmost_column = error.start_column;
+ push_error(error);
+ valid_escape = false;
+ break;
+ }
- tokens.write[i] = decode_uint32(b) & ~TOKEN_BYTE_MASK;
- b += 4;
+ if (valid_escape) {
+ result += escaped;
+ }
+ } else if (ch == quote_char) {
+ _advance();
+ if (is_multiline) {
+ if (_peek() == quote_char && _peek(1) == quote_char) {
+ // Ended the multiline string. Consume all quotes.
+ _advance();
+ _advance();
+ break;
+ }
+ } else {
+ // Ended single-line string.
+ break;
+ }
} else {
- tokens.write[i] = *b;
- b += 1;
- total_len--;
+ result += ch;
+ _advance();
+ if (ch == '\n') {
+ newline(false);
+ }
}
}
- token = 0;
+ // Make the literal.
+ Variant string;
+ switch (type) {
+ case STRING_NAME:
+ string = StringName(result);
+ break;
+ case STRING_NODEPATH:
+ string = NodePath(result);
+ break;
+ case STRING_REGULAR:
+ string = result;
+ break;
+ }
- return OK;
+ return make_literal(string);
}
-Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code) {
- Vector<uint8_t> buf;
+void GDScriptTokenizer::check_indent() {
+ ERR_FAIL_COND_MSG(column != 1, "Checking tokenizer indentation in the middle of a line.");
- Map<StringName, int> identifier_map;
- HashMap<Variant, int, VariantHasher, VariantComparator> constant_map;
- Map<uint32_t, int> line_map;
- Vector<uint32_t> token_array;
+ if (_is_at_end()) {
+ // Send dedents for every indent level.
+ pending_indents -= indent_level();
+ indent_stack.clear();
+ return;
+ }
- GDScriptTokenizerText tt;
- tt.set_code(p_code);
- int line = -1;
+ for (;;) {
+ CharType current_indent_char = _peek();
+ int indent_count = 0;
- while (true) {
- if (tt.get_token_line() != line) {
- line = tt.get_token_line();
- line_map[line] = token_array.size();
+ if (current_indent_char != ' ' && current_indent_char != '\t' && current_indent_char != '\r' && current_indent_char != '\n' && current_indent_char != '#') {
+ // First character of the line is not whitespace, so we clear all indentation levels.
+ // Unless we are in a continuation or in multiline mode (inside expression).
+ if (line_continuation || multiline_mode) {
+ return;
+ }
+ pending_indents -= indent_level();
+ indent_stack.clear();
+ return;
}
- uint32_t token = tt.get_token();
- switch (tt.get_token()) {
- case TK_IDENTIFIER: {
- StringName id = tt.get_token_identifier();
- if (!identifier_map.has(id)) {
- int idx = identifier_map.size();
- identifier_map[id] = idx;
- }
- token |= identifier_map[id] << TOKEN_BITS;
- } break;
- case TK_CONSTANT: {
- const Variant &c = tt.get_token_constant();
- if (!constant_map.has(c)) {
- int idx = constant_map.size();
- constant_map[c] = idx;
- }
- token |= constant_map[c] << TOKEN_BITS;
- } break;
- case TK_BUILT_IN_TYPE: {
- token |= tt.get_token_type() << TOKEN_BITS;
- } break;
- case TK_BUILT_IN_FUNC: {
- token |= tt.get_token_built_in_func() << TOKEN_BITS;
-
- } break;
- case TK_NEWLINE: {
- token |= tt.get_token_line_indent() << TOKEN_BITS;
- } break;
- case TK_ERROR: {
- ERR_FAIL_V(Vector<uint8_t>());
- } break;
- default: {
+ if (_peek() == '\r') {
+ _advance();
+ if (_peek() != '\n') {
+ push_error("Stray carriage return character in source code.");
}
- };
-
- token_array.push_back(token);
-
- if (tt.get_token() == TK_EOF) {
- break;
}
- tt.advance();
- }
-
- //reverse maps
-
- Map<int, StringName> rev_identifier_map;
- for (Map<StringName, int>::Element *E = identifier_map.front(); E; E = E->next()) {
- rev_identifier_map[E->get()] = E->key();
- }
+ if (_peek() == '\n') {
+ // Empty line, keep going.
+ _advance();
+ newline(false);
+ continue;
+ }
- Map<int, Variant> rev_constant_map;
- const Variant *K = nullptr;
- while ((K = constant_map.next(K))) {
- rev_constant_map[constant_map[*K]] = *K;
- }
+ // Check indent level.
+ bool mixed = false;
+ while (!_is_at_end()) {
+ CharType space = _peek();
+ if (space == '\t') {
+ // Consider individual tab columns.
+ column += tab_size - 1;
+ indent_count += tab_size;
+ } else if (space == ' ') {
+ indent_count += 1;
+ } else {
+ break;
+ }
+ mixed = mixed || space != current_indent_char;
+ _advance();
+ }
- Map<int, uint32_t> rev_line_map;
- for (Map<uint32_t, int>::Element *E = line_map.front(); E; E = E->next()) {
- rev_line_map[E->get()] = E->key();
- }
+ if (mixed) {
+ Token error = make_error("Mixed use of tabs and spaces for indentation.");
+ error.start_line = line;
+ error.start_column = 1;
+ error.leftmost_column = 1;
+ error.rightmost_column = column;
+ push_error(error);
+ }
- //save header
- buf.resize(24);
- buf.write[0] = 'G';
- buf.write[1] = 'D';
- buf.write[2] = 'S';
- buf.write[3] = 'C';
- encode_uint32(BYTECODE_VERSION, &buf.write[4]);
- encode_uint32(identifier_map.size(), &buf.write[8]);
- encode_uint32(constant_map.size(), &buf.write[12]);
- encode_uint32(line_map.size(), &buf.write[16]);
- encode_uint32(token_array.size(), &buf.write[20]);
-
- //save identifiers
-
- for (Map<int, StringName>::Element *E = rev_identifier_map.front(); E; E = E->next()) {
- CharString cs = String(E->get()).utf8();
- int len = cs.length() + 1;
- int extra = 4 - (len % 4);
- if (extra == 4) {
- extra = 0;
+ if (_is_at_end()) {
+ // Reached the end with an empty line, so just dedent as much as needed.
+ pending_indents -= indent_level();
+ indent_stack.clear();
+ return;
}
- uint8_t ibuf[4];
- encode_uint32(len + extra, ibuf);
- for (int i = 0; i < 4; i++) {
- buf.push_back(ibuf[i]);
+ if (_peek() == '\r') {
+ _advance();
+ if (_peek() != '\n') {
+ push_error("Stray carriage return character in source code.");
+ }
}
- for (int i = 0; i < len; i++) {
- buf.push_back(cs[i] ^ 0xb6);
+ if (_peek() == '\n') {
+ // Empty line, keep going.
+ _advance();
+ newline(false);
+ continue;
}
- for (int i = 0; i < extra; i++) {
- buf.push_back(0 ^ 0xb6);
+ if (_peek() == '#') {
+ // Comment. Advance to the next line.
+ while (_peek() != '\n' && !_is_at_end()) {
+ _advance();
+ }
+ if (_is_at_end()) {
+ // Reached the end with an empty line, so just dedent as much as needed.
+ pending_indents -= indent_level();
+ indent_stack.clear();
+ return;
+ }
+ _advance(); // Consume '\n'.
+ newline(false);
+ continue;
}
- }
- for (Map<int, Variant>::Element *E = rev_constant_map.front(); E; E = E->next()) {
- int len;
- // Objects cannot be constant, never encode objects
- Error err = encode_variant(E->get(), nullptr, len, false);
- ERR_FAIL_COND_V_MSG(err != OK, Vector<uint8_t>(), "Error when trying to encode Variant.");
- int pos = buf.size();
- buf.resize(pos + len);
- encode_variant(E->get(), &buf.write[pos], len, false);
- }
+ if (line_continuation || multiline_mode) {
+ // We cleared up all the whitespace at the beginning of the line.
+ // But if this is a continuation or multiline mode and we don't want any indentation change.
+ return;
+ }
- for (Map<int, uint32_t>::Element *E = rev_line_map.front(); E; E = E->next()) {
- uint8_t ibuf[8];
- encode_uint32(E->key(), &ibuf[0]);
- encode_uint32(E->get(), &ibuf[4]);
- for (int i = 0; i < 8; i++) {
- buf.push_back(ibuf[i]);
+ // Check if indentation character is consistent.
+ if (indent_char == '\0') {
+ // First time indenting, choose character now.
+ indent_char = current_indent_char;
+ } else if (current_indent_char != indent_char) {
+ Token error = make_error(vformat("Used \"%c\" for indentation instead \"%c\" as used before in the file.", String(&current_indent_char, 1).c_escape(), String(&indent_char, 1).c_escape()));
+ error.start_line = line;
+ error.start_column = 1;
+ error.leftmost_column = 1;
+ error.rightmost_column = column;
+ push_error(error);
}
- }
- for (int i = 0; i < token_array.size(); i++) {
- uint32_t token = token_array[i];
+ // Now we can do actual indentation changes.
- if (token & ~TOKEN_MASK) {
- uint8_t buf4[4];
- encode_uint32(token_array[i] | TOKEN_BYTE_MASK, &buf4[0]);
- for (int j = 0; j < 4; j++) {
- buf.push_back(buf4[j]);
- }
+ // Check if indent or dedent.
+ int previous_indent = 0;
+ if (indent_level() > 0) {
+ previous_indent = indent_stack.back()->get();
+ }
+ if (indent_count == previous_indent) {
+ // No change in indentation.
+ return;
+ }
+ if (indent_count > previous_indent) {
+ // Indentation increased.
+ indent_stack.push_back(indent_count);
+ pending_indents++;
} else {
- buf.push_back(token);
+ // Indentation decreased (dedent).
+ if (indent_level() == 0) {
+ push_error("Tokenizer bug: trying to dedent without previous indent.");
+ return;
+ }
+ while (indent_level() > 0 && indent_stack.back()->get() > indent_count) {
+ indent_stack.pop_back();
+ pending_indents--;
+ }
+ if ((indent_level() > 0 && indent_stack.back()->get() != indent_count) || (indent_level() == 0 && indent_count != 0)) {
+ // Mismatched indentation alignment.
+ Token error = make_error("Unindent doesn't match the previous indentation level.");
+ error.start_line = line;
+ error.start_column = 1;
+ error.leftmost_column = 1;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ push_error(error);
+ // Still, we'll be lenient and keep going, so keep this level in the stack.
+ indent_stack.push_back(indent_count);
+ }
}
+ break; // Get out of the loop in any case.
}
-
- return buf;
}
-GDScriptTokenizerBuffer::Token GDScriptTokenizerBuffer::get_token(int p_offset) const {
- int offset = token + p_offset;
-
- if (offset < 0 || offset >= tokens.size()) {
- return TK_EOF;
+void GDScriptTokenizer::_skip_whitespace() {
+ if (pending_indents != 0) {
+ // Still have some indent/dedent tokens to give.
+ return;
}
- return GDScriptTokenizerBuffer::Token(tokens[offset] & TOKEN_MASK);
-}
-
-StringName GDScriptTokenizerBuffer::get_token_identifier(int p_offset) const {
- int offset = token + p_offset;
+ bool is_bol = column == 1; // Beginning of line.
- ERR_FAIL_INDEX_V(offset, tokens.size(), StringName());
- uint32_t identifier = tokens[offset] >> TOKEN_BITS;
- ERR_FAIL_UNSIGNED_INDEX_V(identifier, (uint32_t)identifiers.size(), StringName());
+ if (is_bol) {
+ check_indent();
+ return;
+ }
- return identifiers[identifier];
+ for (;;) {
+ CharType c = _peek();
+ switch (c) {
+ case ' ':
+ _advance();
+ break;
+ case '\t':
+ _advance();
+ // Consider individual tab columns.
+ column += tab_size - 1;
+ break;
+ case '\r':
+ _advance(); // Consume either way.
+ if (_peek() != '\n') {
+ push_error("Stray carriage return character in source code.");
+ return;
+ }
+ break;
+ case '\n':
+ _advance();
+ newline(!is_bol); // Don't create new line token if line is empty.
+ check_indent();
+ break;
+ case '#':
+ // Comment.
+ while (_peek() != '\n' && !_is_at_end()) {
+ _advance();
+ }
+ if (_is_at_end()) {
+ return;
+ }
+ _advance(); // Consume '\n'
+ newline(!is_bol);
+ check_indent();
+ break;
+ default:
+ return;
+ }
+ }
}
-GDScriptFunctions::Function GDScriptTokenizerBuffer::get_token_built_in_func(int p_offset) const {
- int offset = token + p_offset;
- ERR_FAIL_INDEX_V(offset, tokens.size(), GDScriptFunctions::FUNC_MAX);
- return GDScriptFunctions::Function(tokens[offset] >> TOKEN_BITS);
-}
+GDScriptTokenizer::Token GDScriptTokenizer::scan() {
+ if (has_error()) {
+ return pop_error();
+ }
-Variant::Type GDScriptTokenizerBuffer::get_token_type(int p_offset) const {
- int offset = token + p_offset;
- ERR_FAIL_INDEX_V(offset, tokens.size(), Variant::NIL);
+ _skip_whitespace();
- return Variant::Type(tokens[offset] >> TOKEN_BITS);
-}
+ if (pending_newline) {
+ pending_newline = false;
+ if (!multiline_mode) {
+ // Don't return newline tokens on multine mode.
+ return last_newline;
+ }
+ }
-int GDScriptTokenizerBuffer::get_token_line(int p_offset) const {
- int offset = token + p_offset;
- int pos = lines.find_nearest(offset);
+ // Check for potential errors after skipping whitespace().
+ if (has_error()) {
+ return pop_error();
+ }
- if (pos < 0) {
- return -1;
+ _start = _current;
+ start_line = line;
+ start_column = column;
+ leftmost_column = column;
+ rightmost_column = column;
+
+ if (pending_indents != 0) {
+ // Adjust position for indent.
+ _start -= start_column - 1;
+ start_column = 1;
+ leftmost_column = 1;
+ if (pending_indents > 0) {
+ // Indents.
+ pending_indents--;
+ return make_token(Token::INDENT);
+ } else {
+ // Dedents.
+ pending_indents++;
+ Token dedent = make_token(Token::DEDENT);
+ dedent.end_column += 1;
+ dedent.rightmost_column += 1;
+ return dedent;
+ }
}
- if (pos >= lines.size()) {
- pos = lines.size() - 1;
+
+ if (_is_at_end()) {
+ return make_token(Token::TK_EOF);
}
- uint32_t l = lines.getv(pos);
- return l & TOKEN_LINE_MASK;
-}
+ const CharType c = _advance();
-int GDScriptTokenizerBuffer::get_token_column(int p_offset) const {
- int offset = token + p_offset;
- int pos = lines.find_nearest(offset);
- if (pos < 0) {
- return -1;
- }
- if (pos >= lines.size()) {
- pos = lines.size() - 1;
+ if (c == '\\') {
+ // Line continuation with backslash.
+ if (_peek() == '\r') {
+ if (_peek(1) != '\n') {
+ return make_error("Unexpected carriage return character.");
+ }
+ _advance();
+ }
+ if (_peek() != '\n') {
+ return make_error("Expected new line after \"\\\".");
+ }
+ _advance();
+ newline(false);
+ line_continuation = true;
+ return scan(); // Recurse to get next token.
}
- uint32_t l = lines.getv(pos);
- return l >> TOKEN_LINE_BITS;
-}
+ line_continuation = false;
-int GDScriptTokenizerBuffer::get_token_line_indent(int p_offset) const {
- int offset = token + p_offset;
- ERR_FAIL_INDEX_V(offset, tokens.size(), 0);
- return tokens[offset] >> TOKEN_BITS;
-}
+ if (_is_digit(c)) {
+ return number();
+ } else if (_is_alphanumeric(c)) {
+ return potential_identifier();
+ }
-const Variant &GDScriptTokenizerBuffer::get_token_constant(int p_offset) const {
- int offset = token + p_offset;
- ERR_FAIL_INDEX_V(offset, tokens.size(), nil);
- uint32_t constant = tokens[offset] >> TOKEN_BITS;
- ERR_FAIL_UNSIGNED_INDEX_V(constant, (uint32_t)constants.size(), nil);
- return constants[constant];
-}
+ switch (c) {
+ // String literals.
+ case '"':
+ case '\'':
+ return string();
+
+ // Annotation.
+ case '@':
+ return annotation();
+
+ // Single characters.
+ case '~':
+ return make_token(Token::TILDE);
+ case ',':
+ return make_token(Token::COMMA);
+ case ':':
+ return make_token(Token::COLON);
+ case ';':
+ return make_token(Token::SEMICOLON);
+ case '$':
+ return make_token(Token::DOLLAR);
+ case '?':
+ return make_token(Token::QUESTION_MARK);
+ case '`':
+ return make_token(Token::BACKTICK);
+
+ // Parens.
+ case '(':
+ push_paren('(');
+ return make_token(Token::PARENTHESIS_OPEN);
+ case '[':
+ push_paren('[');
+ return make_token(Token::BRACKET_OPEN);
+ case '{':
+ push_paren('{');
+ return make_token(Token::BRACE_OPEN);
+ case ')':
+ if (!pop_paren('(')) {
+ return make_paren_error(c);
+ }
+ return make_token(Token::PARENTHESIS_CLOSE);
+ case ']':
+ if (!pop_paren('[')) {
+ return make_paren_error(c);
+ }
+ return make_token(Token::BRACKET_CLOSE);
+ case '}':
+ if (!pop_paren('{')) {
+ return make_paren_error(c);
+ }
+ return make_token(Token::BRACE_CLOSE);
+
+ // Double characters.
+ case '!':
+ if (_peek() == '=') {
+ _advance();
+ return make_token(Token::BANG_EQUAL);
+ } else {
+ return make_token(Token::BANG);
+ }
+ case '.':
+ if (_peek() == '.') {
+ _advance();
+ return make_token(Token::PERIOD_PERIOD);
+ } else if (_is_digit(_peek())) {
+ // Number starting with '.'.
+ return number();
+ } else {
+ return make_token(Token::PERIOD);
+ }
+ case '+':
+ if (_peek() == '=') {
+ _advance();
+ return make_token(Token::PLUS_EQUAL);
+ } else {
+ return make_token(Token::PLUS);
+ }
+ case '-':
+ if (_peek() == '=') {
+ _advance();
+ return make_token(Token::MINUS_EQUAL);
+ } else if (_peek() == '>') {
+ _advance();
+ return make_token(Token::FORWARD_ARROW);
+ } else {
+ return make_token(Token::MINUS);
+ }
+ case '*':
+ if (_peek() == '=') {
+ _advance();
+ return make_token(Token::STAR_EQUAL);
+ } else {
+ return make_token(Token::STAR);
+ }
+ case '/':
+ if (_peek() == '=') {
+ _advance();
+ return make_token(Token::SLASH_EQUAL);
+ } else {
+ return make_token(Token::SLASH);
+ }
+ case '%':
+ if (_peek() == '=') {
+ _advance();
+ return make_token(Token::PERCENT_EQUAL);
+ } else {
+ return make_token(Token::PERCENT);
+ }
+ case '^':
+ if (_peek() == '=') {
+ _advance();
+ return make_token(Token::CARET_EQUAL);
+ } else if (_peek() == '"' || _peek() == '\'') {
+ // Node path
+ return string();
+ } else {
+ return make_token(Token::CARET);
+ }
+ case '&':
+ if (_peek() == '&') {
+ _advance();
+ return make_token(Token::AMPERSAND_AMPERSAND);
+ } else if (_peek() == '=') {
+ _advance();
+ return make_token(Token::AMPERSAND_EQUAL);
+ } else if (_peek() == '"' || _peek() == '\'') {
+ // String Name
+ return string();
+ } else {
+ return make_token(Token::AMPERSAND);
+ }
+ case '|':
+ if (_peek() == '|') {
+ _advance();
+ return make_token(Token::PIPE_PIPE);
+ } else if (_peek() == '=') {
+ _advance();
+ return make_token(Token::PIPE_EQUAL);
+ } else {
+ return make_token(Token::PIPE);
+ }
-String GDScriptTokenizerBuffer::get_token_error(int p_offset) const {
- ERR_FAIL_V(String());
-}
+ // Potential VCS conflict markers.
+ case '=':
+ if (_peek() == '=') {
+ return check_vcs_marker('=', Token::EQUAL_EQUAL);
+ } else {
+ return make_token(Token::EQUAL);
+ }
+ case '<':
+ if (_peek() == '=') {
+ _advance();
+ return make_token(Token::LESS_EQUAL);
+ } else if (_peek() == '<') {
+ if (_peek(1) == '=') {
+ _advance();
+ _advance(); // Advance both '<' and '='
+ return make_token(Token::LESS_LESS_EQUAL);
+ } else {
+ return check_vcs_marker('<', Token::LESS_LESS);
+ }
+ } else {
+ return make_token(Token::LESS);
+ }
+ case '>':
+ if (_peek() == '=') {
+ _advance();
+ return make_token(Token::GREATER_EQUAL);
+ } else if (_peek() == '>') {
+ if (_peek(1) == '=') {
+ _advance();
+ _advance(); // Advance both '>' and '='
+ return make_token(Token::GREATER_GREATER_EQUAL);
+ } else {
+ return check_vcs_marker('>', Token::GREATER_GREATER);
+ }
+ } else {
+ return make_token(Token::GREATER);
+ }
-void GDScriptTokenizerBuffer::advance(int p_amount) {
- ERR_FAIL_INDEX(p_amount + token, tokens.size());
- token += p_amount;
+ default:
+ return make_error(vformat(R"(Unknown character "%s".")", String(&c, 1)));
+ }
}
-GDScriptTokenizerBuffer::GDScriptTokenizerBuffer() {
- token = 0;
+GDScriptTokenizer::GDScriptTokenizer() {
+#ifdef TOOLS_ENABLED
+ if (EditorSettings::get_singleton()) {
+ tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size");
+ }
+#endif // TOOLS_ENABLED
}
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 32603c010f..059a226924 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -31,268 +31,221 @@
#ifndef GDSCRIPT_TOKENIZER_H
#define GDSCRIPT_TOKENIZER_H
-#include "core/pair.h"
+#include "core/list.h"
#include "core/set.h"
-#include "core/string_name.h"
-#include "core/ustring.h"
#include "core/variant.h"
-#include "core/vmap.h"
-#include "gdscript_functions.h"
+#include "core/vector.h"
class GDScriptTokenizer {
public:
- enum Token {
-
- TK_EMPTY,
- TK_IDENTIFIER,
- TK_CONSTANT,
- TK_SELF,
- TK_BUILT_IN_TYPE,
- TK_BUILT_IN_FUNC,
- TK_OP_IN,
- TK_OP_EQUAL,
- TK_OP_NOT_EQUAL,
- TK_OP_LESS,
- TK_OP_LESS_EQUAL,
- TK_OP_GREATER,
- TK_OP_GREATER_EQUAL,
- TK_OP_AND,
- TK_OP_OR,
- TK_OP_NOT,
- TK_OP_ADD,
- TK_OP_SUB,
- TK_OP_MUL,
- TK_OP_DIV,
- TK_OP_MOD,
- TK_OP_SHIFT_LEFT,
- TK_OP_SHIFT_RIGHT,
- TK_OP_ASSIGN,
- TK_OP_ASSIGN_ADD,
- TK_OP_ASSIGN_SUB,
- TK_OP_ASSIGN_MUL,
- TK_OP_ASSIGN_DIV,
- TK_OP_ASSIGN_MOD,
- TK_OP_ASSIGN_SHIFT_LEFT,
- TK_OP_ASSIGN_SHIFT_RIGHT,
- TK_OP_ASSIGN_BIT_AND,
- TK_OP_ASSIGN_BIT_OR,
- TK_OP_ASSIGN_BIT_XOR,
- TK_OP_BIT_AND,
- TK_OP_BIT_OR,
- TK_OP_BIT_XOR,
- TK_OP_BIT_INVERT,
- //TK_OP_PLUS_PLUS,
- //TK_OP_MINUS_MINUS,
- TK_CF_IF,
- TK_CF_ELIF,
- TK_CF_ELSE,
- TK_CF_FOR,
- TK_CF_WHILE,
- TK_CF_BREAK,
- TK_CF_CONTINUE,
- TK_CF_PASS,
- TK_CF_RETURN,
- TK_CF_MATCH,
- TK_PR_FUNCTION,
- TK_PR_CLASS,
- TK_PR_CLASS_NAME,
- TK_PR_EXTENDS,
- TK_PR_IS,
- TK_PR_ONREADY,
- TK_PR_TOOL,
- TK_PR_STATIC,
- TK_PR_EXPORT,
- TK_PR_SETGET,
- TK_PR_CONST,
- TK_PR_VAR,
- TK_PR_AS,
- TK_PR_VOID,
- TK_PR_ENUM,
- TK_PR_PRELOAD,
- TK_PR_ASSERT,
- TK_PR_YIELD,
- TK_PR_SIGNAL,
- TK_PR_BREAKPOINT,
- TK_PR_REMOTE,
- TK_PR_MASTER,
- TK_PR_PUPPET,
- TK_PR_REMOTESYNC,
- TK_PR_MASTERSYNC,
- TK_PR_PUPPETSYNC,
- TK_BRACKET_OPEN,
- TK_BRACKET_CLOSE,
- TK_CURLY_BRACKET_OPEN,
- TK_CURLY_BRACKET_CLOSE,
- TK_PARENTHESIS_OPEN,
- TK_PARENTHESIS_CLOSE,
- TK_COMMA,
- TK_SEMICOLON,
- TK_PERIOD,
- TK_QUESTION_MARK,
- TK_COLON,
- TK_DOLLAR,
- TK_FORWARD_ARROW,
- TK_NEWLINE,
- TK_CONST_PI,
- TK_CONST_TAU,
- TK_WILDCARD,
- TK_CONST_INF,
- TK_CONST_NAN,
- TK_ERROR,
- TK_EOF,
- TK_CURSOR, //used for code completion
- TK_MAX
- };
-
-protected:
- enum StringMode {
- STRING_SINGLE_QUOTE,
- STRING_DOUBLE_QUOTE,
- STRING_MULTILINE
+ enum CursorPlace {
+ CURSOR_NONE,
+ CURSOR_BEGINNING,
+ CURSOR_MIDDLE,
+ CURSOR_END,
};
- static const char *token_names[TK_MAX];
-
-public:
- static const char *get_token_name(Token p_token);
-
- bool is_token_literal(int p_offset = 0, bool variable_safe = false) const;
- StringName get_token_literal(int p_offset = 0) const;
-
- virtual const Variant &get_token_constant(int p_offset = 0) const = 0;
- virtual Token get_token(int p_offset = 0) const = 0;
- virtual StringName get_token_identifier(int p_offset = 0) const = 0;
- virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const = 0;
- virtual Variant::Type get_token_type(int p_offset = 0) const = 0;
- virtual int get_token_line(int p_offset = 0) const = 0;
- virtual int get_token_column(int p_offset = 0) const = 0;
- virtual int get_token_line_indent(int p_offset = 0) const = 0;
- virtual int get_token_line_tab_indent(int p_offset = 0) const = 0;
- virtual String get_token_error(int p_offset = 0) const = 0;
- virtual void advance(int p_amount = 1) = 0;
-#ifdef DEBUG_ENABLED
- virtual const Vector<Pair<int, String>> &get_warning_skips() const = 0;
- virtual const Set<String> &get_warning_global_skips() const = 0;
- virtual bool is_ignoring_warnings() const = 0;
-#endif // DEBUG_ENABLED
-
- virtual ~GDScriptTokenizer() {}
-};
-
-class GDScriptTokenizerText : public GDScriptTokenizer {
- enum {
- MAX_LOOKAHEAD = 4,
- TK_RB_SIZE = MAX_LOOKAHEAD * 2 + 1
+ struct Token {
+ enum Type {
+ EMPTY,
+ // Basic
+ ANNOTATION,
+ IDENTIFIER,
+ LITERAL,
+ // Comparison
+ LESS,
+ LESS_EQUAL,
+ GREATER,
+ GREATER_EQUAL,
+ EQUAL_EQUAL,
+ BANG_EQUAL,
+ // Logical
+ AND,
+ OR,
+ NOT,
+ AMPERSAND_AMPERSAND,
+ PIPE_PIPE,
+ BANG,
+ // Bitwise
+ AMPERSAND,
+ PIPE,
+ TILDE,
+ CARET,
+ LESS_LESS,
+ GREATER_GREATER,
+ // Math
+ PLUS,
+ MINUS,
+ STAR,
+ SLASH,
+ PERCENT,
+ // Assignment
+ EQUAL,
+ PLUS_EQUAL,
+ MINUS_EQUAL,
+ STAR_EQUAL,
+ SLASH_EQUAL,
+ PERCENT_EQUAL,
+ LESS_LESS_EQUAL,
+ GREATER_GREATER_EQUAL,
+ AMPERSAND_EQUAL,
+ PIPE_EQUAL,
+ CARET_EQUAL,
+ // Control flow
+ IF,
+ ELIF,
+ ELSE,
+ FOR,
+ WHILE,
+ BREAK,
+ CONTINUE,
+ PASS,
+ RETURN,
+ MATCH,
+ // Keywords
+ AS,
+ ASSERT,
+ AWAIT,
+ BREAKPOINT,
+ CLASS,
+ CLASS_NAME,
+ CONST,
+ ENUM,
+ EXTENDS,
+ FUNC,
+ IN,
+ IS,
+ NAMESPACE,
+ PRELOAD,
+ SELF,
+ SIGNAL,
+ STATIC,
+ SUPER,
+ TRAIT,
+ VAR,
+ VOID,
+ YIELD,
+ // Punctuation
+ BRACKET_OPEN,
+ BRACKET_CLOSE,
+ BRACE_OPEN,
+ BRACE_CLOSE,
+ PARENTHESIS_OPEN,
+ PARENTHESIS_CLOSE,
+ COMMA,
+ SEMICOLON,
+ PERIOD,
+ PERIOD_PERIOD,
+ COLON,
+ DOLLAR,
+ FORWARD_ARROW,
+ UNDERSCORE,
+ // Whitespace
+ NEWLINE,
+ INDENT,
+ DEDENT,
+ // Constants
+ CONST_PI,
+ CONST_TAU,
+ CONST_INF,
+ CONST_NAN,
+ // Error message improvement
+ VCS_CONFLICT_MARKER,
+ BACKTICK,
+ QUESTION_MARK,
+ // Special
+ ERROR,
+ TK_EOF, // "EOF" is reserved
+ TK_MAX
+ };
- };
+ Type type = EMPTY;
+ Variant literal;
+ int start_line = 0, end_line = 0, start_column = 0, end_column = 0;
+ int leftmost_column = 0, rightmost_column = 0; // Column span for multiline tokens.
+ int cursor_position = -1;
+ CursorPlace cursor_place = CURSOR_NONE;
+ String source;
+
+ const char *get_name() const;
+ // TODO: Allow some keywords as identifiers?
+ bool is_identifier() const { return type == IDENTIFIER; }
+ StringName get_identifier() const { return literal; }
+
+ Token(Type p_type) {
+ type = p_type;
+ }
- struct TokenData {
- Token type;
- StringName identifier; //for identifier types
- Variant constant; //for constant types
- union {
- Variant::Type vtype; //for type types
- GDScriptFunctions::Function func; //function for built in functions
- int warning_code; //for warning skip
- };
- int line, col;
- TokenData() {
- type = TK_EMPTY;
- line = col = 0;
- vtype = Variant::NIL;
+ Token() {
+ type = EMPTY;
}
};
- void _make_token(Token p_type);
- void _make_newline(int p_indentation = 0, int p_tabs = 0);
- void _make_identifier(const StringName &p_identifier);
- void _make_built_in_func(GDScriptFunctions::Function p_func);
- void _make_constant(const Variant &p_constant);
- void _make_type(const Variant::Type &p_type);
- void _make_error(const String &p_error);
-
- String code;
- int len;
- int code_pos;
- const CharType *_code;
- int line;
- int column;
- TokenData tk_rb[TK_RB_SIZE * 2 + 1];
- int tk_rb_pos;
- String last_error;
- bool error_flag;
-
-#ifdef DEBUG_ENABLED
- Vector<Pair<int, String>> warning_skips;
- Set<String> warning_global_skips;
- bool ignore_warnings;
-#endif // DEBUG_ENABLED
-
- void _advance();
+private:
+ String source;
+ const CharType *_source = nullptr;
+ const CharType *_current = nullptr;
+ int line = -1, column = -1;
+ int cursor_line = -1, cursor_column = -1;
+ int tab_size = 4;
+
+ // Keep track of multichar tokens.
+ const CharType *_start = nullptr;
+ int start_line = 0, start_column = 0;
+ int leftmost_column = 0, rightmost_column = 0;
+
+ // Info cache.
+ bool line_continuation = false; // Whether this line is a continuation of the previous, like when using '\'.
+ bool multiline_mode = false;
+ List<Token> error_stack;
+ bool pending_newline = false;
+ Token last_newline;
+ int pending_indents = 0;
+ List<int> indent_stack;
+ List<CharType> paren_stack;
+ CharType indent_char = '\0';
+ int position = 0;
+ int length = 0;
+
+ _FORCE_INLINE_ bool _is_at_end() { return position >= length; }
+ _FORCE_INLINE_ CharType _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; }
+ int indent_level() const { return indent_stack.size(); }
+ bool has_error() const { return !error_stack.empty(); }
+ Token pop_error();
+ CharType _advance();
+ void _skip_whitespace();
+ void check_indent();
+
+ Token make_error(const String &p_message);
+ void push_error(const String &p_message);
+ void push_error(const Token &p_error);
+ Token make_paren_error(CharType p_paren);
+ Token make_token(Token::Type p_type);
+ Token make_literal(const Variant &p_literal);
+ Token make_identifier(const StringName &p_identifier);
+ Token check_vcs_marker(CharType p_test, Token::Type p_double_type);
+ void push_paren(CharType p_char);
+ bool pop_paren(CharType p_expected);
+
+ void newline(bool p_make_token);
+ Token number();
+ Token potential_identifier();
+ Token string();
+ Token annotation();
public:
- void set_code(const String &p_code);
- virtual Token get_token(int p_offset = 0) const;
- virtual StringName get_token_identifier(int p_offset = 0) const;
- virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const;
- virtual Variant::Type get_token_type(int p_offset = 0) const;
- virtual int get_token_line(int p_offset = 0) const;
- virtual int get_token_column(int p_offset = 0) const;
- virtual int get_token_line_indent(int p_offset = 0) const;
- virtual int get_token_line_tab_indent(int p_offset = 0) const;
- virtual const Variant &get_token_constant(int p_offset = 0) const;
- virtual String get_token_error(int p_offset = 0) const;
- virtual void advance(int p_amount = 1);
-#ifdef DEBUG_ENABLED
- virtual const Vector<Pair<int, String>> &get_warning_skips() const { return warning_skips; }
- virtual const Set<String> &get_warning_global_skips() const { return warning_global_skips; }
- virtual bool is_ignoring_warnings() const { return ignore_warnings; }
-#endif // DEBUG_ENABLED
-};
+ Token scan();
-class GDScriptTokenizerBuffer : public GDScriptTokenizer {
- enum {
+ void set_source_code(const String &p_source_code);
- TOKEN_BYTE_MASK = 0x80,
- TOKEN_BITS = 8,
- TOKEN_MASK = (1 << TOKEN_BITS) - 1,
- TOKEN_LINE_BITS = 24,
- TOKEN_LINE_MASK = (1 << TOKEN_LINE_BITS) - 1,
- };
+ int get_cursor_line() const;
+ int get_cursor_column() const;
+ void set_cursor_position(int p_line, int p_column);
+ void set_multiline_mode(bool p_state);
+ bool is_past_cursor() const;
+ static String get_token_name(Token::Type p_token_type);
- Vector<StringName> identifiers;
- Vector<Variant> constants;
- VMap<uint32_t, uint32_t> lines;
- Vector<uint32_t> tokens;
- Variant nil;
- int token;
-
-public:
- Error set_code_buffer(const Vector<uint8_t> &p_buffer);
- static Vector<uint8_t> parse_code_string(const String &p_code);
- virtual Token get_token(int p_offset = 0) const;
- virtual StringName get_token_identifier(int p_offset = 0) const;
- virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const;
- virtual Variant::Type get_token_type(int p_offset = 0) const;
- virtual int get_token_line(int p_offset = 0) const;
- virtual int get_token_column(int p_offset = 0) const;
- virtual int get_token_line_indent(int p_offset = 0) const;
- virtual int get_token_line_tab_indent(int p_offset = 0) const { return 0; }
- virtual const Variant &get_token_constant(int p_offset = 0) const;
- virtual String get_token_error(int p_offset = 0) const;
- virtual void advance(int p_amount = 1);
-#ifdef DEBUG_ENABLED
- virtual const Vector<Pair<int, String>> &get_warning_skips() const {
- static Vector<Pair<int, String>> v;
- return v;
- }
- virtual const Set<String> &get_warning_global_skips() const {
- static Set<String> s;
- return s;
- }
- virtual bool is_ignoring_warnings() const { return true; }
-#endif // DEBUG_ENABLED
- GDScriptTokenizerBuffer();
+ GDScriptTokenizer();
};
-#endif // GDSCRIPT_TOKENIZER_H
+#endif
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
new file mode 100644
index 0000000000..105facd9d0
--- /dev/null
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -0,0 +1,210 @@
+/*************************************************************************/
+/* gdscript_warning.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_warning.h"
+
+#include "core/variant.h"
+
+#ifdef DEBUG_ENABLED
+
+String GDScriptWarning::get_message() const {
+#define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String());
+
+ switch (code) {
+ case UNASSIGNED_VARIABLE_OP_ASSIGN: {
+ CHECK_SYMBOLS(1);
+ return "Using assignment with operation but the variable '" + symbols[0] + "' was not previously assigned a value.";
+ } break;
+ case UNASSIGNED_VARIABLE: {
+ CHECK_SYMBOLS(1);
+ return "The variable '" + symbols[0] + "' was used but never assigned a value.";
+ } break;
+ case UNUSED_VARIABLE: {
+ CHECK_SYMBOLS(1);
+ return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'";
+ } break;
+ case UNUSED_LOCAL_CONSTANT: {
+ CHECK_SYMBOLS(1);
+ return "The local constant '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'";
+ } break;
+ case SHADOWED_VARIABLE: {
+ CHECK_SYMBOLS(4);
+ return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]);
+ } break;
+ case SHADOWED_VARIABLE_BASE_CLASS: {
+ CHECK_SYMBOLS(4);
+ return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
+ } break;
+ case UNUSED_PRIVATE_CLASS_VARIABLE: {
+ CHECK_SYMBOLS(1);
+ return "The class variable '" + symbols[0] + "' is declared but never used in the script.";
+ } break;
+ case UNUSED_PARAMETER: {
+ CHECK_SYMBOLS(2);
+ return "The parameter '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'";
+ } break;
+ case UNREACHABLE_CODE: {
+ CHECK_SYMBOLS(1);
+ return "Unreachable code (statement after return) in function '" + symbols[0] + "()'.";
+ } break;
+ case UNREACHABLE_PATTERN: {
+ return "Unreachable pattern (pattern after wildcard or bind).";
+ } break;
+ case STANDALONE_EXPRESSION: {
+ return "Standalone expression (the line has no effect).";
+ } break;
+ case VOID_ASSIGNMENT: {
+ CHECK_SYMBOLS(1);
+ return "Assignment operation, but the function '" + symbols[0] + "()' returns void.";
+ } break;
+ case NARROWING_CONVERSION: {
+ return "Narrowing conversion (float is converted to int and loses precision).";
+ } break;
+ case INCOMPATIBLE_TERNARY: {
+ return "Values of the ternary conditional are not mutually compatible.";
+ } break;
+ case UNUSED_SIGNAL: {
+ CHECK_SYMBOLS(1);
+ return "The signal '" + symbols[0] + "' is declared but never emitted.";
+ } break;
+ case RETURN_VALUE_DISCARDED: {
+ CHECK_SYMBOLS(1);
+ return "The function '" + symbols[0] + "()' returns a value, but this value is never used.";
+ } break;
+ case PROPERTY_USED_AS_FUNCTION: {
+ CHECK_SYMBOLS(2);
+ return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a property with the same name. Did you mean to access it?";
+ } break;
+ case CONSTANT_USED_AS_FUNCTION: {
+ CHECK_SYMBOLS(2);
+ return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a constant with the same name. Did you mean to access it?";
+ } break;
+ case FUNCTION_USED_AS_PROPERTY: {
+ CHECK_SYMBOLS(2);
+ return "The property '" + symbols[0] + "' was not found in base '" + symbols[1] + "' but there's a method with the same name. Did you mean to call it?";
+ } break;
+ case INTEGER_DIVISION: {
+ return "Integer division, decimal part will be discarded.";
+ } break;
+ case UNSAFE_PROPERTY_ACCESS: {
+ CHECK_SYMBOLS(2);
+ return "The property '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
+ } break;
+ case UNSAFE_METHOD_ACCESS: {
+ CHECK_SYMBOLS(2);
+ return "The method '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
+ } break;
+ case UNSAFE_CAST: {
+ CHECK_SYMBOLS(1);
+ return "The value is cast to '" + symbols[0] + "' but has an unknown type.";
+ } break;
+ case UNSAFE_CALL_ARGUMENT: {
+ CHECK_SYMBOLS(4);
+ return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided";
+ } break;
+ case DEPRECATED_KEYWORD: {
+ CHECK_SYMBOLS(2);
+ return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'.";
+ } break;
+ case STANDALONE_TERNARY: {
+ return "Standalone ternary conditional operator: the return value is being discarded.";
+ }
+ case ASSERT_ALWAYS_TRUE: {
+ return "Assert statement is redundant because the expression is always true.";
+ }
+ case ASSERT_ALWAYS_FALSE: {
+ return "Assert statement will raise an error because the expression is always false.";
+ }
+ case REDUNDANT_AWAIT: {
+ return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)";
+ }
+ case WARNING_MAX:
+ break; // Can't happen, but silences warning
+ }
+ ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + ".");
+
+#undef CHECK_SYMBOLS
+}
+
+String GDScriptWarning::get_name() const {
+ return get_name_from_code(code);
+}
+
+String GDScriptWarning::get_name_from_code(Code p_code) {
+ ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String());
+
+ static const char *names[] = {
+ "UNASSIGNED_VARIABLE",
+ "UNASSIGNED_VARIABLE_OP_ASSIGN",
+ "UNUSED_VARIABLE",
+ "UNUSED_LOCAL_CONSTANT",
+ "SHADOWED_VARIABLE",
+ "SHADOWED_VARIABLE_BASE_CLASS",
+ "UNUSED_PRIVATE_CLASS_VARIABLE",
+ "UNUSED_PARAMETER",
+ "UNREACHABLE_CODE",
+ "UNREACHABLE_PATTERN",
+ "STANDALONE_EXPRESSION",
+ "VOID_ASSIGNMENT",
+ "NARROWING_CONVERSION",
+ "INCOMPATIBLE_TERNARY",
+ "UNUSED_SIGNAL",
+ "RETURN_VALUE_DISCARDED",
+ "PROPERTY_USED_AS_FUNCTION",
+ "CONSTANT_USED_AS_FUNCTION",
+ "FUNCTION_USED_AS_PROPERTY",
+ "INTEGER_DIVISION",
+ "UNSAFE_PROPERTY_ACCESS",
+ "UNSAFE_METHOD_ACCESS",
+ "UNSAFE_CAST",
+ "UNSAFE_CALL_ARGUMENT",
+ "DEPRECATED_KEYWORD",
+ "STANDALONE_TERNARY",
+ "ASSERT_ALWAYS_TRUE",
+ "ASSERT_ALWAYS_FALSE",
+ "REDUNDANT_AWAIT",
+ };
+
+ static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
+
+ return names[(int)p_code];
+}
+
+GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) {
+ for (int i = 0; i < WARNING_MAX; i++) {
+ if (get_name_from_code((Code)i) == p_name) {
+ return (Code)i;
+ }
+ }
+
+ ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name);
+}
+
+#endif // DEBUG_ENABLED
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
new file mode 100644
index 0000000000..e183d6f302
--- /dev/null
+++ b/modules/gdscript/gdscript_warning.h
@@ -0,0 +1,87 @@
+/*************************************************************************/
+/* gdscript_warning.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_WARNINGS
+#define GDSCRIPT_WARNINGS
+
+#ifdef DEBUG_ENABLED
+
+#include "core/ustring.h"
+#include "core/vector.h"
+
+class GDScriptWarning {
+public:
+ enum Code {
+ UNASSIGNED_VARIABLE, // Variable used but never assigned.
+ UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc).
+ UNUSED_VARIABLE, // Local variable is declared but never used.
+ UNUSED_LOCAL_CONSTANT, // Local constant is declared but never used.
+ SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class.
+ SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class.
+ UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the file.
+ UNUSED_PARAMETER, // Function parameter is never used.
+ UNREACHABLE_CODE, // Code after a return statement.
+ UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind).
+ STANDALONE_EXPRESSION, // Expression not assigned to a variable.
+ VOID_ASSIGNMENT, // Function returns void but it's assigned to a variable.
+ NARROWING_CONVERSION, // Float value into an integer slot, precision is lost.
+ INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible.
+ UNUSED_SIGNAL, // Signal is defined but never emitted.
+ RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used.
+ PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name.
+ CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name.
+ FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name.
+ INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded.
+ UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes).
+ UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes).
+ UNSAFE_CAST, // Cast used in an unknown type.
+ UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument.
+ DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced.
+ STANDALONE_TERNARY, // Return value of ternary expression is discarded.
+ ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
+ ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false.
+ REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
+ WARNING_MAX,
+ };
+
+ Code code = WARNING_MAX;
+ int start_line = -1, end_line = -1;
+ int leftmost_column = -1, rightmost_column = -1;
+ Vector<String> symbols;
+
+ String get_name() const;
+ String get_message() const;
+ static String get_name_from_code(Code p_code);
+ static Code get_code_from_name(const String &p_name);
+};
+
+#endif // DEBUG_ENABLED
+
+#endif // GDSCRIPT_WARNINGS
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index 385d5dd7cb..ae7898fdf2 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -31,6 +31,7 @@
#include "gdscript_extend_parser.h"
#include "../gdscript.h"
+#include "../gdscript_analyzer.h"
#include "core/io/json.h"
#include "gdscript_language_protocol.h"
#include "gdscript_workspace.h"
@@ -38,15 +39,17 @@
void ExtendGDScriptParser::update_diagnostics() {
diagnostics.clear();
- if (has_error()) {
+ const List<ParserError> &errors = get_errors();
+ for (const List<ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) {
+ const ParserError &error = E->get();
lsp::Diagnostic diagnostic;
diagnostic.severity = lsp::DiagnosticSeverity::Error;
- diagnostic.message = get_error();
+ diagnostic.message = error.message;
diagnostic.source = "gdscript";
diagnostic.code = -1;
lsp::Range range;
lsp::Position pos;
- int line = LINE_NUMBER_TO_INDEX(get_error_line());
+ int line = LINE_NUMBER_TO_INDEX(error.line);
const String &line_text = get_lines()[line];
pos.line = line;
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
@@ -67,7 +70,7 @@ void ExtendGDScriptParser::update_diagnostics() {
diagnostic.code = warning.code;
lsp::Range range;
lsp::Position pos;
- int line = LINE_NUMBER_TO_INDEX(warning.line);
+ int line = LINE_NUMBER_TO_INDEX(warning.start_line);
const String &line_text = get_lines()[line];
pos.line = line;
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
@@ -82,7 +85,7 @@ void ExtendGDScriptParser::update_diagnostics() {
void ExtendGDScriptParser::update_symbols() {
members.clear();
- const GDScriptParser::Node *head = get_parse_tree();
+ const GDScriptParser::Node *head = get_tree();
if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
parse_class_symbol(gdclass, class_symbol);
@@ -106,15 +109,15 @@ void ExtendGDScriptParser::update_symbols() {
void ExtendGDScriptParser::update_document_links(const String &p_code) {
document_links.clear();
- GDScriptTokenizerText tokenizer;
+ GDScriptTokenizer tokenizer;
FileAccessRef fs = FileAccess::create(FileAccess::ACCESS_RESOURCES);
- tokenizer.set_code(p_code);
+ tokenizer.set_source_code(p_code);
while (true) {
- GDScriptTokenizerText::Token token = tokenizer.get_token();
- if (token == GDScriptTokenizer::TK_EOF || token == GDScriptTokenizer::TK_ERROR) {
+ GDScriptTokenizer::Token token = tokenizer.scan();
+ if (token.type == GDScriptTokenizer::Token::TK_EOF) {
break;
- } else if (token == GDScriptTokenizer::TK_CONSTANT) {
- const Variant &const_val = tokenizer.get_token_constant();
+ } else if (token.type == GDScriptTokenizer::Token::LITERAL) {
+ const Variant &const_val = token.literal;
if (const_val.get_type() == Variant::STRING) {
String path = const_val;
bool exists = fs->file_exists(path);
@@ -126,15 +129,14 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) {
String value = const_val;
lsp::DocumentLink link;
link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path);
- link.range.start.line = LINE_NUMBER_TO_INDEX(tokenizer.get_token_line());
- link.range.end.line = link.range.start.line;
- link.range.end.character = LINE_NUMBER_TO_INDEX(tokenizer.get_token_column());
- link.range.start.character = link.range.end.character - value.length();
+ link.range.start.line = LINE_NUMBER_TO_INDEX(token.start_line);
+ link.range.end.line = LINE_NUMBER_TO_INDEX(token.end_line);
+ link.range.start.character = LINE_NUMBER_TO_INDEX(token.start_column);
+ link.range.end.character = LINE_NUMBER_TO_INDEX(token.end_column);
document_links.push_back(link);
}
}
}
- tokenizer.advance();
}
}
@@ -144,219 +146,238 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
r_symbol.uri = uri;
r_symbol.script_path = path;
r_symbol.children.clear();
- r_symbol.name = p_class->name;
+ r_symbol.name = p_class->identifier != nullptr ? String(p_class->identifier->name) : String();
if (r_symbol.name.empty()) {
r_symbol.name = path.get_file();
}
r_symbol.kind = lsp::SymbolKind::Class;
r_symbol.deprecated = false;
- r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->line);
- r_symbol.range.start.character = p_class->column;
+ r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->start_line);
+ r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_class->start_column);
r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_class->end_line);
r_symbol.selectionRange.start.line = r_symbol.range.start.line;
r_symbol.detail = "class " + r_symbol.name;
bool is_root_class = &r_symbol == &class_symbol;
- r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class);
-
- for (int i = 0; i < p_class->variables.size(); ++i) {
- const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
-
- lsp::DocumentSymbol symbol;
- symbol.name = m.identifier;
- symbol.kind = lsp::SymbolKind::Variable;
- symbol.deprecated = false;
- const int line = LINE_NUMBER_TO_INDEX(m.line);
- symbol.range.start.line = line;
- symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length();
- symbol.range.end.line = line;
- symbol.range.end.character = lines[line].length();
- symbol.selectionRange.start.line = symbol.range.start.line;
- if (m._export.type != Variant::NIL) {
- symbol.detail += "export ";
- }
- symbol.detail += "var " + m.identifier;
- if (m.data_type.kind != GDScriptParser::DataType::UNRESOLVED) {
- symbol.detail += ": " + m.data_type.to_string();
- }
- if (m.default_value.get_type() != Variant::NIL) {
- symbol.detail += " = " + JSON::print(m.default_value);
- }
-
- symbol.documentation = parse_documentation(line);
- symbol.uri = uri;
- symbol.script_path = path;
-
- r_symbol.children.push_back(symbol);
- }
-
- for (int i = 0; i < p_class->_signals.size(); ++i) {
- const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i];
-
- lsp::DocumentSymbol symbol;
- symbol.name = signal.name;
- symbol.kind = lsp::SymbolKind::Event;
- symbol.deprecated = false;
- const int line = LINE_NUMBER_TO_INDEX(signal.line);
- symbol.range.start.line = line;
- symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length();
- symbol.range.end.line = symbol.range.start.line;
- symbol.range.end.character = lines[line].length();
- symbol.selectionRange.start.line = symbol.range.start.line;
- symbol.documentation = parse_documentation(line);
- symbol.uri = uri;
- symbol.script_path = path;
- symbol.detail = "signal " + signal.name + "(";
- for (int j = 0; j < signal.arguments.size(); j++) {
- if (j > 0) {
- symbol.detail += ", ";
- }
- symbol.detail += signal.arguments[j];
- }
- symbol.detail += ")";
-
- r_symbol.children.push_back(symbol);
- }
+ r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->start_line), is_root_class);
+
+ for (int i = 0; i < p_class->members.size(); i++) {
+ const ClassNode::Member &m = p_class->members[i];
+
+ switch (m.type) {
+ case ClassNode::Member::VARIABLE: {
+ lsp::DocumentSymbol symbol;
+ symbol.name = m.variable->identifier->name;
+ symbol.kind = lsp::SymbolKind::Variable;
+ symbol.deprecated = false;
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.variable->start_line);
+ symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.variable->start_column);
+ symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.variable->end_line);
+ symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.variable->end_column);
+ symbol.selectionRange.start.line = symbol.range.start.line;
+ if (m.variable->exported) {
+ symbol.detail += "@export ";
+ }
+ symbol.detail += "var " + m.variable->identifier->name;
+ if (m.get_datatype().is_hard_type()) {
+ symbol.detail += ": " + m.get_datatype().to_string();
+ }
+ if (m.variable->initializer != nullptr && m.variable->initializer->is_constant) {
+ symbol.detail += " = " + JSON::print(m.variable->initializer->reduced_value);
+ }
- for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
- lsp::DocumentSymbol symbol;
- const GDScriptParser::ClassNode::Constant &c = E->value();
- const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
- ERR_FAIL_COND(!node);
- symbol.name = E->key();
- symbol.kind = lsp::SymbolKind::Constant;
- symbol.deprecated = false;
- const int line = LINE_NUMBER_TO_INDEX(E->get().expression->line);
- symbol.range.start.line = line;
- symbol.range.start.character = E->get().expression->column;
- symbol.range.end.line = symbol.range.start.line;
- symbol.range.end.character = lines[line].length();
- symbol.selectionRange.start.line = symbol.range.start.line;
- symbol.documentation = parse_documentation(line);
- symbol.uri = uri;
- symbol.script_path = path;
+ symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.variable->start_line));
+ symbol.uri = uri;
+ symbol.script_path = path;
+
+ r_symbol.children.push_back(symbol);
+ } break;
+ case ClassNode::Member::CONSTANT: {
+ lsp::DocumentSymbol symbol;
+
+ symbol.name = m.constant->identifier->name;
+ symbol.kind = lsp::SymbolKind::Constant;
+ symbol.deprecated = false;
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line);
+ symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.constant->start_column);
+ symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.constant->end_line);
+ symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.constant->start_column);
+ symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line);
+ symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.constant->start_line));
+ symbol.uri = uri;
+ symbol.script_path = path;
+
+ symbol.detail = "const " + symbol.name;
+ if (m.constant->get_datatype().is_hard_type()) {
+ symbol.detail += ": " + m.constant->get_datatype().to_string();
+ }
- symbol.detail = "const " + symbol.name;
- if (c.type.kind != GDScriptParser::DataType::UNRESOLVED) {
- symbol.detail += ": " + c.type.to_string();
- }
+ const Variant &default_value = m.constant->initializer->reduced_value;
+ String value_text;
+ if (default_value.get_type() == Variant::OBJECT) {
+ RES res = default_value;
+ if (res.is_valid() && !res->get_path().empty()) {
+ value_text = "preload(\"" + res->get_path() + "\")";
+ if (symbol.documentation.empty()) {
+ if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) {
+ symbol.documentation = S->get()->class_symbol.documentation;
+ }
+ }
+ } else {
+ value_text = JSON::print(default_value);
+ }
+ } else {
+ value_text = JSON::print(default_value);
+ }
+ if (!value_text.empty()) {
+ symbol.detail += " = " + value_text;
+ }
- String value_text;
- if (node->value.get_type() == Variant::OBJECT) {
- RES res = node->value;
- if (res.is_valid() && !res->get_path().empty()) {
- value_text = "preload(\"" + res->get_path() + "\")";
- if (symbol.documentation.empty()) {
- if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) {
- symbol.documentation = S->get()->class_symbol.documentation;
+ r_symbol.children.push_back(symbol);
+ } break;
+ case ClassNode::Member::ENUM_VALUE: {
+ lsp::DocumentSymbol symbol;
+
+ symbol.name = m.constant->identifier->name;
+ symbol.kind = lsp::SymbolKind::EnumMember;
+ symbol.deprecated = false;
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
+ symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.enum_value.leftmost_column);
+ symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
+ symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.enum_value.rightmost_column);
+ symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
+ symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.enum_value.line));
+ symbol.uri = uri;
+ symbol.script_path = path;
+
+ symbol.detail = symbol.name + " = " + itos(m.enum_value.value);
+
+ r_symbol.children.push_back(symbol);
+ } break;
+ case ClassNode::Member::SIGNAL: {
+ lsp::DocumentSymbol symbol;
+ symbol.name = m.signal->identifier->name;
+ symbol.kind = lsp::SymbolKind::Event;
+ symbol.deprecated = false;
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.signal->start_line);
+ symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.signal->start_column);
+ symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.signal->end_line);
+ symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.signal->end_column);
+ symbol.selectionRange.start.line = symbol.range.start.line;
+ symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.signal->start_line));
+ symbol.uri = uri;
+ symbol.script_path = path;
+ symbol.detail = "signal " + String(m.signal->identifier->name) + "(";
+ for (int j = 0; j < m.signal->parameters.size(); j++) {
+ if (j > 0) {
+ symbol.detail += ", ";
}
+ symbol.detail += m.signal->parameters[i]->identifier->name;
}
- } else {
- value_text = JSON::print(node->value);
- }
- } else {
- value_text = JSON::print(node->value);
- }
- if (!value_text.empty()) {
- symbol.detail += " = " + value_text;
+ symbol.detail += ")";
+
+ r_symbol.children.push_back(symbol);
+ } break;
+ case ClassNode::Member::ENUM: {
+ lsp::DocumentSymbol symbol;
+ symbol.kind = lsp::SymbolKind::Enum;
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.m_enum->start_line);
+ symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.m_enum->start_column);
+ symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.m_enum->end_line);
+ symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.m_enum->end_column);
+ symbol.selectionRange.start.line = symbol.range.start.line;
+ symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.m_enum->start_line));
+ symbol.uri = uri;
+ symbol.script_path = path;
+
+ symbol.detail = "enum " + String(m.m_enum->identifier->name) + "{";
+ for (int j = 0; j < m.m_enum->values.size(); j++) {
+ if (j > 0) {
+ symbol.detail += ", ";
+ }
+ symbol.detail += String(m.m_enum->values[j].identifier->name) + " = " + itos(m.m_enum->values[j].value);
+ }
+ symbol.detail += "}";
+ r_symbol.children.push_back(symbol);
+ } break;
+ case ClassNode::Member::FUNCTION: {
+ lsp::DocumentSymbol symbol;
+ parse_function_symbol(m.function, symbol);
+ r_symbol.children.push_back(symbol);
+ } break;
+ case ClassNode::Member::CLASS: {
+ lsp::DocumentSymbol symbol;
+ parse_class_symbol(m.m_class, symbol);
+ r_symbol.children.push_back(symbol);
+ } break;
+ case ClassNode::Member::UNDEFINED:
+ break; // Unreachable.
}
-
- r_symbol.children.push_back(symbol);
- }
-
- for (int i = 0; i < p_class->functions.size(); ++i) {
- const GDScriptParser::FunctionNode *func = p_class->functions[i];
- lsp::DocumentSymbol symbol;
- parse_function_symbol(func, symbol);
- r_symbol.children.push_back(symbol);
- }
-
- for (int i = 0; i < p_class->static_functions.size(); ++i) {
- const GDScriptParser::FunctionNode *func = p_class->static_functions[i];
- lsp::DocumentSymbol symbol;
- parse_function_symbol(func, symbol);
- r_symbol.children.push_back(symbol);
- }
-
- for (int i = 0; i < p_class->subclasses.size(); ++i) {
- const GDScriptParser::ClassNode *subclass = p_class->subclasses[i];
- lsp::DocumentSymbol symbol;
- parse_class_symbol(subclass, symbol);
- r_symbol.children.push_back(symbol);
}
}
void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol) {
const String uri = get_uri();
- r_symbol.name = p_func->name;
+ r_symbol.name = p_func->identifier->name;
r_symbol.kind = lsp::SymbolKind::Function;
- r_symbol.detail = "func " + p_func->name + "(";
+ r_symbol.detail = "func " + String(p_func->identifier->name) + "(";
r_symbol.deprecated = false;
- const int line = LINE_NUMBER_TO_INDEX(p_func->line);
- r_symbol.range.start.line = line;
- r_symbol.range.start.character = p_func->column;
- r_symbol.range.end.line = MAX(p_func->body->end_line - 2, r_symbol.range.start.line);
- r_symbol.range.end.character = lines[r_symbol.range.end.line].length();
+ r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->start_line);
+ r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_func->start_column);
+ r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_func->start_line);
+ r_symbol.range.end.character = LINE_NUMBER_TO_INDEX(p_func->end_column);
r_symbol.selectionRange.start.line = r_symbol.range.start.line;
- r_symbol.documentation = parse_documentation(line);
+ r_symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(p_func->start_line));
r_symbol.uri = uri;
r_symbol.script_path = path;
- String arguments;
- for (int i = 0; i < p_func->arguments.size(); i++) {
+ String parameters;
+ for (int i = 0; i < p_func->parameters.size(); i++) {
+ const ParameterNode *parameter = p_func->parameters[i];
lsp::DocumentSymbol symbol;
symbol.kind = lsp::SymbolKind::Variable;
- symbol.name = p_func->arguments[i];
- symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->body->line);
- symbol.range.start.character = p_func->body->column;
- symbol.range.end = symbol.range.start;
+ symbol.name = parameter->identifier->name;
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(parameter->start_line);
+ symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_line);
+ symbol.range.end.line = LINE_NUMBER_TO_INDEX(parameter->end_line);
+ symbol.range.end.character = LINE_NUMBER_TO_INDEX(parameter->end_column);
symbol.uri = uri;
symbol.script_path = path;
r_symbol.children.push_back(symbol);
if (i > 0) {
- arguments += ", ";
+ parameters += ", ";
}
- arguments += String(p_func->arguments[i]);
- if (p_func->argument_types[i].kind != GDScriptParser::DataType::UNRESOLVED) {
- arguments += ": " + p_func->argument_types[i].to_string();
+ parameters += String(parameter->identifier->name);
+ if (parameter->get_datatype().is_hard_type()) {
+ parameters += ": " + parameter->get_datatype().to_string();
}
- int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size());
- if (default_value_idx >= 0) {
- const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]);
- if (const_node == nullptr) {
- const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]);
- if (operator_node) {
- const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next);
- }
- }
-
- if (const_node) {
- String value = JSON::print(const_node->value);
- arguments += " = " + value;
- }
+ if (parameter->default_value != nullptr) {
+ String value = JSON::print(parameter->default_value->reduced_value);
+ parameters += " = " + value;
}
}
- r_symbol.detail += arguments + ")";
- if (p_func->return_type.kind != GDScriptParser::DataType::UNRESOLVED) {
- r_symbol.detail += " -> " + p_func->return_type.to_string();
+ r_symbol.detail += parameters + ")";
+ if (p_func->get_datatype().is_hard_type()) {
+ r_symbol.detail += " -> " + p_func->get_datatype().to_string();
}
- for (const Map<StringName, LocalVarNode *>::Element *E = p_func->body->variables.front(); E; E = E->next()) {
+ for (int i = 0; i < p_func->body->locals.size(); i++) {
+ const SuiteNode::Local &local = p_func->body->locals[i];
lsp::DocumentSymbol symbol;
- const GDScriptParser::LocalVarNode *var = E->value();
- symbol.name = E->key();
- symbol.kind = lsp::SymbolKind::Variable;
- symbol.range.start.line = LINE_NUMBER_TO_INDEX(E->get()->line);
- symbol.range.start.character = E->get()->column;
- symbol.range.end.line = symbol.range.start.line;
- symbol.range.end.character = lines[symbol.range.end.line].length();
+ symbol.name = local.name;
+ symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable;
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line);
+ symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column);
+ symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line);
+ symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column);
symbol.uri = uri;
symbol.script_path = path;
- symbol.detail = "var " + symbol.name;
- if (var->datatype.kind != GDScriptParser::DataType::UNRESOLVED) {
- symbol.detail += ": " + var->datatype.to_string();
+ symbol.detail = SuiteNode::Local::CONSTANT ? "const " : "var ";
+ symbol.detail += symbol.name;
+ if (local.get_datatype().is_hard_type()) {
+ symbol.detail += ": " + local.get_datatype().to_string();
}
- symbol.documentation = parse_documentation(line);
+ symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line));
r_symbol.children.push_back(symbol);
}
}
@@ -624,34 +645,24 @@ const Array &ExtendGDScriptParser::get_member_completions() {
Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const {
Dictionary func;
ERR_FAIL_NULL_V(p_func, func);
- func["name"] = p_func->name;
- func["return_type"] = p_func->return_type.to_string();
+ func["name"] = p_func->identifier->name;
+ func["return_type"] = p_func->get_datatype().to_string();
func["rpc_mode"] = p_func->rpc_mode;
- Array arguments;
- for (int i = 0; i < p_func->arguments.size(); i++) {
+ Array parameters;
+ for (int i = 0; i < p_func->parameters.size(); i++) {
Dictionary arg;
- arg["name"] = p_func->arguments[i];
- arg["type"] = p_func->argument_types[i].to_string();
- int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size());
- if (default_value_idx >= 0) {
- const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]);
- if (const_node == nullptr) {
- const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]);
- if (operator_node) {
- const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next);
- }
- }
- if (const_node) {
- arg["default_value"] = const_node->value;
- }
+ arg["name"] = p_func->parameters[i]->identifier->name;
+ arg["type"] = p_func->parameters[i]->get_datatype().to_string();
+ if (p_func->parameters[i]->default_value != nullptr) {
+ arg["default_value"] = p_func->parameters[i]->default_value->reduced_value;
}
- arguments.push_back(arg);
+ parameters.push_back(arg);
}
- if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->line))) {
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->start_line))) {
func["signature"] = symbol->detail;
func["description"] = symbol->documentation;
}
- func["arguments"] = arguments;
+ func["arguments"] = parameters;
return func;
}
@@ -660,91 +671,117 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
ERR_FAIL_NULL_V(p_class, class_api);
- class_api["name"] = String(p_class->name);
+ class_api["name"] = p_class->identifier != nullptr ? String(p_class->identifier->name) : String();
class_api["path"] = path;
Array extends_class;
- for (int i = 0; i < p_class->extends_class.size(); i++) {
- extends_class.append(String(p_class->extends_class[i]));
+ for (int i = 0; i < p_class->extends.size(); i++) {
+ extends_class.append(String(p_class->extends[i]));
}
class_api["extends_class"] = extends_class;
- class_api["extends_file"] = String(p_class->extends_file);
+ class_api["extends_file"] = String(p_class->extends_path);
class_api["icon"] = String(p_class->icon_path);
- if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->line))) {
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->start_line))) {
class_api["signature"] = symbol->detail;
class_api["description"] = symbol->documentation;
}
- Array subclasses;
- for (int i = 0; i < p_class->subclasses.size(); i++) {
- subclasses.push_back(dump_class_api(p_class->subclasses[i]));
- }
- class_api["sub_classes"] = subclasses;
-
+ Array nested_classes;
Array constants;
- for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
- const GDScriptParser::ClassNode::Constant &c = E->value();
- const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
- ERR_FAIL_COND_V(!node, class_api);
-
- Dictionary api;
- api["name"] = E->key();
- api["value"] = node->value;
- api["data_type"] = node->datatype.to_string();
- if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(node->line))) {
- api["signature"] = symbol->detail;
- api["description"] = symbol->documentation;
- }
- constants.push_back(api);
- }
- class_api["constants"] = constants;
-
Array members;
- for (int i = 0; i < p_class->variables.size(); ++i) {
- const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
- Dictionary api;
- api["name"] = m.identifier;
- api["data_type"] = m.data_type.to_string();
- api["default_value"] = m.default_value;
- api["setter"] = String(m.setter);
- api["getter"] = String(m.getter);
- api["export"] = m._export.type != Variant::NIL;
- if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.line))) {
- api["signature"] = symbol->detail;
- api["description"] = symbol->documentation;
- }
- members.push_back(api);
- }
- class_api["members"] = members;
-
Array signals;
- for (int i = 0; i < p_class->_signals.size(); ++i) {
- const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i];
- Dictionary api;
- api["name"] = signal.name;
- Array args;
- for (int j = 0; j < signal.arguments.size(); j++) {
- args.append(signal.arguments[j]);
- }
- api["arguments"] = args;
- if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(signal.line))) {
- api["signature"] = symbol->detail;
- api["description"] = symbol->documentation;
+ Array methods;
+ Array static_functions;
+
+ for (int i = 0; i < p_class->members.size(); i++) {
+ const ClassNode::Member &m = p_class->members[i];
+ switch (m.type) {
+ case ClassNode::Member::CLASS:
+ nested_classes.push_back(dump_class_api(m.m_class));
+ break;
+ case ClassNode::Member::CONSTANT: {
+ Dictionary api;
+ api["name"] = m.constant->identifier->name;
+ api["value"] = m.constant->initializer->reduced_value;
+ api["data_type"] = m.constant->get_datatype().to_string();
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.constant->start_line))) {
+ api["signature"] = symbol->detail;
+ api["description"] = symbol->documentation;
+ }
+ constants.push_back(api);
+ } break;
+ case ClassNode::Member::ENUM_VALUE: {
+ Dictionary api;
+ api["name"] = m.enum_value.identifier->name;
+ api["value"] = m.enum_value.value;
+ api["data_type"] = m.get_datatype().to_string();
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.enum_value.line))) {
+ api["signature"] = symbol->detail;
+ api["description"] = symbol->documentation;
+ }
+ constants.push_back(api);
+ } break;
+ case ClassNode::Member::ENUM: {
+ Dictionary enum_dict;
+ for (int j = 0; j < m.m_enum->values.size(); i++) {
+ enum_dict[m.m_enum->values[i].identifier->name] = m.m_enum->values[i].value;
+ }
+
+ Dictionary api;
+ api["name"] = m.m_enum->identifier->name;
+ api["value"] = enum_dict;
+ api["data_type"] = m.get_datatype().to_string();
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.m_enum->start_line))) {
+ api["signature"] = symbol->detail;
+ api["description"] = symbol->documentation;
+ }
+ constants.push_back(api);
+ } break;
+ case ClassNode::Member::VARIABLE: {
+ Dictionary api;
+ api["name"] = m.variable->identifier->name;
+ api["data_type"] = m.variable->get_datatype().to_string();
+ api["default_value"] = m.variable->initializer != nullptr ? m.variable->initializer->reduced_value : Variant();
+ api["setter"] = m.variable->setter ? ("@" + String(m.variable->identifier->name) + "_setter") : (m.variable->setter_pointer != nullptr ? String(m.variable->setter_pointer->name) : String());
+ api["getter"] = m.variable->getter ? ("@" + String(m.variable->identifier->name) + "_getter") : (m.variable->getter_pointer != nullptr ? String(m.variable->getter_pointer->name) : String());
+ api["export"] = m.variable->exported;
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.variable->start_line))) {
+ api["signature"] = symbol->detail;
+ api["description"] = symbol->documentation;
+ }
+ members.push_back(api);
+ } break;
+ case ClassNode::Member::SIGNAL: {
+ Dictionary api;
+ api["name"] = m.signal->identifier->name;
+ Array pars;
+ for (int j = 0; j < m.signal->parameters.size(); j++) {
+ pars.append(String(m.signal->parameters[i]->identifier->name));
+ }
+ api["arguments"] = pars;
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.signal->start_line))) {
+ api["signature"] = symbol->detail;
+ api["description"] = symbol->documentation;
+ }
+ signals.push_back(api);
+ } break;
+ case ClassNode::Member::FUNCTION: {
+ if (m.function->is_static) {
+ static_functions.append(dump_function_api(m.function));
+ } else {
+ methods.append(dump_function_api(m.function));
+ }
+ } break;
+ case ClassNode::Member::UNDEFINED:
+ break; // Unreachable.
}
- signals.push_back(api);
}
- class_api["signals"] = signals;
- Array methods;
- for (int i = 0; i < p_class->functions.size(); ++i) {
- methods.append(dump_function_api(p_class->functions[i]));
- }
+ class_api["sub_classes"] = nested_classes;
+ class_api["constants"] = constants;
+ class_api["members"] = members;
+ class_api["signals"] = signals;
class_api["methods"] = methods;
-
- Array static_functions;
- for (int i = 0; i < p_class->static_functions.size(); ++i) {
- static_functions.append(dump_function_api(p_class->static_functions[i]));
- }
class_api["static_functions"] = static_functions;
return class_api;
@@ -752,7 +789,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
Dictionary ExtendGDScriptParser::generate_api() const {
Dictionary api;
- const GDScriptParser::Node *head = get_parse_tree();
+ const GDScriptParser::Node *head = get_tree();
if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
api = dump_class_api(gdclass);
}
@@ -763,7 +800,11 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) {
path = p_path;
lines = p_code.split("\n");
- Error err = GDScriptParser::parse(p_code, p_path.get_base_dir(), false, p_path, false, nullptr, false);
+ Error err = GDScriptParser::parse(p_code, p_path, false);
+ if (err == OK) {
+ GDScriptAnalyzer analyzer(this);
+ err = analyzer.analyze();
+ }
update_diagnostics();
update_symbols();
update_document_links(p_code);
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index a203b9bfdb..776193e37c 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -118,7 +118,7 @@ void GDScriptWorkspace::reload_all_workspace_scripts() {
Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(path);
String err_msg = "Failed parse script " + path;
if (S) {
- err_msg += "\n" + S->get()->get_error();
+ err_msg += "\n" + S->get()->get_errors()[0].message;
}
ERR_CONTINUE_MSG(err != OK, err_msg);
}
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index 6c4e529922..23c7f97b5a 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -35,11 +35,13 @@
#include "core/os/dir_access.h"
#include "core/os/file_access.h"
#include "gdscript.h"
+#include "gdscript_cache.h"
#include "gdscript_tokenizer.h"
GDScriptLanguage *script_language_gd = nullptr;
Ref<ResourceFormatLoaderGDScript> resource_loader_gd;
Ref<ResourceFormatSaverGDScript> resource_saver_gd;
+GDScriptCache *gdscript_cache = nullptr;
#ifdef TOOLS_ENABLED
@@ -76,64 +78,8 @@ public:
return;
}
- Vector<uint8_t> file = FileAccess::get_file_as_array(p_path);
- if (file.empty()) {
- return;
- }
-
- String txt;
- txt.parse_utf8((const char *)file.ptr(), file.size());
- file = GDScriptTokenizerBuffer::parse_code_string(txt);
-
- if (!file.empty()) {
- if (script_mode == EditorExportPreset::MODE_SCRIPT_ENCRYPTED) {
- String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("script.gde");
- FileAccess *fa = FileAccess::open(tmp_path, FileAccess::WRITE);
-
- Vector<uint8_t> key;
- key.resize(32);
- for (int i = 0; i < 32; i++) {
- int v = 0;
- if (i * 2 < script_key.length()) {
- CharType ct = script_key[i * 2];
- if (ct >= '0' && ct <= '9') {
- ct = ct - '0';
- } else if (ct >= 'a' && ct <= 'f') {
- ct = 10 + ct - 'a';
- }
- v |= ct << 4;
- }
-
- if (i * 2 + 1 < script_key.length()) {
- CharType ct = script_key[i * 2 + 1];
- if (ct >= '0' && ct <= '9') {
- ct = ct - '0';
- } else if (ct >= 'a' && ct <= 'f') {
- ct = 10 + ct - 'a';
- }
- v |= ct;
- }
- key.write[i] = v;
- }
- FileAccessEncrypted *fae = memnew(FileAccessEncrypted);
- Error err = fae->open_and_parse(fa, key, FileAccessEncrypted::MODE_WRITE_AES256);
-
- if (err == OK) {
- fae->store_buffer(file.ptr(), file.size());
- }
-
- memdelete(fae);
-
- file = FileAccess::get_file_as_array(tmp_path);
- add_file(p_path.get_basename() + ".gde", file, true);
-
- // Clean up temporary file.
- DirAccess::remove_file_or_error(tmp_path);
-
- } else {
- add_file(p_path.get_basename() + ".gdc", file, true);
- }
- }
+ // TODO: Readd compiled/encrypted GDScript on export.
+ return;
}
};
@@ -171,6 +117,8 @@ void register_gdscript_types() {
resource_saver_gd.instance();
ResourceSaver::add_resource_format_saver(resource_saver_gd);
+ gdscript_cache = memnew(GDScriptCache);
+
#ifdef TOOLS_ENABLED
EditorNode::add_init_callback(_editor_init);
@@ -182,6 +130,10 @@ void register_gdscript_types() {
void unregister_gdscript_types() {
ScriptServer::unregister_language(script_language_gd);
+ if (gdscript_cache) {
+ memdelete(gdscript_cache);
+ }
+
if (script_language_gd) {
memdelete(script_language_gd);
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index 34e42489eb..d9862ae361 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -118,9 +118,14 @@ namespace GodotTools.Build
string arguments = string.Empty;
if (buildTool == BuildTool.DotnetCli)
- arguments += "msbuild "; // `dotnet msbuild` command
+ arguments += "msbuild"; // `dotnet msbuild` command
- arguments += $@"""{buildInfo.Solution}"" /t:{string.Join(",", buildInfo.Targets)} " +
+ arguments += $@" ""{buildInfo.Solution}""";
+
+ if (buildInfo.Restore)
+ arguments += " /restore";
+
+ arguments += $@" /t:{string.Join(",", buildInfo.Targets)} " +
$@"""/p:{"Configuration=" + buildInfo.Configuration}"" /v:normal " +
$@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{buildInfo.LogsDirPath}""";
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
index 16c666b8eb..5aba31c622 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
@@ -554,7 +554,7 @@ namespace Godot
/// Returns a vector transformed (multiplied) by the basis matrix.
/// </summary>
/// <param name="v">A vector to transform.</param>
- /// <returns>The transfomed vector.</returns>
+ /// <returns>The transformed vector.</returns>
public Vector3 Xform(Vector3 v)
{
return new Vector3
@@ -572,7 +572,7 @@ namespace Godot
/// basis matrix only if it represents a rotation-reflection.
/// </summary>
/// <param name="v">A vector to inversely transform.</param>
- /// <returns>The inversely transfomed vector.</returns>
+ /// <returns>The inversely transformed vector.</returns>
public Vector3 XformInv(Vector3 v)
{
return new Vector3
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
index 3173a21986..d851abc6d3 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
@@ -486,7 +486,7 @@ namespace Godot
}
/// <summary>
- /// Constructs a color from RGB values on the range of 0 to 1.
+ /// Constructs a color from RGBA values on the range of 0 to 1.
/// </summary>
/// <param name="r">The color's red component, typically on the range of 0 to 1.</param>
/// <param name="g">The color's green component, typically on the range of 0 to 1.</param>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
index af0d474bae..2f8b5f297c 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
@@ -20,8 +20,9 @@ namespace Godot
private Vector3 _normal;
/// <summary>
- /// The normal of the plane (in the plane equation: a, b, and c).
- /// The normal vector must be normalized.
+ /// The normal of the plane, which must be normalized.
+ /// In the scalar equation of the plane `ax + by + cz = d`, this is
+ /// the vector `(a, b, c)`, where `d` is the <see cref="D"/> property.
/// </summary>
/// <value>Equivalent to `x`, `y`, and `z`.</value>
public Vector3 Normal
@@ -81,6 +82,9 @@ namespace Godot
/// <summary>
/// The distance from the origin to the plane (in the direction of
/// <see cref="Normal"/>). This value is typically non-negative.
+ /// In the scalar equation of the plane `ax + by + cz = d`,
+ /// this is `d`, while the `(a, b, c)` coordinates are represented
+ /// by the <see cref="Normal"/> property.
/// </summary>
/// <value>The plane's distance from the origin.</value>
public real_t D { get; set; }
@@ -105,7 +109,7 @@ namespace Godot
/// <summary>
/// Returns the shortest distance from this plane to the position `point`.
/// </summary>
- /// <param name="point">The position to use for the calcualtion.</param>
+ /// <param name="point">The position to use for the calculation.</param>
/// <returns>The shortest distance.</returns>
public real_t DistanceTo(Vector3 point)
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs
index 7c978801bd..b33490f9cb 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs
@@ -299,7 +299,7 @@ namespace Godot
/// Returns a vector transformed (multiplied) by this quaternion.
/// </summary>
/// <param name="v">A vector to transform.</param>
- /// <returns>The transfomed vector.</returns>
+ /// <returns>The transformed vector.</returns>
public Vector3 Xform(Vector3 v)
{
#if DEBUG
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs
index 98d4b92bd1..ac47f6029f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs
@@ -248,7 +248,7 @@ namespace Godot
/// Returns a vector transformed (multiplied) by this transformation matrix.
/// </summary>
/// <param name="v">A vector to transform.</param>
- /// <returns>The transfomed vector.</returns>
+ /// <returns>The transformed vector.</returns>
public Vector3 Xform(Vector3 v)
{
return new Vector3
@@ -266,7 +266,7 @@ namespace Godot
/// transformation matrix only if it represents a rotation-reflection.
/// </summary>
/// <param name="v">A vector to inversely transform.</param>
- /// <returns>The inversely transfomed vector.</returns>
+ /// <returns>The inversely transformed vector.</returns>
public Vector3 XformInv(Vector3 v)
{
Vector3 vInv = v - origin;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
index 9f9ae50c59..06bbe98497 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
@@ -181,7 +181,7 @@ namespace Godot
/// This method does not account for translation (the origin vector).
/// </summary>
/// <param name="v">A vector to transform.</param>
- /// <returns>The transfomed vector.</returns>
+ /// <returns>The transformed vector.</returns>
public Vector2 BasisXform(Vector2 v)
{
return new Vector2(Tdotx(v), Tdoty(v));
@@ -195,7 +195,7 @@ namespace Godot
/// basis matrix only if it represents a rotation-reflection.
/// </summary>
/// <param name="v">A vector to inversely transform.</param>
- /// <returns>The inversely transfomed vector.</returns>
+ /// <returns>The inversely transformed vector.</returns>
public Vector2 BasisXformInv(Vector2 v)
{
return new Vector2(x.Dot(v), y.Dot(v));
@@ -355,7 +355,7 @@ namespace Godot
/// Returns a vector transformed (multiplied) by this transformation matrix.
/// </summary>
/// <param name="v">A vector to transform.</param>
- /// <returns>The transfomed vector.</returns>
+ /// <returns>The transformed vector.</returns>
public Vector2 Xform(Vector2 v)
{
return new Vector2(Tdotx(v), Tdoty(v)) + origin;
@@ -365,7 +365,7 @@ namespace Godot
/// Returns a vector transformed (multiplied) by the inverse transformation matrix.
/// </summary>
/// <param name="v">A vector to inversely transform.</param>
- /// <returns>The inversely transfomed vector.</returns>
+ /// <returns>The inversely transformed vector.</returns>
public Vector2 XformInv(Vector2 v)
{
Vector2 vInv = v - origin;
diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp
index f387c0f288..1af4b46e22 100644
--- a/modules/visual_script/visual_script.cpp
+++ b/modules/visual_script/visual_script.cpp
@@ -36,8 +36,6 @@
#include "scene/main/node.h"
#include "visual_script_nodes.h"
-#include <stdint.h>
-
//used by editor, this is not really saved
void VisualScriptNode::set_breakpoint(bool p_breakpoint) {
breakpoint = p_breakpoint;
@@ -1764,11 +1762,7 @@ Variant VisualScriptInstance::_call_internal(const StringName &p_method, void *p
}
next = node->sequence_outputs[output];
- if (next) {
- VSDEBUG("GOT NEXT NODE - " + itos(next->get_id()));
- } else {
- VSDEBUG("GOT NEXT NODE - NULL");
- }
+ VSDEBUG("GOT NEXT NODE - " + (next ? itos(next->get_id()) : "NULL"));
}
if (flow_stack) {
diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp
index 2fcd9332a1..a1c7dca9d2 100644
--- a/modules/visual_script/visual_script_editor.cpp
+++ b/modules/visual_script/visual_script_editor.cpp
@@ -2513,6 +2513,8 @@ RES VisualScriptEditor::get_edited_resource() const {
}
void VisualScriptEditor::set_edited_resource(const RES &p_res) {
+ ERR_FAIL_COND(script.is_valid());
+ ERR_FAIL_COND(p_res.is_null());
script = p_res;
signal_editor->script = script;
signal_editor->undo_redo = undo_redo;
@@ -2533,6 +2535,9 @@ void VisualScriptEditor::set_edited_resource(const RES &p_res) {
_update_members();
}
+void VisualScriptEditor::enable_editor() {
+}
+
Vector<String> VisualScriptEditor::get_functions() {
return Vector<String>();
}
diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h
index e59618e120..0c5665cee8 100644
--- a/modules/visual_script/visual_script_editor.h
+++ b/modules/visual_script/visual_script_editor.h
@@ -294,6 +294,7 @@ public:
virtual void apply_code() override;
virtual RES get_edited_resource() const override;
virtual void set_edited_resource(const RES &p_res) override;
+ virtual void enable_editor() override;
virtual Vector<String> get_functions() override;
virtual void reload_text() override;
virtual String get_name() override;
diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h
index fb442f4c54..857bef02d1 100644
--- a/platform/android/android_keys_utils.h
+++ b/platform/android/android_keys_utils.h
@@ -148,6 +148,8 @@ enum {
AKEYCODE_BUTTON_START = 108,
AKEYCODE_BUTTON_SELECT = 109,
AKEYCODE_BUTTON_MODE = 110,
+ AKEYCODE_CONTROL_LEFT = 113,
+ AKEYCODE_CONTROL_RIGHT = 114,
// NOTE: If you add a new keycode here you must also add it to several other files.
// Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
@@ -246,6 +248,8 @@ static _WinTranslatePair _ak_to_keycode[] = {
{ KEY_BACKSLASH, AKEYCODE_BACKSLASH },
{ KEY_BRACKETLEFT, AKEYCODE_LEFT_BRACKET },
{ KEY_BRACKETRIGHT, AKEYCODE_RIGHT_BRACKET },
+ { KEY_CONTROL, AKEYCODE_CONTROL_LEFT },
+ { KEY_CONTROL, AKEYCODE_CONTROL_RIGHT },
{ KEY_UNKNOWN, 0 }
};
/*
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 6da1e5f3d6..a4ac87f723 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -164,7 +164,7 @@ def configure(env):
elif env["target"] == "debug":
env.Append(LINKFLAGS=["-O0"])
env.Append(CCFLAGS=["-O0", "-g", "-fno-limit-debug-info"])
- env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"])
+ env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED"])
env.Append(CPPFLAGS=["-UNDEBUG"])
# Compiler configuration
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 3bc9e6d876..7193519a52 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -486,17 +486,40 @@ void DisplayServerAndroid::process_joy_event(DisplayServerAndroid::JoypadEvent p
}
}
+void DisplayServerAndroid::_set_key_modifier_state(Ref<InputEventWithModifiers> ev) {
+ ev->set_shift(shift_mem);
+ ev->set_alt(alt_mem);
+ ev->set_metakey(meta_mem);
+ ev->set_control(control_mem);
+}
+
void DisplayServerAndroid::process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed) {
Ref<InputEventKey> ev;
ev.instance();
int val = p_unicode_char;
int keycode = android_get_keysym(p_keycode);
int phy_keycode = android_get_keysym(p_scancode);
+
+ if (keycode == KEY_SHIFT) {
+ shift_mem = p_pressed;
+ }
+ if (keycode == KEY_ALT) {
+ alt_mem = p_pressed;
+ }
+ if (keycode == KEY_CONTROL) {
+ control_mem = p_pressed;
+ }
+ if (keycode == KEY_META) {
+ meta_mem = p_pressed;
+ }
+
ev->set_keycode(keycode);
ev->set_physical_keycode(phy_keycode);
ev->set_unicode(val);
ev->set_pressed(p_pressed);
+ _set_key_modifier_state(ev);
+
if (val == '\n') {
ev->set_keycode(KEY_ENTER);
} else if (val == 61448) {
@@ -629,6 +652,7 @@ void DisplayServerAndroid::process_hover(int p_type, Point2 p_pos) {
case 10: { // hover exit
Ref<InputEventMouseMotion> ev;
ev.instance();
+ _set_key_modifier_state(ev);
ev->set_position(p_pos);
ev->set_global_position(p_pos);
ev->set_relative(p_pos - hover_prev_pos);
@@ -641,6 +665,7 @@ void DisplayServerAndroid::process_hover(int p_type, Point2 p_pos) {
void DisplayServerAndroid::process_double_tap(Point2 p_pos) {
Ref<InputEventMouseButton> ev;
ev.instance();
+ _set_key_modifier_state(ev);
ev->set_position(p_pos);
ev->set_global_position(p_pos);
ev->set_pressed(false);
@@ -651,6 +676,7 @@ void DisplayServerAndroid::process_double_tap(Point2 p_pos) {
void DisplayServerAndroid::process_scroll(Point2 p_pos) {
Ref<InputEventPanGesture> ev;
ev.instance();
+ _set_key_modifier_state(ev);
ev->set_position(p_pos);
ev->set_delta(p_pos - scroll_prev_pos);
Input::get_singleton()->parse_input_event(ev);
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index d64542df58..4cae52fa76 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -63,6 +63,11 @@ public:
private:
String rendering_driver;
+ bool alt_mem = false;
+ bool shift_mem = false;
+ bool control_mem = false;
+ bool meta_mem = false;
+
bool keep_screen_on;
Vector<TouchPos> touch;
@@ -84,6 +89,8 @@ private:
static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+ void _set_key_modifier_state(Ref<InputEventWithModifiers> ev);
+
public:
static DisplayServerAndroid *get_singleton();
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 71d05bd732..8d3257a365 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "export.h"
+#include "gradle_export_util.h"
#include "core/io/image_loader.h"
#include "core/io/marshalls.h"
@@ -43,6 +44,7 @@
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
+#include "platform/android/export/gradle_export_util.h"
#include "platform/android/logo.gen.h"
#include "platform/android/plugin/godot_plugin_config.h"
#include "platform/android/run_icon.gen.h"
@@ -766,6 +768,30 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
}
+ void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) {
+ String manifest_text =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " xmlns:tools=\"http://schemas.android.com/tools\">\n";
+
+ manifest_text += _get_screen_sizes_tag(p_preset);
+ manifest_text += _get_gles_tag();
+
+ Vector<String> perms;
+ _get_permissions(p_preset, p_give_internet, perms);
+ for (int i = 0; i < perms.size(); i++) {
+ manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i));
+ }
+
+ manifest_text += _get_xr_features_tag(p_preset);
+ manifest_text += _get_instrumentation_tag(p_preset);
+ String plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
+ manifest_text += _get_application_tag(p_preset, plugins_names);
+ manifest_text += "</manifest>\n";
+ String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
+ store_string_at_path(manifest_path, manifest_text);
+ }
+
void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) {
// Leaving the unused types commented because looking these constants up
// again later would be annoying
@@ -1407,23 +1433,81 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
//printf("end\n");
}
- void _process_launcher_icons(const String &p_processing_file_name, const Ref<Image> &p_source_image, const LauncherIcon p_icon, Vector<uint8_t> &p_data) {
- if (p_processing_file_name == p_icon.export_path) {
- Ref<Image> working_image = p_source_image;
+ void _process_launcher_icons(const String &p_file_name, const Ref<Image> &p_source_image, int dimension, Vector<uint8_t> &p_data) {
+ Ref<Image> working_image = p_source_image;
+
+ if (p_source_image->get_width() != dimension || p_source_image->get_height() != dimension) {
+ working_image = p_source_image->duplicate();
+ working_image->resize(dimension, dimension, Image::Interpolation::INTERPOLATE_LANCZOS);
+ }
+
+ Vector<uint8_t> png_buffer;
+ Error err = PNGDriverCommon::image_to_png(working_image, png_buffer);
+ if (err == OK) {
+ p_data.resize(png_buffer.size());
+ memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size());
+ } else {
+ String err_str = String("Failed to convert resized icon (") + p_file_name + ") to png.";
+ WARN_PRINT(err_str.utf8().get_data());
+ }
+ }
+
+ void load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background) {
+ String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
+
+ icon.instance();
+ foreground.instance();
+ background.instance();
+
+ // Regular icon: user selection -> project icon -> default.
+ String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges();
+ if (path.empty() || ImageLoader::load_image(path, icon) != OK) {
+ ImageLoader::load_image(project_icon_path, icon);
+ }
+
+ // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default).
+ path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges();
+ if (path.empty() || ImageLoader::load_image(path, foreground) != OK) {
+ foreground = icon;
+ }
+
+ // Adaptive background: user selection -> default.
+ path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges();
+ if (!path.empty()) {
+ ImageLoader::load_image(path, background);
+ }
+ }
+
+ void store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data) {
+ String img_path = launcher_icon.export_path;
+ img_path = img_path.insert(0, "res://android/build/");
+ store_file_at_path(img_path, data);
+ }
+
+ void _copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset, const Ref<Image> &main_image,
+ const Ref<Image> &foreground, const Ref<Image> &background) {
+ // Prepare images to be resized for the icons. If some image ends up being uninitialized,
+ // the default image from the export template will be used.
- if (p_source_image->get_width() != p_icon.dimensions || p_source_image->get_height() != p_icon.dimensions) {
- working_image = p_source_image->duplicate();
- working_image->resize(p_icon.dimensions, p_icon.dimensions, Image::Interpolation::INTERPOLATE_LANCZOS);
+ for (int i = 0; i < icon_densities_count; ++i) {
+ if (main_image.is_valid() && !main_image->empty()) {
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_icons[i].export_path, main_image, launcher_icons[i].dimensions, data);
+ store_image(launcher_icons[i], data);
}
- Vector<uint8_t> png_buffer;
- Error err = PNGDriverCommon::image_to_png(working_image, png_buffer);
- if (err == OK) {
- p_data.resize(png_buffer.size());
- memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size());
- } else {
- String err_str = String("Failed to convert resized icon (") + p_processing_file_name + ") to png.";
- WARN_PRINT(err_str.utf8().get_data());
+ if (foreground.is_valid() && !foreground->empty()) {
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_adaptive_icon_foregrounds[i].export_path, foreground,
+ launcher_adaptive_icon_foregrounds[i].dimensions, data);
+ store_image(launcher_adaptive_icon_foregrounds[i], data);
+ }
+
+ if (background.is_valid() && !background->empty()) {
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_adaptive_icon_backgrounds[i].export_path, background,
+ launcher_adaptive_icon_backgrounds[i].dimensions, data);
+ store_image(launcher_adaptive_icon_backgrounds[i], data);
}
}
}
@@ -2003,9 +2087,17 @@ public:
EditorProgress ep("export", "Exporting for Android", 105, true);
- if (bool(p_preset->get("custom_template/use_custom_build"))) { //custom build
- //re-generate build.gradle and AndroidManifest.xml
+ bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build"));
+ bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);
+
+ Ref<Image> main_image;
+ Ref<Image> foreground;
+ Ref<Image> background;
+
+ load_icon_refs(p_preset, main_image, foreground, background);
+ if (use_custom_build) {
+ //re-generate build.gradle and AndroidManifest.xml
{ //test that installed build version is alright
FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ);
if (!f) {
@@ -2018,6 +2110,18 @@ public:
return ERR_UNCONFIGURED;
}
}
+
+ // TODO: should we use "package/name" or "application/config/name"?
+ String project_name = get_project_name(p_preset->get("package/name"));
+ // instead of calling _fix_resources
+ Error err = _create_project_name_strings_files(p_preset, project_name);
+ if (err != OK) {
+ EditorNode::add_io_error("Unable to overwrite res://android/build/res/*.xml files with project name");
+ }
+ // Copies the project icon files into the appropriate Gradle project directory.
+ _copy_icons_to_gradle_project(p_preset, main_image, foreground, background);
+ // Write an AndroidManifest.xml file into the Gradle project directory.
+ _write_tmp_manifest(p_preset, p_give_internet, p_debug);
//build project if custom build is enabled
String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path");
@@ -2037,6 +2141,8 @@ public:
build_command = build_path.plus_file(build_command);
String package_name = get_package_name(p_preset->get("package/unique_name"));
+ String version_code = itos(p_preset->get("version/code"));
+ String version_name = p_preset->get("version/name");
Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);
String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins);
@@ -2050,6 +2156,8 @@ public:
}
cmdline.push_back("build");
cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
+ cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code.
+ cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name.
cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.
cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies.
cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies.
@@ -2147,34 +2255,7 @@ public:
Vector<String> enabled_abis = get_enabled_abis(p_preset);
- String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
-
// Prepare images to be resized for the icons. If some image ends up being uninitialized, the default image from the export template will be used.
- Ref<Image> launcher_icon_image;
- Ref<Image> launcher_adaptive_icon_foreground_image;
- Ref<Image> launcher_adaptive_icon_background_image;
-
- launcher_icon_image.instance();
- launcher_adaptive_icon_foreground_image.instance();
- launcher_adaptive_icon_background_image.instance();
-
- // Regular icon: user selection -> project icon -> default.
- String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges();
- if (path.empty() || ImageLoader::load_image(path, launcher_icon_image) != OK) {
- ImageLoader::load_image(project_icon_path, launcher_icon_image);
- }
-
- // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default).
- path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges();
- if (path.empty() || ImageLoader::load_image(path, launcher_adaptive_icon_foreground_image) != OK) {
- launcher_adaptive_icon_foreground_image = launcher_icon_image;
- }
-
- // Adaptive background: user selection -> default.
- path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges();
- if (!path.empty()) {
- ImageLoader::load_image(path, launcher_adaptive_icon_background_image);
- }
Vector<String> invalid_abis(enabled_abis);
while (ret == UNZ_OK) {
@@ -2197,23 +2278,30 @@ public:
//write
- if (file == "AndroidManifest.xml") {
- _fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG));
- }
-
- if (file == "resources.arsc") {
- _fix_resources(p_preset, data);
- }
-
- for (int i = 0; i < icon_densities_count; ++i) {
- if (launcher_icon_image.is_valid() && !launcher_icon_image->empty()) {
- _process_launcher_icons(file, launcher_icon_image, launcher_icons[i], data);
+ if (!use_custom_build) {
+ if (file == "AndroidManifest.xml") {
+ _fix_manifest(p_preset, data, p_give_internet);
}
- if (launcher_adaptive_icon_foreground_image.is_valid() && !launcher_adaptive_icon_foreground_image->empty()) {
- _process_launcher_icons(file, launcher_adaptive_icon_foreground_image, launcher_adaptive_icon_foregrounds[i], data);
+ if (file == "resources.arsc") {
+ _fix_resources(p_preset, data);
}
- if (launcher_adaptive_icon_background_image.is_valid() && !launcher_adaptive_icon_background_image->empty()) {
- _process_launcher_icons(file, launcher_adaptive_icon_background_image, launcher_adaptive_icon_backgrounds[i], data);
+
+ for (int i = 0; i < icon_densities_count; ++i) {
+ if (main_image.is_valid() && !main_image->empty()) {
+ if (file == launcher_icons[i].export_path) {
+ _process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data);
+ }
+ }
+ if (foreground.is_valid() && !foreground->empty()) {
+ if (file == launcher_adaptive_icon_foregrounds[i].export_path) {
+ _process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data);
+ }
+ }
+ if (background.is_valid() && !background->empty()) {
+ if (file == launcher_adaptive_icon_backgrounds[i].export_path) {
+ _process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data);
+ }
+ }
}
}
diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h
index f988047483..209a664f8f 100644
--- a/platform/android/export/gradle_export_util.h
+++ b/platform/android/export/gradle_export_util.h
@@ -37,6 +37,13 @@
#include "core/os/os.h"
#include "editor/editor_export.h"
+const String godot_project_name_xml_string = R"(<?xml version="1.0" encoding="utf-8"?>
+<!--WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">%s</string>
+</resources>
+)";
+
// Utility method used to create a directory.
Error create_directory(const String &p_dir) {
if (!DirAccess::exists(p_dir)) {
@@ -94,8 +101,145 @@ Error store_string_at_path(const String &p_path, const String &p_data) {
// This method will be called ONLY when custom build is enabled.
Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
String dst_path = p_path.replace_first("res://", "res://android/build/assets/");
- Error err = store_file_at_path(dst_path, p_data, Z_NO_COMPRESSION);
+ Error err = store_file_at_path(dst_path, p_data);
return err;
}
+// Creates strings.xml files inside the gradle project for different locales.
+Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name) {
+ // Stores the string into the default values directory.
+ String processed_default_xml_string = vformat(godot_project_name_xml_string, project_name.xml_escape(true));
+ store_string_at_path("res://android/build/res/values/godot_project_name_string.xml", processed_default_xml_string);
+
+ // Searches the Gradle project res/ directory to find all supported locales
+ DirAccessRef da = DirAccess::open("res://android/build/res");
+ if (!da) {
+ return ERR_CANT_OPEN;
+ }
+ da->list_dir_begin();
+ while (true) {
+ String file = da->get_next();
+ if (file == "") {
+ break;
+ }
+ if (!file.begins_with("values-")) {
+ // NOTE: This assumes all directories that start with "values-" are for localization.
+ continue;
+ }
+ String locale = file.replace("values-", "").replace("-r", "_");
+ String property_name = "application/config/name_" + locale;
+ String locale_directory = "res://android/build/res/" + file + "/godot_project_name_string.xml";
+ if (ProjectSettings::get_singleton()->has_setting(property_name)) {
+ String locale_project_name = ProjectSettings::get_singleton()->get(property_name);
+ String processed_xml_string = vformat(godot_project_name_xml_string, locale_project_name.xml_escape(true));
+ store_string_at_path(locale_directory, processed_xml_string);
+ } else {
+ // TODO: Once the legacy build system is deprecated we don't need to have xml files for this else branch
+ store_string_at_path(locale_directory, processed_default_xml_string);
+ }
+ }
+ da->list_dir_end();
+ return OK;
+}
+
+String bool_to_string(bool v) {
+ return v ? "true" : "false";
+}
+
+String _get_gles_tag() {
+ bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" &&
+ !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2");
+ return min_gles3 ? " <uses-feature android:glEsVersion=\"0x00030000\" android:required=\"true\" />\n" : "";
+}
+
+String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) {
+ String manifest_screen_sizes = " <supports-screens \n tools:node=\"replace\"";
+ String sizes[] = { "small", "normal", "large", "xlarge" };
+ size_t num_sizes = sizeof(sizes) / sizeof(sizes[0]);
+ for (size_t i = 0; i < num_sizes; i++) {
+ String feature_name = vformat("screen/support_%s", sizes[i]);
+ String feature_support = bool_to_string(p_preset->get(feature_name));
+ String xml_entry = vformat("\n android:%sScreens=\"%s\"", sizes[i], feature_support);
+ manifest_screen_sizes += xml_entry;
+ }
+ manifest_screen_sizes += " />\n";
+ return manifest_screen_sizes;
+}
+
+String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
+ String manifest_xr_features;
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ if (uses_xr) {
+ int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof
+ if (dof_index == 1) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"false\" android:version=\"1\" />\n";
+ } else if (dof_index == 2) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n";
+ }
+ int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
+ if (hand_tracking_index == 1) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n";
+ } else if (hand_tracking_index == 2) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"true\" />\n";
+ }
+ }
+ return manifest_xr_features;
+}
+
+String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) {
+ String package_name = p_preset->get("package/unique_name");
+ String manifest_instrumentation_text = vformat(
+ " <instrumentation\n"
+ " tools:node=\"replace\"\n"
+ " android:name=\".GodotInstrumentation\"\n"
+ " android:icon=\"@mipmap/icon\"\n"
+ " android:label=\"@string/godot_project_name_string\"\n"
+ " android:targetPackage=\"%s\" />\n",
+ package_name);
+ return manifest_instrumentation_text;
+}
+
+String _get_plugins_tag(const String &plugins_names) {
+ if (!plugins_names.empty()) {
+ return vformat(" <meta-data tools:node=\"replace\" android:name=\"plugins\" android:value=\"%s\" />\n", plugins_names);
+ } else {
+ return " <meta-data tools:node=\"remove\" android:name=\"plugins\" />\n";
+ }
+}
+
+String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ String orientation = (int)(p_preset->get("screen/orientation")) == 1 ? "portrait" : "landscape";
+ String manifest_activity_text = vformat(
+ " <activity android:name=\"com.godot.game.GodotApp\" "
+ "tools:replace=\"android:screenOrientation\" "
+ "android:screenOrientation=\"%s\">\n",
+ orientation);
+ if (uses_xr) {
+ String focus_awareness = bool_to_string(p_preset->get("xr_features/focus_awareness"));
+ manifest_activity_text += vformat(" <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"%s\" />\n", focus_awareness);
+ } else {
+ manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n";
+ }
+ manifest_activity_text += " </activity>\n";
+ return manifest_activity_text;
+}
+
+String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const String &plugins_names) {
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ String manifest_application_text =
+ " <application android:label=\"@string/godot_project_name_string\"\n"
+ " android:allowBackup=\"false\" tools:ignore=\"GoogleAppIndexingWarning\"\n"
+ " android:icon=\"@mipmap/icon\">)\n\n"
+ " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n";
+
+ manifest_application_text += _get_plugins_tag(plugins_names);
+ if (uses_xr) {
+ manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n";
+ }
+ manifest_application_text += _get_activity_tag(p_preset);
+ manifest_application_text += " </application>\n";
+ return manifest_application_text;
+}
+
#endif //GODOT_GRADLE_EXPORT_UTIL_H
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 19202d2310..3f8d138e8f 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -82,6 +82,8 @@ android {
// Feel free to modify the application id to your own.
applicationId getExportPackageName()
+ versionCode getExportVersionCode()
+ versionName getExportVersionName()
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
}
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index acfdef531e..7d5d238100 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -28,6 +28,22 @@ ext.getExportPackageName = { ->
return appId
}
+ext.getExportVersionCode = { ->
+ String versionCode = project.hasProperty("export_version_code") ? project.property("export_version_code") : ""
+ if (versionCode == null || versionCode.isEmpty()) {
+ versionCode = "1"
+ }
+ return Integer.parseInt(versionCode)
+}
+
+ext.getExportVersionName = { ->
+ String versionName = project.hasProperty("export_version_name") ? project.property("export_version_name") : ""
+ if (versionName == null || versionName.isEmpty()) {
+ versionName = "1.0"
+ }
+ return versionName
+}
+
final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"
/**
diff --git a/platform/android/java/lib/res/values-ar/strings.xml b/platform/android/java/app/res/values-ar/godot_project_name_string.xml
index 77cd61ea51..23aa5cf3e1 100644
--- a/platform/android/java/lib/res/values-ar/strings.xml
+++ b/platform/android/java/app/res/values-ar/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ar</string>
</resources>
diff --git a/platform/android/java/lib/res/values-bg/strings.xml b/platform/android/java/app/res/values-bg/godot_project_name_string.xml
index 0f42d1f22b..dbb7e04ae5 100644
--- a/platform/android/java/lib/res/values-bg/strings.xml
+++ b/platform/android/java/app/res/values-bg/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-bg</string>
</resources>
diff --git a/platform/android/java/lib/res/values-ca/strings.xml b/platform/android/java/app/res/values-ca/godot_project_name_string.xml
index 291a44d5e2..709d0961e6 100644
--- a/platform/android/java/lib/res/values-ca/strings.xml
+++ b/platform/android/java/app/res/values-ca/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ca</string>
</resources>
diff --git a/platform/android/java/lib/res/values-cs/strings.xml b/platform/android/java/app/res/values-cs/godot_project_name_string.xml
index 83ff73e12a..ab248a8032 100644
--- a/platform/android/java/lib/res/values-cs/strings.xml
+++ b/platform/android/java/app/res/values-cs/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-cs</string>
</resources>
diff --git a/platform/android/java/lib/res/values-da/strings.xml b/platform/android/java/app/res/values-da/godot_project_name_string.xml
index fd251a7c90..906bf44f57 100644
--- a/platform/android/java/lib/res/values-da/strings.xml
+++ b/platform/android/java/app/res/values-da/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-da</string>
</resources>
diff --git a/platform/android/java/lib/res/values-de/strings.xml b/platform/android/java/app/res/values-de/godot_project_name_string.xml
index f6e80b0b1a..0cacb0175f 100644
--- a/platform/android/java/lib/res/values-de/strings.xml
+++ b/platform/android/java/app/res/values-de/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-de</string>
</resources>
diff --git a/platform/android/java/lib/res/values-el/strings.xml b/platform/android/java/app/res/values-el/godot_project_name_string.xml
index adcdf13eb1..047de616a5 100644
--- a/platform/android/java/lib/res/values-el/strings.xml
+++ b/platform/android/java/app/res/values-el/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-el</string>
</resources>
diff --git a/platform/android/java/lib/res/values-en/strings.xml b/platform/android/java/app/res/values-en/godot_project_name_string.xml
index 1b251c9ab6..bb3a5dbef3 100644
--- a/platform/android/java/lib/res/values-en/strings.xml
+++ b/platform/android/java/app/res/values-en/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-en</string>
</resources>
diff --git a/platform/android/java/lib/res/values-es-rES/strings.xml b/platform/android/java/app/res/values-es-rES/godot_project_name_string.xml
index b580a8270b..d4537f3496 100644
--- a/platform/android/java/lib/res/values-es-rES/strings.xml
+++ b/platform/android/java/app/res/values-es-rES/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-es_ES</string>
</resources>
diff --git a/platform/android/java/lib/res/values-es/strings.xml b/platform/android/java/app/res/values-es/godot_project_name_string.xml
index 6aedd6870b..d63a16022e 100644
--- a/platform/android/java/lib/res/values-es/strings.xml
+++ b/platform/android/java/app/res/values-es/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-es</string>
</resources>
diff --git a/platform/android/java/app/res/values-fa/godot_project_name_string.xml b/platform/android/java/app/res/values-fa/godot_project_name_string.xml
new file mode 100644
index 0000000000..c303f13d5f
--- /dev/null
+++ b/platform/android/java/app/res/values-fa/godot_project_name_string.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">godot-project-name-fa</string>
+</resources>
diff --git a/platform/android/java/lib/res/values-fi/strings.xml b/platform/android/java/app/res/values-fi/godot_project_name_string.xml
index bd7ef059ab..bd6005574a 100644
--- a/platform/android/java/lib/res/values-fi/strings.xml
+++ b/platform/android/java/app/res/values-fi/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-fi</string>
</resources>
diff --git a/platform/android/java/lib/res/values-fr/strings.xml b/platform/android/java/app/res/values-fr/godot_project_name_string.xml
index 03994099cf..2e94b65a20 100644
--- a/platform/android/java/lib/res/values-fr/strings.xml
+++ b/platform/android/java/app/res/values-fr/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-fr</string>
</resources>
diff --git a/platform/android/java/lib/res/values-hi/strings.xml b/platform/android/java/app/res/values-hi/godot_project_name_string.xml
index 60d3b46861..0bf75dcd56 100644
--- a/platform/android/java/lib/res/values-hi/strings.xml
+++ b/platform/android/java/app/res/values-hi/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-hi</string>
</resources>
diff --git a/platform/android/java/lib/res/values-hr/strings.xml b/platform/android/java/app/res/values-hr/godot_project_name_string.xml
index e552a6f6ec..d3f75910f9 100644
--- a/platform/android/java/lib/res/values-hr/strings.xml
+++ b/platform/android/java/app/res/values-hr/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-hr</string>
</resources>
diff --git a/platform/android/java/lib/res/values-hu/strings.xml b/platform/android/java/app/res/values-hu/godot_project_name_string.xml
index ed21411acb..012b613af3 100644
--- a/platform/android/java/lib/res/values-hu/strings.xml
+++ b/platform/android/java/app/res/values-hu/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-hu</string>
</resources>
diff --git a/platform/android/java/app/res/values-in/godot_project_name_string.xml b/platform/android/java/app/res/values-in/godot_project_name_string.xml
new file mode 100644
index 0000000000..eedecff7a1
--- /dev/null
+++ b/platform/android/java/app/res/values-in/godot_project_name_string.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">godot-project-name-in</string>
+</resources>
diff --git a/platform/android/java/lib/res/values-it/strings.xml b/platform/android/java/app/res/values-it/godot_project_name_string.xml
index 880b87e030..7e734047c4 100644
--- a/platform/android/java/lib/res/values-it/strings.xml
+++ b/platform/android/java/app/res/values-it/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-it</string>
</resources>
diff --git a/platform/android/java/app/res/values-iw/godot_project_name_string.xml b/platform/android/java/app/res/values-iw/godot_project_name_string.xml
new file mode 100644
index 0000000000..03893f0cbb
--- /dev/null
+++ b/platform/android/java/app/res/values-iw/godot_project_name_string.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">godot-project-name-iw</string>
+</resources>
diff --git a/platform/android/java/lib/res/values-ja/strings.xml b/platform/android/java/app/res/values-ja/godot_project_name_string.xml
index 27d3ba521e..f9dd4fab0d 100644
--- a/platform/android/java/lib/res/values-ja/strings.xml
+++ b/platform/android/java/app/res/values-ja/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ja</string>
</resources>
diff --git a/platform/android/java/app/res/values-ko/godot_project_name_string.xml b/platform/android/java/app/res/values-ko/godot_project_name_string.xml
new file mode 100644
index 0000000000..26f5dac176
--- /dev/null
+++ b/platform/android/java/app/res/values-ko/godot_project_name_string.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">godot-project-name-ko</string>
+</resources>
diff --git a/platform/android/java/lib/res/values-lt/strings.xml b/platform/android/java/app/res/values-lt/godot_project_name_string.xml
index 10a93926db..1c2e976cc5 100644
--- a/platform/android/java/lib/res/values-lt/strings.xml
+++ b/platform/android/java/app/res/values-lt/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-lt</string>
</resources>
diff --git a/platform/android/java/lib/res/values-lv/strings.xml b/platform/android/java/app/res/values-lv/godot_project_name_string.xml
index 4f230b97f8..b5e638ed73 100644
--- a/platform/android/java/lib/res/values-lv/strings.xml
+++ b/platform/android/java/app/res/values-lv/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-lv</string>
</resources>
diff --git a/platform/android/java/lib/res/values-nb/strings.xml b/platform/android/java/app/res/values-nb/godot_project_name_string.xml
index a85a3da39a..e6d89d6a3f 100644
--- a/platform/android/java/lib/res/values-nb/strings.xml
+++ b/platform/android/java/app/res/values-nb/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-nb</string>
</resources>
diff --git a/platform/android/java/lib/res/values-nl/strings.xml b/platform/android/java/app/res/values-nl/godot_project_name_string.xml
index c459f64397..93cb3a3878 100644
--- a/platform/android/java/lib/res/values-nl/strings.xml
+++ b/platform/android/java/app/res/values-nl/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-nl</string>
</resources>
diff --git a/platform/android/java/lib/res/values-pl/strings.xml b/platform/android/java/app/res/values-pl/godot_project_name_string.xml
index 34a846cc78..e5d6ac74fb 100644
--- a/platform/android/java/lib/res/values-pl/strings.xml
+++ b/platform/android/java/app/res/values-pl/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-pl</string>
</resources>
diff --git a/platform/android/java/lib/res/values-pt/strings.xml b/platform/android/java/app/res/values-pt/godot_project_name_string.xml
index 5f7a875eb5..a4624655c5 100644
--- a/platform/android/java/lib/res/values-pt/strings.xml
+++ b/platform/android/java/app/res/values-pt/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-pt</string>
</resources>
diff --git a/platform/android/java/lib/res/values-ro/strings.xml b/platform/android/java/app/res/values-ro/godot_project_name_string.xml
index de990e789b..19e026637e 100644
--- a/platform/android/java/lib/res/values-ro/strings.xml
+++ b/platform/android/java/app/res/values-ro/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ro</string>
</resources>
diff --git a/platform/android/java/lib/res/values-ru/strings.xml b/platform/android/java/app/res/values-ru/godot_project_name_string.xml
index 73d8a27443..284845241f 100644
--- a/platform/android/java/lib/res/values-ru/strings.xml
+++ b/platform/android/java/app/res/values-ru/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-ru</string>
</resources>
diff --git a/platform/android/java/lib/res/values-sk/strings.xml b/platform/android/java/app/res/values-sk/godot_project_name_string.xml
index 053960efed..f8ab4a5b59 100644
--- a/platform/android/java/lib/res/values-sk/strings.xml
+++ b/platform/android/java/app/res/values-sk/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sk</string>
</resources>
diff --git a/platform/android/java/lib/res/values-sl/strings.xml b/platform/android/java/app/res/values-sl/godot_project_name_string.xml
index d6dff8289a..98bd53e8d2 100644
--- a/platform/android/java/lib/res/values-sl/strings.xml
+++ b/platform/android/java/app/res/values-sl/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sl</string>
</resources>
diff --git a/platform/android/java/lib/res/values-sr/strings.xml b/platform/android/java/app/res/values-sr/godot_project_name_string.xml
index b7e79e89ea..3f400f2a4d 100644
--- a/platform/android/java/lib/res/values-sr/strings.xml
+++ b/platform/android/java/app/res/values-sr/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sr</string>
</resources>
diff --git a/platform/android/java/lib/res/values-sv/strings.xml b/platform/android/java/app/res/values-sv/godot_project_name_string.xml
index 9436c3870a..8670b7c9aa 100644
--- a/platform/android/java/lib/res/values-sv/strings.xml
+++ b/platform/android/java/app/res/values-sv/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-sv</string>
</resources>
diff --git a/platform/android/java/lib/res/values-th/strings.xml b/platform/android/java/app/res/values-th/godot_project_name_string.xml
index 629d77b9c2..a1cc1bcd49 100644
--- a/platform/android/java/lib/res/values-th/strings.xml
+++ b/platform/android/java/app/res/values-th/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-th</string>
</resources>
diff --git a/platform/android/java/lib/res/values-tl/strings.xml b/platform/android/java/app/res/values-tl/godot_project_name_string.xml
index f8832d6b1f..6d66d114cf 100644
--- a/platform/android/java/lib/res/values-tl/strings.xml
+++ b/platform/android/java/app/res/values-tl/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-tl</string>
</resources>
diff --git a/platform/android/java/lib/res/values-tr/strings.xml b/platform/android/java/app/res/values-tr/godot_project_name_string.xml
index f3a8f57de4..ba3bd7de36 100644
--- a/platform/android/java/lib/res/values-tr/strings.xml
+++ b/platform/android/java/app/res/values-tr/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-tr</string>
</resources>
diff --git a/platform/android/java/lib/res/values-uk/strings.xml b/platform/android/java/app/res/values-uk/godot_project_name_string.xml
index 8ba2bf86aa..5f14ab25a0 100644
--- a/platform/android/java/lib/res/values-uk/strings.xml
+++ b/platform/android/java/app/res/values-uk/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-uk</string>
</resources>
diff --git a/platform/android/java/lib/res/values-vi/strings.xml b/platform/android/java/app/res/values-vi/godot_project_name_string.xml
index 8bf063ed82..295378e111 100644
--- a/platform/android/java/lib/res/values-vi/strings.xml
+++ b/platform/android/java/app/res/values-vi/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-vi</string>
</resources>
diff --git a/platform/android/java/lib/res/values-zh-rHK/strings.xml b/platform/android/java/app/res/values-zh-rHK/godot_project_name_string.xml
index 8a6269da0f..40ab0f285a 100644
--- a/platform/android/java/lib/res/values-zh-rHK/strings.xml
+++ b/platform/android/java/app/res/values-zh-rHK/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-zh_HK</string>
</resources>
diff --git a/platform/android/java/lib/res/values-zh-rTW/strings.xml b/platform/android/java/app/res/values-zh-rTW/godot_project_name_string.xml
index b1bb39d5d6..095bd564e2 100644
--- a/platform/android/java/lib/res/values-zh-rTW/strings.xml
+++ b/platform/android/java/app/res/values-zh-rTW/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-zh_TW</string>
</resources>
diff --git a/platform/android/java/lib/res/values-zh-rCN/strings.xml b/platform/android/java/app/res/values-zh/godot_project_name_string.xml
index 6668c56bd9..31aa8c273a 100644
--- a/platform/android/java/lib/res/values-zh-rCN/strings.xml
+++ b/platform/android/java/app/res/values-zh/godot_project_name_string.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
<resources>
<string name="godot_project_name_string">godot-project-name-zh</string>
</resources>
diff --git a/platform/android/java/app/res/values/godot_project_name_string.xml b/platform/android/java/app/res/values/godot_project_name_string.xml
new file mode 100644
index 0000000000..7ec2738896
--- /dev/null
+++ b/platform/android/java/app/res/values/godot_project_name_string.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME-->
+<resources>
+ <string name="godot_project_name_string">godot-project-name</string>
+</resources>
diff --git a/platform/android/java/lib/res/values-fa/strings.xml b/platform/android/java/lib/res/values-fa/strings.xml
index f1e29013c4..60b01accf1 100644
--- a/platform/android/java/lib/res/values-fa/strings.xml
+++ b/platform/android/java/lib/res/values-fa/strings.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <string name="godot_project_name_string">godot-project-name-fa</string>
<string name="text_paused_cellular">آیا می خواهید بر روی اتصال داده همراه دانلود را شروع کنید؟ بر اساس نوع سطح داده شما این ممکن است برای شما هزینه مالی داشته باشد.</string>
<string name="text_paused_cellular_2">اگر نمی خواهید بر روی اتصال داده همراه دانلود را شروع کنید ، دانلود به صورت خودکار در زمان دسترسی به وای-فای شروع می شود.</string>
<string name="text_button_resume_cellular">ادامه دانلود</string>
diff --git a/platform/android/java/lib/res/values-in/strings.xml b/platform/android/java/lib/res/values-in/strings.xml
deleted file mode 100644
index 169b65decb..0000000000
--- a/platform/android/java/lib/res/values-in/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="godot_project_name_string">godot-project-name-id</string>
-</resources>
diff --git a/platform/android/java/lib/res/values-iw/strings.xml b/platform/android/java/lib/res/values-iw/strings.xml
deleted file mode 100644
index b4826798c7..0000000000
--- a/platform/android/java/lib/res/values-iw/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="godot_project_name_string">godot-project-name-he</string>
-</resources>
diff --git a/platform/android/java/lib/res/values-ko/strings.xml b/platform/android/java/lib/res/values-ko/strings.xml
index efc5c7e015..7b62345977 100644
--- a/platform/android/java/lib/res/values-ko/strings.xml
+++ b/platform/android/java/lib/res/values-ko/strings.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <string name="godot_project_name_string">godot-project-name-ko</string>
<string name="text_paused_cellular">모바일 네트워크를 사용하여 다운로드 하시겠습니까? 남은 데이터 사용량에 따라, 요금이 부과될 수 있습니다.</string>
<string name="text_paused_cellular_2">모바일 네트워크를 사용하여 다운로드 하지 않을 경우, 와이파이 연결이 가능할 때 자동적으로 다운로드가 이루어집니다.</string>
<string name="text_button_resume_cellular">다운로드 계속하기</string>
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
index 1b55090451..1ae400abb5 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -467,6 +467,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
final Activity activity = getActivity();
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+ window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE);
pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
index 4da2f31250..d169f46599 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
@@ -117,6 +117,11 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
godot.onBackPressed();
}
+ @Override
+ public GodotInputHandler getInputHandler() {
+ return inputHandler;
+ }
+
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
index 27e63f3a66..68b8a16641 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
@@ -30,6 +30,8 @@
package org.godotengine.godot;
+import org.godotengine.godot.input.GodotInputHandler;
+
import android.view.SurfaceView;
public interface GodotRenderView {
@@ -43,4 +45,6 @@ public interface GodotRenderView {
abstract public void onActivityResumed();
abstract public void onBackPressed();
+
+ abstract public GodotInputHandler getInputHandler();
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
index aace593bae..65708389c3 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
@@ -90,6 +90,11 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
godot.onBackPressed();
}
+ @Override
+ public GodotInputHandler getInputHandler() {
+ return mInputHandler;
+ }
+
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
index 7f596575a8..c0defd008e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
@@ -155,14 +155,35 @@ public class GodotEditText extends EditText {
// ===========================================================
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
- super.onKeyDown(keyCode, keyEvent);
-
- /* Let GlSurfaceView get focus if back key is input. */
+ /* Let SurfaceView get focus if back key is input. */
if (keyCode == KeyEvent.KEYCODE_BACK) {
mRenderView.getView().requestFocus();
}
- return true;
+ // pass event to godot in special cases
+ if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyDown(keyCode, keyEvent)) {
+ return true;
+ } else {
+ return super.onKeyDown(keyCode, keyEvent);
+ }
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent keyEvent) {
+ if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyUp(keyCode, keyEvent)) {
+ return true;
+ } else {
+ return super.onKeyUp(keyCode, keyEvent);
+ }
+ }
+
+ private boolean needHandlingInGodot(int keyCode, KeyEvent keyEvent) {
+ boolean isArrowKey = keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
+ keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT;
+ boolean isModifiedKey = keyEvent.isAltPressed() || keyEvent.isCtrlPressed() || keyEvent.isSymPressed() ||
+ keyEvent.isFunctionPressed() || keyEvent.isMetaPressed();
+ return isArrowKey || keyCode == KeyEvent.KEYCODE_TAB || KeyEvent.isModifierKey(keyCode) ||
+ isModifiedKey;
}
// ===========================================================
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub
index b72d29149c..49c77468ed 100644
--- a/platform/iphone/SCsub
+++ b/platform/iphone/SCsub
@@ -3,10 +3,8 @@
Import("env")
iphone_lib = [
- "godot_iphone.cpp",
- "os_iphone.cpp",
- "semaphore_iphone.cpp",
- "gl_view.mm",
+ "godot_iphone.mm",
+ "os_iphone.mm",
"main.m",
"app_delegate.mm",
"view_controller.mm",
@@ -15,6 +13,12 @@ iphone_lib = [
"icloud.mm",
"ios.mm",
"vulkan_context_iphone.mm",
+ "display_server_iphone.mm",
+ "joypad_iphone.mm",
+ "godot_view.mm",
+ "display_layer.mm",
+ "godot_view_renderer.mm",
+ "godot_view_gesture_recognizer.m",
]
env_ios = env.Clone()
diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h
index 27552d781a..2f082f1e07 100644
--- a/platform/iphone/app_delegate.h
+++ b/platform/iphone/app_delegate.h
@@ -28,29 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#if defined(OPENGL_ENABLED)
-#import "gl_view.h"
-#endif
-#import "view_controller.h"
#import <UIKit/UIKit.h>
-#import <CoreMotion/CoreMotion.h>
+@class ViewController;
// FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented again,
// so it can't be done with compilation time branching.
//#if defined(OPENGL_ENABLED)
//@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {
//#endif
-#if defined(VULKAN_ENABLED)
-@interface AppDelegate : NSObject <UIApplicationDelegate> {
-#endif
- //@property (strong, nonatomic) UIWindow *window;
- ViewController *view_controller;
- bool is_focus_out;
-};
+//#if defined(VULKAN_ENABLED)
+@interface AppDelegate : NSObject <UIApplicationDelegate>
+//#endif
@property(strong, nonatomic) UIWindow *window;
-
-+ (ViewController *)getViewController;
+@property(strong, class, readonly, nonatomic) ViewController *viewController;
@end
diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm
index c4ef185bf1..7edbcc4667 100644
--- a/platform/iphone/app_delegate.mm
+++ b/platform/iphone/app_delegate.mm
@@ -29,644 +29,60 @@
/*************************************************************************/
#import "app_delegate.h"
-
#include "core/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
-#if defined(OPENGL_ENABLED)
-#import "gl_view.h"
-#endif
+#import "godot_view.h"
#include "main/main.h"
#include "os_iphone.h"
+#import "view_controller.h"
-#import "GameController/GameController.h"
#import <AudioToolbox/AudioServices.h>
-#define kFilteringFactor 0.1
#define kRenderingFrequency 60
-#define kAccelerometerFrequency 100.0 // Hz
-
-Error _shell_open(String);
-void _set_keep_screen_on(bool p_enabled);
-
-Error _shell_open(String p_uri) {
- NSString *url = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
-
- if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]])
- return ERR_CANT_OPEN;
-
- printf("opening url %ls\n", p_uri.c_str());
- [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
- [url release];
- return OK;
-};
-
-void _set_keep_screen_on(bool p_enabled) {
- [[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled];
-};
-
-void _vibrate() {
- AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
-};
-
-@implementation AppDelegate
-
-@synthesize window;
extern int gargc;
extern char **gargv;
-extern int iphone_main(int, int, int, char **, String);
+
+extern int iphone_main(int, char **, String);
extern void iphone_finish();
-CMMotionManager *motionManager;
-bool motionInitialised;
+@implementation AppDelegate
static ViewController *mainViewController = nil;
-+ (ViewController *)getViewController {
- return mainViewController;
-}
-
-NSMutableDictionary *ios_joysticks = nil;
-NSMutableArray *pending_ios_joysticks = nil;
-
-- (GCControllerPlayerIndex)getFreePlayerIndex {
- bool have_player_1 = false;
- bool have_player_2 = false;
- bool have_player_3 = false;
- bool have_player_4 = false;
-
- if (ios_joysticks == nil) {
- NSArray *keys = [ios_joysticks allKeys];
- for (NSNumber *key in keys) {
- GCController *controller = [ios_joysticks objectForKey:key];
- if (controller.playerIndex == GCControllerPlayerIndex1) {
- have_player_1 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex2) {
- have_player_2 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex3) {
- have_player_3 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex4) {
- have_player_4 = true;
- };
- };
- };
-
- if (!have_player_1) {
- return GCControllerPlayerIndex1;
- } else if (!have_player_2) {
- return GCControllerPlayerIndex2;
- } else if (!have_player_3) {
- return GCControllerPlayerIndex3;
- } else if (!have_player_4) {
- return GCControllerPlayerIndex4;
- } else {
- return GCControllerPlayerIndexUnset;
- };
-};
-
-void _ios_add_joystick(GCController *controller, AppDelegate *delegate) {
- // get a new id for our controller
- int joy_id = OSIPhone::get_singleton()->get_unused_joy_id();
- if (joy_id != -1) {
- // assign our player index
- if (controller.playerIndex == GCControllerPlayerIndexUnset) {
- controller.playerIndex = [delegate getFreePlayerIndex];
- };
-
- // tell Godot about our new controller
- OSIPhone::get_singleton()->joy_connection_changed(
- joy_id, true, [controller.vendorName UTF8String]);
-
- // add it to our dictionary, this will retain our controllers
- [ios_joysticks setObject:controller
- forKey:[NSNumber numberWithInt:joy_id]];
-
- // set our input handler
- [delegate setControllerInputHandler:controller];
- } else {
- printf("Couldn't retrieve new joy id\n");
- };
-}
-
-static void on_focus_out(ViewController *view_controller, bool *is_focus_out) {
- if (!*is_focus_out) {
- *is_focus_out = true;
- if (OS::get_singleton()->get_main_loop())
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_WM_FOCUS_OUT);
-
- [view_controller.view stopAnimation];
- if (OS::get_singleton()->native_video_is_playing()) {
- OSIPhone::get_singleton()->native_video_focus_out();
- }
- AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
- if (audio)
- audio->stop();
- }
-}
-
-static void on_focus_in(ViewController *view_controller, bool *is_focus_out) {
- if (*is_focus_out) {
- *is_focus_out = false;
- if (OS::get_singleton()->get_main_loop())
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_WM_FOCUS_IN);
-
- [view_controller.view startAnimation];
- if (OSIPhone::get_singleton()->native_video_is_playing()) {
- OSIPhone::get_singleton()->native_video_unpause();
- }
-
- AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
- if (audio)
- audio->start();
- }
++ (ViewController *)viewController {
+ return mainViewController;
}
-- (void)controllerWasConnected:(NSNotification *)notification {
- // create our dictionary if we don't have one yet
- if (ios_joysticks == nil) {
- ios_joysticks = [[NSMutableDictionary alloc] init];
- };
-
- // get our controller
- GCController *controller = (GCController *)notification.object;
- if (controller == nil) {
- printf("Couldn't retrieve new controller\n");
- } else if ([[ios_joysticks allKeysForObject:controller] count] != 0) {
- printf("Controller is already registered\n");
- } else if (frame_count > 1) {
- _ios_add_joystick(controller, self);
- } else {
- if (pending_ios_joysticks == nil)
- pending_ios_joysticks = [[NSMutableArray alloc] init];
- [pending_ios_joysticks addObject:controller];
- };
-};
-
-- (void)controllerWasDisconnected:(NSNotification *)notification {
- if (ios_joysticks != nil) {
- // find our joystick, there should be only one in our dictionary
- GCController *controller = (GCController *)notification.object;
- NSArray *keys = [ios_joysticks allKeysForObject:controller];
- for (NSNumber *key in keys) {
- // tell Godot this joystick is no longer there
- int joy_id = [key intValue];
- OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, "");
-
- // and remove it from our dictionary
- [ios_joysticks removeObjectForKey:key];
- };
- };
-};
-
-- (int)getJoyIdForController:(GCController *)controller {
- if (ios_joysticks != nil) {
- // find our joystick, there should be only one in our dictionary
- NSArray *keys = [ios_joysticks allKeysForObject:controller];
- for (NSNumber *key in keys) {
- int joy_id = [key intValue];
- return joy_id;
- };
- };
-
- return -1;
-};
-
-- (void)setControllerInputHandler:(GCController *)controller {
- // Hook in the callback handler for the correct gamepad profile.
- // This is a bit of a weird design choice on Apples part.
- // You need to select the most capable gamepad profile for the
- // gamepad attached.
- if (controller.extendedGamepad != nil) {
- // The extended gamepad profile has all the input you could possibly find on
- // a gamepad but will only be active if your gamepad actually has all of
- // these...
- controller.extendedGamepad.valueChangedHandler = ^(
- GCExtendedGamepad *gamepad, GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonB) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
- gamepad.buttonB.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.buttonY) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
- gamepad.buttonY.isPressed);
- } else if (element == gamepad.leftShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
- gamepad.leftShoulder.isPressed);
- } else if (element == gamepad.rightShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
- gamepad.rightShoulder.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
-
- InputDefault::JoyAxis jx;
- jx.min = -1;
- if (element == gamepad.leftThumbstick) {
- jx.value = gamepad.leftThumbstick.xAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx);
- jx.value = -gamepad.leftThumbstick.yAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx);
- } else if (element == gamepad.rightThumbstick) {
- jx.value = gamepad.rightThumbstick.xAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx);
- jx.value = -gamepad.rightThumbstick.yAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx);
- } else if (element == gamepad.leftTrigger) {
- jx.value = gamepad.leftTrigger.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx);
- } else if (element == gamepad.rightTrigger) {
- jx.value = gamepad.rightTrigger.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx);
- };
- };
- } else if (controller.gamepad != nil) {
- // gamepad is the standard profile with 4 buttons, shoulder buttons and a
- // D-pad
- controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
- GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonB) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
- gamepad.buttonB.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.buttonY) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
- gamepad.buttonY.isPressed);
- } else if (element == gamepad.leftShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
- gamepad.leftShoulder.isPressed);
- } else if (element == gamepad.rightShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
- gamepad.rightShoulder.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
- };
-#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
- // while we are setting that as the minimum, seems our
- // build environment doesn't like it
- } else if (controller.microGamepad != nil) {
- // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
- controller.microGamepad.valueChangedHandler =
- ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
- };
-#endif
- };
-
- ///@TODO need to add support for controller.motion which gives us access to
- /// the orientation of the device (if supported)
-
- ///@TODO need to add support for controllerPausedHandler which should be a
- /// toggle
-};
-
-- (void)initGameControllers {
- // get told when controllers connect, this will be called right away for
- // already connected controllers
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(controllerWasConnected:)
- name:GCControllerDidConnectNotification
- object:nil];
-
- // get told when controllers disconnect
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(controllerWasDisconnected:)
- name:GCControllerDidDisconnectNotification
- object:nil];
-};
-
-- (void)deinitGameControllers {
- [[NSNotificationCenter defaultCenter]
- removeObserver:self
- name:GCControllerDidConnectNotification
- object:nil];
- [[NSNotificationCenter defaultCenter]
- removeObserver:self
- name:GCControllerDidDisconnectNotification
- object:nil];
-
- if (ios_joysticks != nil) {
- [ios_joysticks dealloc];
- ios_joysticks = nil;
- };
-
- if (pending_ios_joysticks != nil) {
- [pending_ios_joysticks dealloc];
- pending_ios_joysticks = nil;
- };
-};
-
-OS::VideoMode _get_video_mode() {
- int backingWidth;
- int backingHeight;
-#if defined(OPENGL_ENABLED)
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
- GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
- GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-#endif
-
- OS::VideoMode vm;
- vm.fullscreen = true;
- vm.width = backingWidth;
- vm.height = backingHeight;
- vm.resizable = false;
- return vm;
-};
-
-static int frame_count = 0;
-- (void)drawView:(UIView *)view;
-{
- switch (frame_count) {
- case 0: {
- OS::get_singleton()->set_video_mode(_get_video_mode());
-
- if (!OS::get_singleton()) {
- exit(0);
- };
- ++frame_count;
-
- NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
- OSIPhone::get_singleton()->set_locale(
- String::utf8([locale_code UTF8String]));
-
- NSString *uuid;
- if ([[UIDevice currentDevice]
- respondsToSelector:@selector(identifierForVendor)]) {
- uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
- } else {
- // before iOS 6, so just generate an identifier and store it
- uuid = [[NSUserDefaults standardUserDefaults]
- objectForKey:@"identiferForVendor"];
- if (!uuid) {
- CFUUIDRef cfuuid = CFUUIDCreate(NULL);
- uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid);
- CFRelease(cfuuid);
- [[NSUserDefaults standardUserDefaults]
- setObject:uuid
- forKey:@"identifierForVendor"];
- }
- }
-
- OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
-
- }; break;
-
- case 1: {
- Main::setup2();
- ++frame_count;
-
- if (pending_ios_joysticks != nil) {
- for (GCController *controller in pending_ios_joysticks) {
- _ios_add_joystick(controller, self);
- }
- [pending_ios_joysticks dealloc];
- pending_ios_joysticks = nil;
- }
-
- // this might be necessary before here
- NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
- for (NSString *key in dict) {
- NSObject *value = [dict objectForKey:key];
- String ukey = String::utf8([key UTF8String]);
-
- // we need a NSObject to Variant conversor
-
- if ([value isKindOfClass:[NSString class]]) {
- NSString *str = (NSString *)value;
- String uval = String::utf8([str UTF8String]);
-
- ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
-
- } else if ([value isKindOfClass:[NSNumber class]]) {
- NSNumber *n = (NSNumber *)value;
- double dval = [n doubleValue];
-
- ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
- };
- // do stuff
- }
-
- }; break;
-
- case 2: {
- Main::start();
- ++frame_count;
-
- }; break; // no fallthrough
-
- default: {
- if (OSIPhone::get_singleton()) {
- // OSIPhone::get_singleton()->update_accelerometer(accel[0], accel[1],
- // accel[2]);
- if (motionInitialised) {
- // Just using polling approach for now, we can set this up so it sends
- // data to us in intervals, might be better. See Apple reference pages
- // for more details:
- // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
-
- // Apple splits our accelerometer date into a gravity and user movement
- // component. We add them back together
- CMAcceleration gravity = motionManager.deviceMotion.gravity;
- CMAcceleration acceleration =
- motionManager.deviceMotion.userAcceleration;
-
- ///@TODO We don't seem to be getting data here, is my device broken or
- /// is this code incorrect?
- CMMagneticField magnetic =
- motionManager.deviceMotion.magneticField.field;
-
- ///@TODO we can access rotationRate as a CMRotationRate variable
- ///(processed date) or CMGyroData (raw data), have to see what works
- /// best
- CMRotationRate rotation = motionManager.deviceMotion.rotationRate;
-
- // Adjust for screen orientation.
- // [[UIDevice currentDevice] orientation] changes even if we've fixed
- // our orientation which is not a good thing when you're trying to get
- // your user to move the screen in all directions and want consistent
- // output
-
- ///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
- /// is a bit of a hack. Godot obviously knows the orientation so maybe
- /// we
- // can use that instead? (note that left and right seem swapped)
-
- switch ([[UIApplication sharedApplication] statusBarOrientation]) {
- case UIDeviceOrientationLandscapeLeft: {
- OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- -(acceleration.y + gravity.y), (acceleration.x + gravity.x),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- -magnetic.y, magnetic.x, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x,
- rotation.z);
- }; break;
- case UIDeviceOrientationLandscapeRight: {
- OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- (acceleration.y + gravity.y), -(acceleration.x + gravity.x),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- magnetic.y, -magnetic.x, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x,
- rotation.z);
- }; break;
- case UIDeviceOrientationPortraitUpsideDown: {
- OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- -(acceleration.x + gravity.x), (acceleration.y + gravity.y),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- -magnetic.x, magnetic.y, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y,
- rotation.z);
- }; break;
- default: { // assume portrait
- OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- acceleration.x + gravity.x, acceleration.y + gravity.y,
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y,
- magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y,
- rotation.z);
- }; break;
- };
- }
-
- bool quit_request = OSIPhone::get_singleton()->iterate();
- };
-
- }; break;
- };
-};
-
-- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
- }
-};
-
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- CGRect rect = [[UIScreen mainScreen] bounds];
+ // TODO: might be required to make an early return, so app wouldn't crash because of timeout.
+ // TODO: logo screen is not displayed while shaders are compiling
+ // DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController
- is_focus_out = false;
-
- [application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
- // disable idle timer
- // application.idleTimerDisabled = YES;
+ CGRect windowBounds = [[UIScreen mainScreen] bounds];
// Create a full-screen window
- window = [[UIWindow alloc] initWithFrame:rect];
-
- OS::VideoMode vm = _get_video_mode();
+ self.window = [[[UIWindow alloc] initWithFrame:windowBounds] autorelease];
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
- NSUserDomainMask, YES);
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
- int err = iphone_main(vm.width, vm.height, gargc, gargv, String::utf8([documentsDirectory UTF8String]));
+ int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]));
+
if (err != 0) {
// bail, things did not go very well for us, should probably output a message on screen with our error code...
exit(0);
- return FALSE;
+ return NO;
};
-#if defined(OPENGL_ENABLED)
- // WARNING: We must *always* create the GLView after we have constructed the
- // OS with iphone_main. This allows the GLView to access project settings so
- // it can properly initialize the OpenGL context
- GLView *glView = [[GLView alloc] initWithFrame:rect];
- glView.delegate = self;
-
- view_controller = [[ViewController alloc] init];
- view_controller.view = glView;
-
- _set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO);
- glView.useCADisplayLink =
- bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
- printf("cadisaplylink: %d", glView.useCADisplayLink);
- glView.animationInterval = 1.0 / kRenderingFrequency;
- [glView startAnimation];
-#endif
-
-#if defined(VULKAN_ENABLED)
- view_controller = [[ViewController alloc] init];
-#endif
+ ViewController *viewController = [[ViewController alloc] init];
+ viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
+ viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
- window.rootViewController = view_controller;
+ self.window.rootViewController = viewController;
// Show the window
- [window makeKeyAndVisible];
-
- // Configure and start accelerometer
- if (!motionInitialised) {
- motionManager = [[CMMotionManager alloc] init];
- if (motionManager.deviceMotionAvailable) {
- motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
- [motionManager startDeviceMotionUpdatesUsingReferenceFrame:
- CMAttitudeReferenceFrameXMagneticNorthZVertical];
- motionInitialised = YES;
- };
- };
-
- [self initGameControllers];
+ [self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter]
addObserver:self
@@ -674,40 +90,33 @@ static int frame_count = 0;
name:AVAudioSessionInterruptionNotification
object:[AVAudioSession sharedInstance]];
- // OSIPhone::screen_width = rect.size.width - rect.origin.x;
- // OSIPhone::screen_height = rect.size.height - rect.origin.y;
-
- mainViewController = view_controller;
+ mainViewController = viewController;
// prevent to stop music in another background app
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
- return TRUE;
+ return YES;
};
- (void)onAudioInterruption:(NSNotification *)notification {
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
NSLog(@"Audio interruption began");
- on_focus_out(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_out();
} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
NSLog(@"Audio interruption ended");
- on_focus_in(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_in();
}
}
};
-- (void)applicationWillTerminate:(UIApplication *)application {
- [self deinitGameControllers];
-
- if (motionInitialised) {
- ///@TODO is this the right place to clean this up?
- [motionManager stopDeviceMotionUpdates];
- [motionManager release];
- motionManager = nil;
- motionInitialised = NO;
- };
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
+ }
+};
+- (void)applicationWillTerminate:(UIApplication *)application {
iphone_finish();
};
@@ -722,15 +131,15 @@ static int frame_count = 0;
// notification panel by swiping from the upper part of the screen.
- (void)applicationWillResignActive:(UIApplication *)application {
- on_focus_out(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_out();
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
- on_focus_in(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_in();
}
- (void)dealloc {
- [window release];
+ self.window = nil;
[super dealloc];
}
diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py
index 3e6c2f0ecf..6f67fc53c2 100644
--- a/platform/iphone/detect.py
+++ b/platform/iphone/detect.py
@@ -67,7 +67,7 @@ def configure(env):
elif env["target"] == "debug":
env.Append(CCFLAGS=["-gdwarf-2", "-O0"])
- env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"])
+ env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED"])
if env["use_lto"]:
env.Append(CCFLAGS=["-flto"])
@@ -120,18 +120,18 @@ def configure(env):
CCFLAGS=(
"-arch "
+ arch_flag
- + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0"
+ + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=13.0"
).split()
)
elif env["arch"] == "arm":
detect_darwin_sdk_path("iphone", env)
env.Append(
- CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split()
+ CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=11.0 -MMD -MT dependencies'.split()
)
elif env["arch"] == "arm64":
detect_darwin_sdk_path("iphone", env)
env.Append(
- CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK".split()
+ CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=11.0 -isysroot $IPHONESDK".split()
)
env.Append(CPPDEFINES=["NEED_LONG_INT"])
env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"])
@@ -143,6 +143,9 @@ def configure(env):
else:
env.Append(CCFLAGS=["-fno-exceptions"])
+ # Temp fix for ABS/MAX/MIN macros in iPhone SDK blocking compilation
+ env.Append(CCFLAGS=["-Wno-ambiguous-macro"])
+
## Link flags
if env["arch"] == "x86" or env["arch"] == "x86_64":
@@ -151,7 +154,7 @@ def configure(env):
LINKFLAGS=[
"-arch",
arch_flag,
- "-mios-simulator-version-min=10.0",
+ "-mios-simulator-version-min=13.0",
"-isysroot",
"$IPHONESDK",
"-Xlinker",
@@ -162,9 +165,9 @@ def configure(env):
]
)
elif env["arch"] == "arm":
- env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"])
+ env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])
if env["arch"] == "arm64":
- env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"])
+ env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])
env.Append(
LINKFLAGS=[
@@ -228,8 +231,7 @@ def configure(env):
env.Append(CPPDEFINES=["VULKAN_ENABLED"])
env.Append(LINKFLAGS=["-framework", "IOSurface"])
- if env["use_static_mvk"]:
- env.Append(LINKFLAGS=["-framework", "MoltenVK"])
- env["builtin_vulkan"] = False
- elif not env["builtin_vulkan"]:
- env.Append(LIBS=["vulkan"])
+
+ # Use Static Vulkan for iOS. Dynamic Framework works fine too.
+ env.Append(LINKFLAGS=["-framework", "MoltenVK"])
+ env["builtin_vulkan"] = False
diff --git a/platform/iphone/display_layer.h b/platform/iphone/display_layer.h
new file mode 100644
index 0000000000..bfde8f96a3
--- /dev/null
+++ b/platform/iphone/display_layer.h
@@ -0,0 +1,58 @@
+/*************************************************************************/
+/* display_layer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import <OpenGLES/EAGLDrawable.h>
+#import <QuartzCore/QuartzCore.h>
+
+@protocol DisplayLayer <NSObject>
+
+- (void)renderDisplayLayer;
+- (void)initializeDisplayLayer;
+- (void)layoutDisplayLayer;
+
+@end
+
+// An ugly workaround for iOS simulator
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+#if defined(__IPHONE_13_0)
+API_AVAILABLE(ios(13.0))
+@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
+#else
+@interface GodotMetalLayer : CALayer <DisplayLayer>
+#endif
+#else
+@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
+#endif
+@end
+
+API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0))
+@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer>
+
+@end
diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm
new file mode 100644
index 0000000000..5ec94fb471
--- /dev/null
+++ b/platform/iphone/display_layer.mm
@@ -0,0 +1,186 @@
+/*************************************************************************/
+/* display_layer.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "display_layer.h"
+#include "core/os/keyboard.h"
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <GameController/GameController.h>
+#import <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+@implementation GodotMetalLayer
+
+- (void)initializeDisplayLayer {
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+ if (@available(iOS 13, *)) {
+ // Simulator supports Metal since iOS 13
+ } else {
+ NSLog(@"iOS Simulator prior to iOS 13 does not support Metal rendering.");
+ }
+#endif
+}
+
+- (void)layoutDisplayLayer {
+}
+
+- (void)renderDisplayLayer {
+}
+
+@end
+
+@implementation GodotOpenGLLayer {
+ // The pixel dimensions of the backbuffer
+ GLint backingWidth;
+ GLint backingHeight;
+
+ EAGLContext *context;
+ GLuint viewRenderbuffer, viewFramebuffer;
+ GLuint depthRenderbuffer;
+}
+
+- (void)initializeDisplayLayer {
+ // Get our backing layer
+
+ // Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
+ self.opaque = YES;
+ self.drawableProperties = [NSDictionary
+ dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
+ kEAGLDrawablePropertyRetainedBacking,
+ kEAGLColorFormatRGBA8,
+ kEAGLDrawablePropertyColorFormat,
+ nil];
+
+ // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
+
+ // Create GL ES 2 context
+ if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") {
+ context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+ NSLog(@"Setting up an OpenGL ES 2.0 context.");
+ if (!context) {
+ NSLog(@"Failed to create OpenGL ES 2.0 context!");
+ return;
+ }
+ }
+
+ if (![EAGLContext setCurrentContext:context]) {
+ NSLog(@"Failed to set EAGLContext!");
+ return;
+ }
+ if (![self createFramebuffer]) {
+ NSLog(@"Failed to create frame buffer!");
+ return;
+ }
+}
+
+- (void)layoutDisplayLayer {
+ [EAGLContext setCurrentContext:context];
+ [self destroyFramebuffer];
+ [self createFramebuffer];
+}
+
+- (void)renderDisplayLayer {
+ [EAGLContext setCurrentContext:context];
+}
+
+- (void)dealloc {
+ if ([EAGLContext currentContext] == context) {
+ [EAGLContext setCurrentContext:nil];
+ }
+
+ if (context) {
+ [context release];
+ context = nil;
+ }
+
+ [super dealloc];
+}
+
+- (BOOL)createFramebuffer {
+ glGenFramebuffersOES(1, &viewFramebuffer);
+ glGenRenderbuffersOES(1, &viewRenderbuffer);
+
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
+ // This call associates the storage for the current render buffer with the EAGLDrawable (our CAself)
+ // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
+ [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self];
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
+
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
+
+ // For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
+ glGenRenderbuffersOES(1, &depthRenderbuffer);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
+ glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
+
+ if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
+ NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
+ return NO;
+ }
+
+ // if (OS::get_singleton()) {
+ // OS::VideoMode vm;
+ // vm.fullscreen = true;
+ // vm.width = backingWidth;
+ // vm.height = backingHeight;
+ // vm.resizable = false;
+ // OS::get_singleton()->set_video_mode(vm);
+ // OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
+ // };
+ // gl_view_base_fb = viewFramebuffer;
+
+ return YES;
+}
+
+// Clean up any buffers we have allocated.
+- (void)destroyFramebuffer {
+ glDeleteFramebuffersOES(1, &viewFramebuffer);
+ viewFramebuffer = 0;
+ glDeleteRenderbuffersOES(1, &viewRenderbuffer);
+ viewRenderbuffer = 0;
+
+ if (depthRenderbuffer) {
+ glDeleteRenderbuffersOES(1, &depthRenderbuffer);
+ depthRenderbuffer = 0;
+ }
+}
+
+@end
diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h
new file mode 100644
index 0000000000..e82cef4122
--- /dev/null
+++ b/platform/iphone/display_server_iphone.h
@@ -0,0 +1,202 @@
+/*************************************************************************/
+/* display_server_iphone.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef display_server_iphone_h
+#define display_server_iphone_h
+
+#include "core/input/input.h"
+#include "servers/display_server.h"
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+
+#include "vulkan_context_iphone.h"
+
+#import <QuartzCore/CAMetalLayer.h>
+#include <vulkan/vulkan_metal.h>
+#endif
+
+class DisplayServerIPhone : public DisplayServer {
+ GDCLASS(DisplayServerIPhone, DisplayServer)
+
+ _THREAD_SAFE_CLASS_
+
+#if defined(VULKAN_ENABLED)
+ VulkanContextIPhone *context_vulkan;
+ RenderingDeviceVulkan *rendering_device_vulkan;
+#endif
+
+ DisplayServer::ScreenOrientation screen_orientation;
+
+ ObjectID window_attached_instance_id;
+
+ Callable window_event_callback;
+ Callable window_resize_callback;
+ Callable input_event_callback;
+ Callable input_text_callback;
+
+ int virtual_keyboard_height = 0;
+
+ void perform_event(const Ref<InputEvent> &p_event);
+
+ DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerIPhone();
+
+public:
+ String rendering_driver;
+
+ static DisplayServerIPhone *get_singleton();
+
+ static void register_iphone_driver();
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ static Vector<String> get_rendering_drivers_func();
+
+ // MARK: - Events
+
+ virtual void process_events() override;
+
+ virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+ void send_input_event(const Ref<InputEvent> &p_event) const;
+ void send_input_text(const String &p_text) const;
+ void send_window_event(DisplayServer::WindowEvent p_event) const;
+ void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
+
+ // MARK: - Input
+
+ // MARK: Touches
+
+ void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
+ void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
+ void touches_cancelled(int p_idx);
+
+ // MARK: Keyboard
+
+ void key(uint32_t p_key, bool p_pressed);
+
+ // MARK: Motion
+
+ void update_gravity(float p_x, float p_y, float p_z);
+ void update_accelerometer(float p_x, float p_y, float p_z);
+ void update_magnetometer(float p_x, float p_y, float p_z);
+ void update_gyroscope(float p_x, float p_y, float p_z);
+
+ // MARK: -
+
+ virtual bool has_feature(Feature p_feature) const override;
+ virtual String get_name() const override;
+
+ virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
+
+ virtual int get_screen_count() const override;
+ virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+ virtual Vector<DisplayServer::WindowID> get_window_list() const override;
+
+ virtual WindowID
+ get_window_at_screen_position(const Point2i &p_position) const override;
+
+ virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
+
+ virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual float screen_get_max_scale() const override;
+
+ virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override;
+ virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override;
+
+ virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool can_any_window_draw() const override;
+
+ virtual bool screen_is_touchscreen(int p_screen) const override;
+
+ virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) override;
+ virtual void virtual_keyboard_hide() override;
+
+ void virtual_keyboard_set_height(int height);
+ virtual int virtual_keyboard_get_height() const override;
+
+ virtual void clipboard_set(const String &p_text) override;
+ virtual String clipboard_get() const override;
+
+ virtual void screen_set_keep_on(bool p_enable) override;
+ virtual bool screen_is_kept_on() const override;
+
+ virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
+ virtual bool native_video_is_playing() const override;
+ virtual void native_video_pause() override;
+ virtual void native_video_unpause() override;
+ virtual void native_video_stop() override;
+
+ void resize_window(CGSize size);
+};
+
+#endif /* display_server_iphone_h */
diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm
new file mode 100644
index 0000000000..498be89e48
--- /dev/null
+++ b/platform/iphone/display_server_iphone.mm
@@ -0,0 +1,751 @@
+/*************************************************************************/
+/* display_server_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "display_server_iphone.h"
+#import "app_delegate.h"
+#include "core/io/file_access_pack.h"
+#include "core/project_settings.h"
+#import "godot_view.h"
+#include "ios.h"
+#include "os_iphone.h"
+#import "view_controller.h"
+
+#import <Foundation/Foundation.h>
+#import <sys/utsname.h>
+
+static const float kDisplayServerIPhoneAcceleration = 1;
+static NSDictionary *iOSModelToDPI = @{
+ @[
+ @"iPad1,1",
+ @"iPad2,1",
+ @"iPad2,2",
+ @"iPad2,3",
+ @"iPad2,4",
+ ] : @132,
+ @[
+ @"iPhone1,1",
+ @"iPhone1,2",
+ @"iPhone2,1",
+ @"iPad2,5",
+ @"iPad2,6",
+ @"iPad2,7",
+ @"iPod1,1",
+ @"iPod2,1",
+ @"iPod3,1",
+ ] : @163,
+ @[
+ @"iPad3,1",
+ @"iPad3,2",
+ @"iPad3,3",
+ @"iPad3,4",
+ @"iPad3,5",
+ @"iPad3,6",
+ @"iPad4,1",
+ @"iPad4,2",
+ @"iPad4,3",
+ @"iPad5,3",
+ @"iPad5,4",
+ @"iPad6,3",
+ @"iPad6,4",
+ @"iPad6,7",
+ @"iPad6,8",
+ @"iPad6,11",
+ @"iPad6,12",
+ @"iPad7,1",
+ @"iPad7,2",
+ @"iPad7,3",
+ @"iPad7,4",
+ @"iPad7,5",
+ @"iPad7,6",
+ @"iPad7,11",
+ @"iPad7,12",
+ @"iPad8,1",
+ @"iPad8,2",
+ @"iPad8,3",
+ @"iPad8,4",
+ @"iPad8,5",
+ @"iPad8,6",
+ @"iPad8,7",
+ @"iPad8,8",
+ @"iPad8,9",
+ @"iPad8,10",
+ @"iPad8,11",
+ @"iPad8,12",
+ @"iPad11,3",
+ @"iPad11,4",
+ ] : @264,
+ @[
+ @"iPhone3,1",
+ @"iPhone3,2",
+ @"iPhone3,3",
+ @"iPhone4,1",
+ @"iPhone5,1",
+ @"iPhone5,2",
+ @"iPhone5,3",
+ @"iPhone5,4",
+ @"iPhone6,1",
+ @"iPhone6,2",
+ @"iPhone7,2",
+ @"iPhone8,1",
+ @"iPhone8,4",
+ @"iPhone9,1",
+ @"iPhone9,3",
+ @"iPhone10,1",
+ @"iPhone10,4",
+ @"iPhone11,8",
+ @"iPhone12,1",
+ @"iPhone12,8",
+ @"iPad4,4",
+ @"iPad4,5",
+ @"iPad4,6",
+ @"iPad4,7",
+ @"iPad4,8",
+ @"iPad4,9",
+ @"iPad5,1",
+ @"iPad5,2",
+ @"iPad11,1",
+ @"iPad11,2",
+ @"iPod4,1",
+ @"iPod5,1",
+ @"iPod7,1",
+ @"iPod9,1",
+ ] : @326,
+ @[
+ @"iPhone7,1",
+ @"iPhone8,2",
+ @"iPhone9,2",
+ @"iPhone9,4",
+ @"iPhone10,2",
+ @"iPhone10,5",
+ ] : @401,
+ @[
+ @"iPhone10,3",
+ @"iPhone10,6",
+ @"iPhone11,2",
+ @"iPhone11,4",
+ @"iPhone11,6",
+ @"iPhone12,3",
+ @"iPhone12,5",
+ ] : @458,
+};
+
+DisplayServerIPhone *DisplayServerIPhone::get_singleton() {
+ return (DisplayServerIPhone *)DisplayServer::get_singleton();
+}
+
+DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ rendering_driver = p_rendering_driver;
+
+#if defined(OPENGL_ENABLED)
+ // FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented
+ // again,
+
+ if (rendering_driver == "opengl_es") {
+ bool gl_initialization_error = false;
+
+ // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
+
+ if (RasterizerGLES2::is_viable() == OK) {
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ gl_initialization_error = true;
+ }
+
+ if (gl_initialization_error) {
+ OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", "Unable to initialize video driver");
+ // return ERR_UNAVAILABLE;
+ }
+
+ // rendering_server = memnew(RenderingServerRaster);
+ // // FIXME: Reimplement threaded rendering
+ // if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
+ // rendering_server = memnew(RenderingServerWrapMT(rendering_server,
+ // false));
+ // }
+ // rendering_server->init();
+ // rendering_server->cursor_set_visible(false, 0);
+
+ // reset this to what it should be, it will have been set to 0 after
+ // rendering_server->init() is called
+ // RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
+ }
+#endif
+
+#if defined(VULKAN_ENABLED)
+ rendering_driver = "vulkan";
+
+ context_vulkan = nullptr;
+ rendering_device_vulkan = nullptr;
+
+ if (rendering_driver == "vulkan") {
+ context_vulkan = memnew(VulkanContextIPhone);
+ if (context_vulkan->initialize() != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ ERR_FAIL_MSG("Failed to initialize Vulkan context");
+ }
+
+ CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"];
+
+ if (!layer) {
+ ERR_FAIL_MSG("Failed to create iOS rendering layer.");
+ }
+
+ Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
+ if (context_vulkan->window_create(MAIN_WINDOW_ID, layer, size.width, size.height) != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ ERR_FAIL_MSG("Failed to create Vulkan window.");
+ }
+
+ rendering_device_vulkan = memnew(RenderingDeviceVulkan);
+ rendering_device_vulkan->initialize(context_vulkan);
+
+ RasterizerRD::make_current();
+ }
+#endif
+
+ bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
+ screen_set_keep_on(keep_screen_on);
+
+ Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+
+ r_error = OK;
+}
+
+DisplayServerIPhone::~DisplayServerIPhone() {
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (rendering_device_vulkan) {
+ rendering_device_vulkan->finalize();
+ memdelete(rendering_device_vulkan);
+ rendering_device_vulkan = NULL;
+ }
+
+ if (context_vulkan) {
+ context_vulkan->window_destroy(MAIN_WINDOW_ID);
+ memdelete(context_vulkan);
+ context_vulkan = NULL;
+ }
+ }
+#endif
+}
+
+DisplayServer *DisplayServerIPhone::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ return memnew(DisplayServerIPhone(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+}
+
+Vector<String> DisplayServerIPhone::get_rendering_drivers_func() {
+ Vector<String> drivers;
+
+#if defined(VULKAN_ENABLED)
+ drivers.push_back("vulkan");
+#endif
+#if defined(OPENGL_ENABLED)
+ drivers.push_back("opengl_es");
+#endif
+
+ return drivers;
+}
+
+void DisplayServerIPhone::register_iphone_driver() {
+ register_create_function("iphone", create_func, get_rendering_drivers_func);
+}
+
+// MARK: Events
+
+void DisplayServerIPhone::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+ window_resize_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+ window_event_callback = p_callable;
+}
+void DisplayServerIPhone::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+ input_event_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+ input_text_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::process_events() {
+}
+
+void DisplayServerIPhone::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+ DisplayServerIPhone::get_singleton()->send_input_event(p_event);
+}
+
+void DisplayServerIPhone::send_input_event(const Ref<InputEvent> &p_event) const {
+ _window_callback(input_event_callback, p_event);
+}
+
+void DisplayServerIPhone::send_input_text(const String &p_text) const {
+ _window_callback(input_text_callback, p_text);
+}
+
+void DisplayServerIPhone::send_window_event(DisplayServer::WindowEvent p_event) const {
+ _window_callback(window_event_callback, int(p_event));
+}
+
+void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
+ if (!p_callable.is_null()) {
+ const Variant *argp = &p_arg;
+ Variant ret;
+ Callable::CallError ce;
+ p_callable.call((const Variant **)&argp, 1, ret, ce);
+ }
+}
+
+// MARK: - Input
+
+// MARK: Touches
+
+void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
+ if (!GLOBAL_DEF("debug/disable_touch", false)) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+
+ ev->set_index(p_idx);
+ ev->set_pressed(p_pressed);
+ ev->set_position(Vector2(p_x, p_y));
+ perform_event(ev);
+ }
+};
+
+void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
+ if (!GLOBAL_DEF("debug/disable_touch", false)) {
+ Ref<InputEventScreenDrag> ev;
+ ev.instance();
+ ev->set_index(p_idx);
+ ev->set_position(Vector2(p_x, p_y));
+ ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
+ perform_event(ev);
+ };
+};
+
+void DisplayServerIPhone::perform_event(const Ref<InputEvent> &p_event) {
+ Input::get_singleton()->parse_input_event(p_event);
+};
+
+void DisplayServerIPhone::touches_cancelled(int p_idx) {
+ touch_press(p_idx, -1, -1, false, false);
+};
+
+// MARK: Keyboard
+
+void DisplayServerIPhone::key(uint32_t p_key, bool p_pressed) {
+ Ref<InputEventKey> ev;
+ ev.instance();
+ ev->set_echo(false);
+ ev->set_pressed(p_pressed);
+ ev->set_keycode(p_key);
+ ev->set_physical_keycode(p_key);
+ ev->set_unicode(p_key);
+ perform_event(ev);
+};
+
+// MARK: Motion
+
+void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z));
+};
+
+void DisplayServerIPhone::update_accelerometer(float p_x, float p_y,
+ float p_z) {
+ // Found out the Z should not be negated! Pass as is!
+ Vector3 v_accelerometer = Vector3(
+ p_x / kDisplayServerIPhoneAcceleration,
+ p_y / kDisplayServerIPhoneAcceleration,
+ p_z / kDisplayServerIPhoneAcceleration);
+
+ Input::get_singleton()->set_accelerometer(v_accelerometer);
+
+ /*
+ if (p_x != last_accel.x) {
+ //printf("updating accel x %f\n", p_x);
+ InputEvent ev;
+ ev.type = InputEvent::JOYPAD_MOTION;
+ ev.device = 0;
+ ev.joy_motion.axis = JOY_ANALOG_0;
+ ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE);
+ last_accel.x = p_x;
+ queue_event(ev);
+ };
+ if (p_y != last_accel.y) {
+ //printf("updating accel y %f\n", p_y);
+ InputEvent ev;
+ ev.type = InputEvent::JOYPAD_MOTION;
+ ev.device = 0;
+ ev.joy_motion.axis = JOY_ANALOG_1;
+ ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE);
+ last_accel.y = p_y;
+ queue_event(ev);
+ };
+ if (p_z != last_accel.z) {
+ //printf("updating accel z %f\n", p_z);
+ InputEvent ev;
+ ev.type = InputEvent::JOYPAD_MOTION;
+ ev.device = 0;
+ ev.joy_motion.axis = JOY_ANALOG_2;
+ ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE);
+ last_accel.z = p_z;
+ queue_event(ev);
+ };
+ */
+};
+
+void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z));
+};
+
+void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z));
+};
+
+// MARK: -
+
+bool DisplayServerIPhone::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ // case FEATURE_CONSOLE_WINDOW:
+ // case FEATURE_CURSOR_SHAPE:
+ // case FEATURE_CUSTOM_CURSOR_SHAPE:
+ // case FEATURE_GLOBAL_MENU:
+ // case FEATURE_HIDPI:
+ // case FEATURE_ICON:
+ // case FEATURE_IME:
+ // case FEATURE_MOUSE:
+ // case FEATURE_MOUSE_WARP:
+ // case FEATURE_NATIVE_DIALOG:
+ // case FEATURE_NATIVE_ICON:
+ // case FEATURE_NATIVE_VIDEO:
+ // case FEATURE_WINDOW_TRANSPARENCY:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_KEEP_SCREEN_ON:
+ case FEATURE_ORIENTATION:
+ case FEATURE_TOUCHSCREEN:
+ case FEATURE_VIRTUAL_KEYBOARD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+String DisplayServerIPhone::get_name() const {
+ return "iPhone";
+}
+
+void DisplayServerIPhone::alert(const String &p_alert, const String &p_title) {
+ const CharString utf8_alert = p_alert.utf8();
+ const CharString utf8_title = p_title.utf8();
+ iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
+}
+
+int DisplayServerIPhone::get_screen_count() const {
+ return 1;
+}
+
+Point2i DisplayServerIPhone::screen_get_position(int p_screen) const {
+ return Size2i();
+}
+
+Size2i DisplayServerIPhone::screen_get_size(int p_screen) const {
+ CALayer *layer = AppDelegate.viewController.godotView.renderingLayer;
+
+ if (!layer) {
+ return Size2i();
+ }
+
+ return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
+}
+
+Rect2i DisplayServerIPhone::screen_get_usable_rect(int p_screen) const {
+ return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
+}
+
+int DisplayServerIPhone::screen_get_dpi(int p_screen) const {
+ struct utsname systemInfo;
+ uname(&systemInfo);
+
+ NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
+
+ for (NSArray *keyArray in iOSModelToDPI) {
+ if ([keyArray containsObject:string]) {
+ NSNumber *value = iOSModelToDPI[keyArray];
+ return [value intValue];
+ }
+ }
+
+ return 163;
+}
+
+float DisplayServerIPhone::screen_get_scale(int p_screen) const {
+ return [UIScreen mainScreen].nativeScale;
+}
+
+Vector<DisplayServer::WindowID> DisplayServerIPhone::get_window_list() const {
+ Vector<DisplayServer::WindowID> list;
+ list.push_back(MAIN_WINDOW_ID);
+ return list;
+}
+
+DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const Point2i &p_position) const {
+ return MAIN_WINDOW_ID;
+}
+
+void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+ window_attached_instance_id = p_instance;
+}
+
+ObjectID DisplayServerIPhone::window_get_attached_instance_id(WindowID p_window) const {
+ return window_attached_instance_id;
+}
+
+void DisplayServerIPhone::window_set_title(const String &p_title, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+int DisplayServerIPhone::window_get_current_screen(WindowID p_window) const {
+ return SCREEN_OF_MAIN_WINDOW;
+}
+
+void DisplayServerIPhone::window_set_current_screen(int p_screen, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Point2i DisplayServerIPhone::window_get_position(WindowID p_window) const {
+ return Point2i();
+}
+
+void DisplayServerIPhone::window_set_position(const Point2i &p_position, WindowID p_window) {
+ // Probably not supported for single window iOS app
+}
+
+void DisplayServerIPhone::window_set_transient(WindowID p_window, WindowID p_parent) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::window_set_max_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_max_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerIPhone::window_set_min_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_min_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerIPhone::window_set_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_size(WindowID p_window) const {
+ CGRect screenBounds = [UIScreen mainScreen].bounds;
+ return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale();
+}
+
+Size2i DisplayServerIPhone::window_get_real_size(WindowID p_window) const {
+ return window_get_size(p_window);
+}
+
+void DisplayServerIPhone::window_set_mode(WindowMode p_mode, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+DisplayServer::WindowMode DisplayServerIPhone::window_get_mode(WindowID p_window) const {
+ return WindowMode::WINDOW_MODE_FULLSCREEN;
+}
+
+bool DisplayServerIPhone::window_is_maximize_allowed(WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerIPhone::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+bool DisplayServerIPhone::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerIPhone::window_request_attention(WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+float DisplayServerIPhone::screen_get_max_scale() const {
+ return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
+};
+
+void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
+ screen_orientation = p_orientation;
+}
+
+DisplayServer::ScreenOrientation DisplayServerIPhone::screen_get_orientation(int p_screen) const {
+ return screen_orientation;
+}
+
+bool DisplayServerIPhone::window_can_draw(WindowID p_window) const {
+ return true;
+}
+
+bool DisplayServerIPhone::can_any_window_draw() const {
+ return true;
+}
+
+bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const {
+ return true;
+}
+
+void DisplayServerIPhone::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) {
+ [AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text];
+}
+
+void DisplayServerIPhone::virtual_keyboard_hide() {
+ [AppDelegate.viewController.godotView resignFirstResponder];
+}
+
+void DisplayServerIPhone::virtual_keyboard_set_height(int height) {
+ virtual_keyboard_height = height * screen_get_max_scale();
+}
+
+int DisplayServerIPhone::virtual_keyboard_get_height() const {
+ return virtual_keyboard_height;
+}
+
+void DisplayServerIPhone::clipboard_set(const String &p_text) {
+ [UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()];
+}
+
+String DisplayServerIPhone::clipboard_get() const {
+ NSString *text = [UIPasteboard generalPasteboard].string;
+
+ return String::utf8([text UTF8String]);
+}
+
+void DisplayServerIPhone::screen_set_keep_on(bool p_enable) {
+ [UIApplication sharedApplication].idleTimerDisabled = p_enable;
+}
+
+bool DisplayServerIPhone::screen_is_kept_on() const {
+ return [UIApplication sharedApplication].idleTimerDisabled;
+}
+
+Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) {
+ FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
+ bool exists = f && f->is_open();
+
+ String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir();
+
+ if (!exists) {
+ return FAILED;
+ }
+
+ String tempFile = OSIPhone::get_singleton()->get_user_data_dir();
+
+ if (p_path.begins_with("res://")) {
+ if (PackedData::get_singleton()->has_path(p_path)) {
+ printf("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str());
+ return ERR_INVALID_PARAMETER;
+ } else {
+ p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
+ }
+ } else if (p_path.begins_with("user://")) {
+ p_path = p_path.replace("user:/", user_data_dir);
+ }
+
+ memdelete(f);
+
+ printf("Playing video: %S\n", p_path.c_str());
+
+ String file_path = ProjectSettings::get_singleton()->globalize_path(p_path);
+
+ NSString *filePath = [[[NSString alloc] initWithUTF8String:file_path.utf8().get_data()] autorelease];
+ NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()];
+ NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()];
+
+ if (![AppDelegate.viewController playVideoAtPath:filePath
+ volume:p_volume
+ audio:audioTrack
+ subtitle:subtitleTrack]) {
+ return OK;
+ }
+
+ return FAILED;
+}
+
+bool DisplayServerIPhone::native_video_is_playing() const {
+ return [AppDelegate.viewController isVideoPlaying];
+}
+
+void DisplayServerIPhone::native_video_pause() {
+ if (native_video_is_playing()) {
+ [AppDelegate.viewController pauseVideo];
+ }
+}
+
+void DisplayServerIPhone::native_video_unpause() {
+ [AppDelegate.viewController unpauseVideo];
+};
+
+void DisplayServerIPhone::native_video_stop() {
+ if (native_video_is_playing()) {
+ [AppDelegate.viewController stopVideo];
+ }
+}
+
+void DisplayServerIPhone::resize_window(CGSize viewSize) {
+ Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
+
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (context_vulkan) {
+ context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y);
+ }
+ }
+#endif
+
+ Variant resize_rect = Rect2i(Point2i(), size);
+ _window_callback(window_resize_callback, resize_rect);
+}
diff --git a/platform/iphone/game_center.h b/platform/iphone/game_center.h
index 0d3ef5b696..1e9a68fe48 100644
--- a/platform/iphone/game_center.h
+++ b/platform/iphone/game_center.h
@@ -51,12 +51,12 @@ public:
void connect();
bool is_authenticated();
- Error post_score(Variant p_score);
- Error award_achievement(Variant p_params);
+ Error post_score(Dictionary p_score);
+ Error award_achievement(Dictionary p_params);
void reset_achievements();
void request_achievements();
void request_achievement_descriptions();
- Error show_game_center(Variant p_params);
+ Error show_game_center(Dictionary p_params);
Error request_identity_verification_signature();
void game_center_closed();
diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm
index 8d470da1a8..4481775c32 100644
--- a/platform/iphone/game_center.mm
+++ b/platform/iphone/game_center.mm
@@ -47,13 +47,15 @@ extern "C" {
#import "app_delegate.h"
};
+#import "view_controller.h"
+
GameCenter *GameCenter::instance = NULL;
void GameCenter::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated);
ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score);
- ClassDB::bind_method(D_METHOD("award_achievement"), &GameCenter::award_achievement);
+ ClassDB::bind_method(D_METHOD("award_achievement", "achievement"), &GameCenter::award_achievement);
ClassDB::bind_method(D_METHOD("reset_achievements"), &GameCenter::reset_achievements);
ClassDB::bind_method(D_METHOD("request_achievements"), &GameCenter::request_achievements);
ClassDB::bind_method(D_METHOD("request_achievement_descriptions"), &GameCenter::request_achievement_descriptions);
@@ -105,7 +107,14 @@ void GameCenter::connect() {
ret["type"] = "authentication";
if (player.isAuthenticated) {
ret["result"] = "ok";
- ret["player_id"] = [player.playerID UTF8String];
+ if (@available(iOS 13, *)) {
+ ret["player_id"] = [player.teamPlayerID UTF8String];
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ ret["player_id"] = [player.playerID UTF8String];
+#endif
+ }
+
GameCenter::get_singleton()->authenticated = true;
} else {
ret["result"] = "error";
@@ -123,11 +132,10 @@ bool GameCenter::is_authenticated() {
return authenticated;
};
-Error GameCenter::post_score(Variant p_score) {
- Dictionary params = p_score;
- ERR_FAIL_COND_V(!params.has("score") || !params.has("category"), ERR_INVALID_PARAMETER);
- float score = params["score"];
- String category = params["category"];
+Error GameCenter::post_score(Dictionary p_score) {
+ ERR_FAIL_COND_V(!p_score.has("score") || !p_score.has("category"), ERR_INVALID_PARAMETER);
+ float score = p_score["score"];
+ String category = p_score["category"];
NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease];
GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease];
@@ -153,11 +161,10 @@ Error GameCenter::post_score(Variant p_score) {
return OK;
};
-Error GameCenter::award_achievement(Variant p_params) {
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("name") || !params.has("progress"), ERR_INVALID_PARAMETER);
- String name = params["name"];
- float progress = params["progress"];
+Error GameCenter::award_achievement(Dictionary p_params) {
+ ERR_FAIL_COND_V(!p_params.has("name") || !p_params.has("progress"), ERR_INVALID_PARAMETER);
+ String name = p_params["name"];
+ float progress = p_params["progress"];
NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease];
@@ -167,8 +174,8 @@ Error GameCenter::award_achievement(Variant p_params) {
achievement.percentComplete = progress;
achievement.showsCompletionBanner = NO;
- if (params.has("show_completion_banner")) {
- achievement.showsCompletionBanner = params["show_completion_banner"] ? YES : NO;
+ if (p_params.has("show_completion_banner")) {
+ achievement.showsCompletionBanner = p_params["show_completion_banner"] ? YES : NO;
}
[GKAchievement reportAchievements:@[ achievement ]
@@ -202,7 +209,7 @@ void GameCenter::request_achievement_descriptions() {
Array hidden;
Array replayable;
- for (int i = 0; i < [descriptions count]; i++) {
+ for (NSUInteger i = 0; i < [descriptions count]; i++) {
GKAchievementDescription *description = [descriptions objectAtIndex:i];
const char *str = [description.identifier UTF8String];
@@ -250,7 +257,7 @@ void GameCenter::request_achievements() {
PackedStringArray names;
PackedFloat32Array percentages;
- for (int i = 0; i < [achievements count]; i++) {
+ for (NSUInteger i = 0; i < [achievements count]; i++) {
GKAchievement *achievement = [achievements objectAtIndex:i];
const char *str = [achievement.identifier UTF8String];
names.push_back(String::utf8(str != NULL ? str : ""));
@@ -285,14 +292,12 @@ void GameCenter::reset_achievements() {
}];
};
-Error GameCenter::show_game_center(Variant p_params) {
+Error GameCenter::show_game_center(Dictionary p_params) {
ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED);
- Dictionary params = p_params;
-
GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault;
- if (params.has("view")) {
- String view_name = params["view"];
+ if (p_params.has("view")) {
+ String view_name = p_params["view"];
if (view_name == "default") {
view_state = GKGameCenterViewControllerStateDefault;
} else if (view_name == "leaderboards") {
@@ -306,7 +311,7 @@ Error GameCenter::show_game_center(Variant p_params) {
}
}
- GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init];
+ GKGameCenterViewController *controller = [[[GKGameCenterViewController alloc] init] autorelease];
ERR_FAIL_COND_V(!controller, FAILED);
ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;
@@ -316,8 +321,8 @@ Error GameCenter::show_game_center(Variant p_params) {
controller.viewState = view_state;
if (view_state == GKGameCenterViewControllerStateLeaderboards) {
controller.leaderboardIdentifier = nil;
- if (params.has("leaderboard_name")) {
- String name = params["leaderboard_name"];
+ if (p_params.has("leaderboard_name")) {
+ String name = p_params["leaderboard_name"];
NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
controller.leaderboardIdentifier = name_str;
}
@@ -341,7 +346,13 @@ Error GameCenter::request_identity_verification_signature() {
ret["signature"] = [[signature base64EncodedStringWithOptions:0] UTF8String];
ret["salt"] = [[salt base64EncodedStringWithOptions:0] UTF8String];
ret["timestamp"] = timestamp;
- ret["player_id"] = [player.playerID UTF8String];
+ if (@available(iOS 13, *)) {
+ ret["player_id"] = [player.teamPlayerID UTF8String];
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ ret["player_id"] = [player.playerID UTF8String];
+#endif
+ }
} else {
ret["result"] = "error";
ret["error_code"] = (int64_t)error.code;
diff --git a/platform/iphone/gl_view.h b/platform/iphone/gl_view.h
deleted file mode 100644
index 975aa4b70a..0000000000
--- a/platform/iphone/gl_view.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/*************************************************************************/
-/* gl_view.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#import <AVFoundation/AVFoundation.h>
-#import <MediaPlayer/MediaPlayer.h>
-#import <OpenGLES/EAGL.h>
-#import <OpenGLES/ES1/gl.h>
-#import <OpenGLES/ES1/glext.h>
-#import <UIKit/UIKit.h>
-
-@protocol GLViewDelegate;
-
-@interface GLView : UIView <UIKeyInput> {
-@private
- // The pixel dimensions of the backbuffer
- GLint backingWidth;
- GLint backingHeight;
-
- EAGLContext *context;
-
- // OpenGL names for the renderbuffer and framebuffers used to render to this view
- GLuint viewRenderbuffer, viewFramebuffer;
-
- // OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist)
- GLuint depthRenderbuffer;
-
- BOOL useCADisplayLink;
- // CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
- CADisplayLink *displayLink;
-
- // An animation timer that, when animation is started, will periodically call -drawView at the given rate.
- // Only used if CADisplayLink is not
- NSTimer *animationTimer;
-
- NSTimeInterval animationInterval;
-
- // Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer.
- id<GLViewDelegate> delegate;
-
- // Flag to denote that the -setupView method of a delegate has been called.
- // Resets to NO whenever the delegate changes.
- BOOL delegateSetup;
- BOOL active;
- float screen_scale;
-}
-
-@property(nonatomic, assign) id<GLViewDelegate> delegate;
-
-// AVPlayer-related properties
-@property(strong, nonatomic) AVAsset *avAsset;
-@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
-@property(strong, nonatomic) AVPlayer *avPlayer;
-@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
-
-@property(strong, nonatomic) UIWindow *backgroundWindow;
-
-@property(nonatomic) UITextAutocorrectionType autocorrectionType;
-
-- (void)startAnimation;
-- (void)stopAnimation;
-- (void)drawView;
-
-- (BOOL)canBecomeFirstResponder;
-
-- (void)open_keyboard;
-- (void)hide_keyboard;
-- (void)deleteBackward;
-- (BOOL)hasText;
-- (void)insertText:(NSString *)p_text;
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification;
-- (void)keyboardOnScreen:(NSNotification *)notification;
-- (void)keyboardHidden:(NSNotification *)notification;
-
-@property NSTimeInterval animationInterval;
-@property(nonatomic, assign) BOOL useCADisplayLink;
-
-@end
-
-@protocol GLViewDelegate <NSObject>
-
-@required
-
-// Draw with OpenGL ES
-- (void)drawView:(GLView *)view;
-
-@optional
-
-// Called whenever you need to do some initialization before rendering.
-- (void)setupView:(GLView *)view;
-
-@end
diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm
deleted file mode 100644
index 1169ebc6b4..0000000000
--- a/platform/iphone/gl_view.mm
+++ /dev/null
@@ -1,702 +0,0 @@
-/*************************************************************************/
-/* gl_view.mm */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#import "gl_view.h"
-
-#include "core/os/keyboard.h"
-#include "core/project_settings.h"
-#include "os_iphone.h"
-#include "servers/audio_server.h"
-
-#import <OpenGLES/EAGLDrawable.h>
-#import <QuartzCore/QuartzCore.h>
-
-/*
-@interface GLView (private)
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-@end
-*/
-
-int gl_view_base_fb;
-static String keyboard_text;
-static GLView *_instance = NULL;
-
-static bool video_found_error = false;
-static bool video_playing = false;
-static CMTime video_current_time;
-
-void _show_keyboard(String);
-void _hide_keyboard();
-bool _play_video(String, float, String, String);
-bool _is_video_playing();
-void _pause_video();
-void _focus_out_video();
-void _unpause_video();
-void _stop_video();
-CGFloat _points_to_pixels(CGFloat);
-
-void _show_keyboard(String p_existing) {
- keyboard_text = p_existing;
- printf("instance on show is %p\n", _instance);
- [_instance open_keyboard];
-};
-
-void _hide_keyboard() {
- printf("instance on hide is %p\n", _instance);
- [_instance hide_keyboard];
- keyboard_text = "";
-};
-
-Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height) {
- UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0);
- if (_instance != nil && [_instance respondsToSelector:@selector(safeAreaInsets)]) {
- insets = [_instance safeAreaInsets];
- }
- ERR_FAIL_COND_V(insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0,
- Rect2(0, 0, p_window_width, p_window_height));
- UIEdgeInsets window_insets = UIEdgeInsetsMake(_points_to_pixels(insets.top), _points_to_pixels(insets.left), _points_to_pixels(insets.bottom), _points_to_pixels(insets.right));
- return Rect2(window_insets.left, window_insets.top, p_window_width - window_insets.right - window_insets.left, p_window_height - window_insets.bottom - window_insets.top);
-}
-
-bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
- p_path = ProjectSettings::get_singleton()->globalize_path(p_path);
-
- NSString *file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease];
-
- _instance.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:file_path]];
-
- _instance.avPlayerItem = [[AVPlayerItem alloc] initWithAsset:_instance.avAsset];
- [_instance.avPlayerItem addObserver:_instance forKeyPath:@"status" options:0 context:nil];
-
- _instance.avPlayer = [[AVPlayer alloc] initWithPlayerItem:_instance.avPlayerItem];
- _instance.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_instance.avPlayer];
-
- [_instance.avPlayer addObserver:_instance forKeyPath:@"status" options:0 context:nil];
- [[NSNotificationCenter defaultCenter]
- addObserver:_instance
- selector:@selector(playerItemDidReachEnd:)
- name:AVPlayerItemDidPlayToEndTimeNotification
- object:[_instance.avPlayer currentItem]];
-
- [_instance.avPlayer addObserver:_instance forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
-
- [_instance.avPlayerLayer setFrame:_instance.bounds];
- [_instance.layer addSublayer:_instance.avPlayerLayer];
- [_instance.avPlayer play];
-
- AVMediaSelectionGroup *audioGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
-
- NSMutableArray *allAudioParams = [NSMutableArray array];
- for (id track in audioGroup.options) {
- NSString *language = [[track locale] localeIdentifier];
- NSLog(@"subtitle lang: %@", language);
-
- if ([language isEqualToString:[NSString stringWithUTF8String:p_audio_track.utf8()]]) {
- AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
- [audioInputParams setVolume:p_volume atTime:kCMTimeZero];
- [audioInputParams setTrackID:[track trackID]];
- [allAudioParams addObject:audioInputParams];
-
- AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
- [audioMix setInputParameters:allAudioParams];
-
- [_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
- [_instance.avPlayer.currentItem setAudioMix:audioMix];
-
- break;
- }
- }
-
- AVMediaSelectionGroup *subtitlesGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
- NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
-
- for (id track in useableTracks) {
- NSString *language = [[track locale] localeIdentifier];
- NSLog(@"subtitle lang: %@", language);
-
- if ([language isEqualToString:[NSString stringWithUTF8String:p_subtitle_track.utf8()]]) {
- [_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
- break;
- }
- }
-
- video_playing = true;
-
- return true;
-}
-
-bool _is_video_playing() {
- if (_instance.avPlayer.error) {
- printf("Error during playback\n");
- }
- return (_instance.avPlayer.rate > 0 && !_instance.avPlayer.error);
-}
-
-void _pause_video() {
- video_current_time = _instance.avPlayer.currentTime;
- [_instance.avPlayer pause];
- video_playing = false;
-}
-
-void _focus_out_video() {
- printf("focus out pausing video\n");
- [_instance.avPlayer pause];
-};
-
-void _unpause_video() {
- [_instance.avPlayer play];
- video_playing = true;
-};
-
-void _stop_video() {
- [_instance.avPlayer pause];
- [_instance.avPlayerLayer removeFromSuperlayer];
- _instance.avPlayer = nil;
- video_playing = false;
-}
-
-CGFloat _points_to_pixels(CGFloat points) {
- float pixelPerInch;
- if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
- pixelPerInch = 132;
- } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
- pixelPerInch = 163;
- } else {
- pixelPerInch = 160;
- }
- CGFloat pointsPerInch = 72.0;
- return (points / pointsPerInch * pixelPerInch);
-}
-
-@implementation GLView
-
-@synthesize animationInterval;
-
-static const int max_touches = 8;
-static UITouch *touches[max_touches];
-
-static void init_touches() {
- for (int i = 0; i < max_touches; i++) {
- touches[i] = NULL;
- };
-};
-
-static int get_touch_id(UITouch *p_touch) {
- int first = -1;
- for (int i = 0; i < max_touches; i++) {
- if (first == -1 && touches[i] == NULL) {
- first = i;
- continue;
- };
- if (touches[i] == p_touch)
- return i;
- };
-
- if (first != -1) {
- touches[first] = p_touch;
- return first;
- };
-
- return -1;
-};
-
-static int remove_touch(UITouch *p_touch) {
- int remaining = 0;
- for (int i = 0; i < max_touches; i++) {
- if (touches[i] == NULL)
- continue;
- if (touches[i] == p_touch)
- touches[i] = NULL;
- else
- ++remaining;
- };
- return remaining;
-};
-
-static void clear_touches() {
- for (int i = 0; i < max_touches; i++) {
- touches[i] = NULL;
- };
-};
-
-// Implement this to override the default layer class (which is [CALayer class]).
-// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering.
-+ (Class)layerClass {
- return [CAEAGLLayer class];
-}
-
-//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
-- (id)initWithCoder:(NSCoder *)coder {
- active = FALSE;
- if ((self = [super initWithCoder:coder])) {
- self = [self initGLES];
- }
- return self;
-}
-
-- (id)initGLES {
- // Get our backing layer
- CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
-
- // Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
- eaglLayer.opaque = YES;
- eaglLayer.drawableProperties = [NSDictionary
- dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
- kEAGLDrawablePropertyRetainedBacking,
- kEAGLColorFormatRGBA8,
- kEAGLDrawablePropertyColorFormat,
- nil];
-
- // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
-
- // Create GL ES 2 context
- if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") {
- context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
- NSLog(@"Setting up an OpenGL ES 2.0 context.");
- if (!context) {
- NSLog(@"Failed to create OpenGL ES 2.0 context!");
- return nil;
- }
- }
-
- if (![EAGLContext setCurrentContext:context]) {
- NSLog(@"Failed to set EAGLContext!");
- return nil;
- }
- if (![self createFramebuffer]) {
- NSLog(@"Failed to create frame buffer!");
- return nil;
- }
-
- // Default the animation interval to 1/60th of a second.
- animationInterval = 1.0 / 60.0;
- return self;
-}
-
-- (id<GLViewDelegate>)delegate {
- return delegate;
-}
-
-// Update the delegate, and if it needs a -setupView: call, set our internal flag so that it will be called.
-- (void)setDelegate:(id<GLViewDelegate>)d {
- delegate = d;
- delegateSetup = ![delegate respondsToSelector:@selector(setupView:)];
-}
-
-@synthesize useCADisplayLink;
-
-// If our view is resized, we'll be asked to layout subviews.
-// This is the perfect opportunity to also update the framebuffer so that it is
-// the same size as our display area.
-
-- (void)layoutSubviews {
- [EAGLContext setCurrentContext:context];
- [self destroyFramebuffer];
- [self createFramebuffer];
- [self drawView];
-}
-
-- (BOOL)createFramebuffer {
- // Generate IDs for a framebuffer object and a color renderbuffer
- UIScreen *mainscr = [UIScreen mainScreen];
- printf("******** screen size %i, %i\n", (int)mainscr.currentMode.size.width, (int)mainscr.currentMode.size.height);
- self.contentScaleFactor = mainscr.nativeScale;
-
- glGenFramebuffersOES(1, &viewFramebuffer);
- glGenRenderbuffersOES(1, &viewRenderbuffer);
-
- glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
- // This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer)
- // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
- [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
- glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
-
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-
- // For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
- glGenRenderbuffersOES(1, &depthRenderbuffer);
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
- glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
- glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
-
- if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
- NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
- return NO;
- }
-
- if (OS::get_singleton()) {
- OS::VideoMode vm;
- vm.fullscreen = true;
- vm.width = backingWidth;
- vm.height = backingHeight;
- vm.resizable = false;
- OS::get_singleton()->set_video_mode(vm);
- OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
- };
- gl_view_base_fb = viewFramebuffer;
-
- return YES;
-}
-
-// Clean up any buffers we have allocated.
-- (void)destroyFramebuffer {
- glDeleteFramebuffersOES(1, &viewFramebuffer);
- viewFramebuffer = 0;
- glDeleteRenderbuffersOES(1, &viewRenderbuffer);
- viewRenderbuffer = 0;
-
- if (depthRenderbuffer) {
- glDeleteRenderbuffersOES(1, &depthRenderbuffer);
- depthRenderbuffer = 0;
- }
-}
-
-- (void)startAnimation {
- if (active)
- return;
- active = TRUE;
- printf("start animation!\n");
- if (useCADisplayLink) {
- // Approximate frame rate
- // assumes device refreshes at 60 fps
- int frameInterval = (int)floor(animationInterval * 60.0f);
-
- displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
- [displayLink setFrameInterval:frameInterval];
-
- // Setup DisplayLink in main thread
- [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
- } else {
- animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
- }
-
- if (video_playing) {
- _unpause_video();
- }
-}
-
-- (void)stopAnimation {
- if (!active)
- return;
- active = FALSE;
- printf("******** stop animation!\n");
-
- if (useCADisplayLink) {
- [displayLink invalidate];
- displayLink = nil;
- } else {
- [animationTimer invalidate];
- animationTimer = nil;
- }
-
- clear_touches();
-
- if (video_playing) {
- // save position
- }
-}
-
-- (void)setAnimationInterval:(NSTimeInterval)interval {
- animationInterval = interval;
- if ((useCADisplayLink && displayLink) || (!useCADisplayLink && animationTimer)) {
- [self stopAnimation];
- [self startAnimation];
- }
-}
-
-// Updates the OpenGL view when the timer fires
-- (void)drawView {
- if (!active) {
- printf("draw view not active!\n");
- return;
- };
- if (useCADisplayLink) {
- // Pause the CADisplayLink to avoid recursion
- [displayLink setPaused:YES];
-
- // Process all input events
- while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
- ;
-
- // We are good to go, resume the CADisplayLink
- [displayLink setPaused:NO];
- }
-
- // Make sure that you are drawing to the current context
- [EAGLContext setCurrentContext:context];
-
- // If our drawing delegate needs to have the view setup, then call -setupView: and flag that it won't need to be called again.
- if (!delegateSetup) {
- [delegate setupView:self];
- delegateSetup = YES;
- }
-
- glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
-
- [delegate drawView:self];
-
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
- [context presentRenderbuffer:GL_RENDERBUFFER_OES];
-
-#ifdef DEBUG_ENABLED
- GLenum err = glGetError();
- if (err)
- NSLog(@"DrawView: %x error", err);
-#endif
-}
-
-- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
- NSArray *tlist = [[event allTouches] allObjects];
- for (unsigned int i = 0; i < [tlist count]; i++) {
- if ([touches containsObject:[tlist objectAtIndex:i]]) {
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseBegan)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- CGPoint touchPoint = [touch locationInView:self];
- OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
- };
- };
-}
-
-- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
- NSArray *tlist = [[event allTouches] allObjects];
- for (unsigned int i = 0; i < [tlist count]; i++) {
- if ([touches containsObject:[tlist objectAtIndex:i]]) {
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseMoved)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- CGPoint touchPoint = [touch locationInView:self];
- CGPoint prev_point = [touch previousLocationInView:self];
- OSIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
- };
- };
-}
-
-- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
- NSArray *tlist = [[event allTouches] allObjects];
- for (unsigned int i = 0; i < [tlist count]; i++) {
- if ([touches containsObject:[tlist objectAtIndex:i]]) {
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseEnded)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- remove_touch(touch);
- CGPoint touchPoint = [touch locationInView:self];
- OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
- };
- };
-}
-
-- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
- OSIPhone::get_singleton()->touches_cancelled();
- clear_touches();
-};
-
-- (BOOL)canBecomeFirstResponder {
- return YES;
-};
-
-- (void)open_keyboard {
- //keyboard_text = p_existing;
- [self becomeFirstResponder];
-};
-
-- (void)hide_keyboard {
- //keyboard_text = p_existing;
- [self resignFirstResponder];
-};
-
-- (void)keyboardOnScreen:(NSNotification *)notification {
- NSDictionary *info = notification.userInfo;
- NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
-
- CGRect rawFrame = [value CGRectValue];
- CGRect keyboardFrame = [self convertRect:rawFrame fromView:nil];
-
- OSIPhone::get_singleton()->set_virtual_keyboard_height(_points_to_pixels(keyboardFrame.size.height));
-}
-
-- (void)keyboardHidden:(NSNotification *)notification {
- OSIPhone::get_singleton()->set_virtual_keyboard_height(0);
-}
-
-- (void)deleteBackward {
- if (keyboard_text.length())
- keyboard_text.erase(keyboard_text.length() - 1, 1);
- OSIPhone::get_singleton()->key(KEY_BACKSPACE, true);
-};
-
-- (BOOL)hasText {
- return keyboard_text.length() ? YES : NO;
-};
-
-- (void)insertText:(NSString *)p_text {
- String character;
- character.parse_utf8([p_text UTF8String]);
- keyboard_text = keyboard_text + character;
- OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
- printf("inserting text with character %lc\n", (CharType)character[0]);
-};
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
- printf("*********** route changed!\n");
- NSDictionary *interuptionDict = notification.userInfo;
-
- NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
-
- switch (routeChangeReason) {
- case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
- NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
- NSLog(@"Headphone/Line plugged in");
- }; break;
-
- case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
- NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
- NSLog(@"Headphone/Line was pulled. Resuming video play....");
- if (_is_video_playing()) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [_instance.avPlayer play]; // NOTE: change this line according your current player implementation
- NSLog(@"resumed play");
- });
- };
- }; break;
-
- case AVAudioSessionRouteChangeReasonCategoryChange: {
- // called at start - also when other audio wants to play
- NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
- }; break;
- }
-}
-
-// When created via code however, we get initWithFrame
-- (id)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- _instance = self;
- printf("after init super %p\n", self);
- if (self != nil) {
- self = [self initGLES];
- printf("after init gles %p\n", self);
- }
- init_touches();
- self.multipleTouchEnabled = YES;
- self.autocorrectionType = UITextAutocorrectionTypeNo;
-
- printf("******** adding observer for sound routing changes\n");
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(audioRouteChangeListenerCallback:)
- name:AVAudioSessionRouteChangeNotification
- object:nil];
-
- printf("******** adding observer for keyboard show/hide\n");
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(keyboardOnScreen:)
- name:UIKeyboardDidShowNotification
- object:nil];
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(keyboardHidden:)
- name:UIKeyboardDidHideNotification
- object:nil];
-
- //self.autoresizesSubviews = YES;
- //[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth];
-
- return self;
-}
-
-//- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
-// return YES;
-//}
-
-//- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
-// return YES;
-//}
-
-// Stop animating and release resources when they are no longer needed.
-- (void)dealloc {
- [self stopAnimation];
-
- if ([EAGLContext currentContext] == context) {
- [EAGLContext setCurrentContext:nil];
- }
-
- [context release];
- context = nil;
-
- [super dealloc];
-}
-
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
- if (object == _instance.avPlayerItem && [keyPath isEqualToString:@"status"]) {
- if (_instance.avPlayerItem.status == AVPlayerStatusFailed || _instance.avPlayer.status == AVPlayerStatusFailed) {
- _stop_video();
- video_found_error = true;
- }
-
- if (_instance.avPlayer.status == AVPlayerStatusReadyToPlay &&
- _instance.avPlayerItem.status == AVPlayerItemStatusReadyToPlay &&
- CMTIME_COMPARE_INLINE(video_current_time, ==, kCMTimeZero)) {
- //NSLog(@"time: %@", video_current_time);
-
- [_instance.avPlayer seekToTime:video_current_time];
- video_current_time = kCMTimeZero;
- }
- }
-
- if (object == _instance.avPlayer && [keyPath isEqualToString:@"rate"]) {
- NSLog(@"Player playback rate changed: %.5f", _instance.avPlayer.rate);
- if (_is_video_playing() && _instance.avPlayer.rate == 0.0 && !_instance.avPlayer.error) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [_instance.avPlayer play]; // NOTE: change this line according your current player implementation
- NSLog(@"resumed play");
- });
-
- NSLog(@" . . . PAUSED (or just started)");
- }
- }
-}
-
-- (void)playerItemDidReachEnd:(NSNotification *)notification {
- _stop_video();
-}
-
-@end
diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.mm
index b9d217c9d2..090b772947 100644
--- a/platform/iphone/godot_iphone.cpp
+++ b/platform/iphone/godot_iphone.mm
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* godot_iphone.cpp */
+/* godot_iphone.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -38,19 +38,53 @@
static OSIPhone *os = nullptr;
-extern "C" {
-int add_path(int p_argc, char **p_args);
-int add_cmdline(int p_argc, char **p_args);
+int add_path(int, char **);
+int add_cmdline(int, char **);
+int iphone_main(int, char **, String);
+
+int add_path(int p_argc, char **p_args) {
+ NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
+ if (!str) {
+ return p_argc;
+ }
+
+ p_args[p_argc++] = (char *)"--path";
+ [str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
+ p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+ p_args[p_argc] = NULL;
+ [str release];
+
+ return p_argc;
};
-int iphone_main(int, int, int, char **, String);
+int add_cmdline(int p_argc, char **p_args) {
+ NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
+ if (!arr) {
+ return p_argc;
+ }
+
+ for (NSUInteger i = 0; i < [arr count]; i++) {
+ NSString *str = [arr objectAtIndex:i];
+ if (!str) {
+ continue;
+ }
+ [str retain]; // @todo delete these at some point
+ p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+ [str release];
+ };
+
+ p_args[p_argc] = NULL;
+
+ return p_argc;
+};
-int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
+int iphone_main(int argc, char **argv, String data_dir) {
size_t len = strlen(argv[0]);
while (len--) {
- if (argv[0][len] == '/')
+ if (argv[0][len] == '/') {
break;
+ }
}
if (len >= 0) {
@@ -65,7 +99,10 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
char cwd[512];
getcwd(cwd, sizeof(cwd));
printf("cwd %s\n", cwd);
- os = new OSIPhone(width, height, data_dir);
+ os = new OSIPhone(data_dir);
+
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
char *fargv[64];
for (int i = 0; i < argc; i++) {
@@ -76,10 +113,14 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
argc = add_cmdline(argc, fargv);
printf("os created\n");
+
Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
printf("setup %i\n", err);
- if (err != OK)
+ if (err != OK) {
return 255;
+ }
+
+ os->initialize_modules();
return 0;
};
diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h
new file mode 100644
index 0000000000..62fa2f5a32
--- /dev/null
+++ b/platform/iphone/godot_view.h
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* godot_view.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+class String;
+
+@protocol DisplayLayer;
+@protocol GodotViewRendererProtocol;
+
+@interface GodotView : UIView <UIKeyInput>
+
+@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
+
+@property(assign, readonly, nonatomic) BOOL isActive;
+
+@property(assign, nonatomic) BOOL useCADisplayLink;
+@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+@property(assign, readonly, nonatomic) BOOL canRender;
+
+@property(assign, nonatomic) NSTimeInterval renderingInterval;
+
+- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
+- (void)stopRendering;
+- (void)startRendering;
+
+- (BOOL)becomeFirstResponderWithString:(String)p_existing;
+
+@end
diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm
new file mode 100644
index 0000000000..c0a31549c4
--- /dev/null
+++ b/platform/iphone/godot_view.mm
@@ -0,0 +1,499 @@
+/*************************************************************************/
+/* godot_view.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view.h"
+#include "core/os/keyboard.h"
+#include "core/ustring.h"
+#import "display_layer.h"
+#include "display_server_iphone.h"
+#import "godot_view_gesture_recognizer.h"
+#import "godot_view_renderer.h"
+
+#import <CoreMotion/CoreMotion.h>
+
+static const int max_touches = 8;
+
+@interface GodotView () {
+ UITouch *godot_touches[max_touches];
+ String keyboard_text;
+}
+
+@property(assign, nonatomic) BOOL isActive;
+
+// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
+@property(strong, nonatomic) CADisplayLink *displayLink;
+
+// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
+// Only used if CADisplayLink is not
+@property(strong, nonatomic) NSTimer *animationTimer;
+
+@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+
+@property(strong, nonatomic) CMMotionManager *motionManager;
+
+@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer;
+
+@end
+
+@implementation GodotView
+
+- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
+ if (self.renderingLayer) {
+ return self.renderingLayer;
+ }
+
+ CALayer<DisplayLayer> *layer;
+
+ if ([driverName isEqualToString:@"vulkan"]) {
+ layer = [GodotMetalLayer layer];
+ } else if ([driverName isEqualToString:@"opengl_es"]) {
+ if (@available(iOS 13, *)) {
+ NSLog(@"OpenGL ES is deprecated on iOS 13");
+ }
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+ return nil;
+#else
+ layer = [GodotOpenGLLayer layer];
+#endif
+ } else {
+ return nil;
+ }
+
+ layer.frame = self.bounds;
+ layer.contentsScale = self.contentScaleFactor;
+
+ [self.layer addSublayer:layer];
+ self.renderingLayer = layer;
+
+ [layer initializeDisplayLayer];
+
+ return self.renderingLayer;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [self stopRendering];
+
+ self.renderer = nil;
+
+ if (self.renderingLayer) {
+ [self.renderingLayer removeFromSuperlayer];
+ self.renderingLayer = nil;
+ }
+
+ if (self.motionManager) {
+ [self.motionManager stopDeviceMotionUpdates];
+ self.motionManager = nil;
+ }
+
+ if (self.displayLink) {
+ [self.displayLink invalidate];
+ self.displayLink = nil;
+ }
+
+ if (self.animationTimer) {
+ [self.animationTimer invalidate];
+ self.animationTimer = nil;
+ }
+
+ if (self.delayGestureRecognizer) {
+ self.delayGestureRecognizer = nil;
+ }
+
+ [super dealloc];
+}
+
+- (void)godot_commonInit {
+ self.contentScaleFactor = [UIScreen mainScreen].nativeScale;
+
+ [self initTouches];
+
+ // Configure and start accelerometer
+ if (!self.motionManager) {
+ self.motionManager = [[[CMMotionManager alloc] init] autorelease];
+ if (self.motionManager.deviceMotionAvailable) {
+ self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
+ [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical];
+ } else {
+ self.motionManager = nil;
+ }
+ }
+
+ // Initialize delay gesture recognizer
+ GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init];
+ self.delayGestureRecognizer = gestureRecognizer;
+ [self addGestureRecognizer:self.delayGestureRecognizer];
+ [gestureRecognizer release];
+}
+
+- (void)stopRendering {
+ if (!self.isActive) {
+ return;
+ }
+
+ self.isActive = NO;
+
+ printf("******** stop animation!\n");
+
+ if (self.useCADisplayLink) {
+ [self.displayLink invalidate];
+ self.displayLink = nil;
+ } else {
+ [self.animationTimer invalidate];
+ self.animationTimer = nil;
+ }
+
+ [self clearTouches];
+}
+
+- (void)startRendering {
+ if (self.isActive) {
+ return;
+ }
+
+ self.isActive = YES;
+
+ printf("start animation!\n");
+
+ if (self.useCADisplayLink) {
+ self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
+
+ // if (@available(iOS 10, *)) {
+ self.displayLink.preferredFramesPerSecond = (NSInteger)(1.0 / self.renderingInterval);
+ // } else {
+ // // Approximate frame rate
+ // // assumes device refreshes at 60 fps
+ // int frameInterval = (int)floor(self.renderingInterval * 60.0f);
+ // [self.displayLink setFrameInterval:frameInterval];
+ // }
+
+ // Setup DisplayLink in main thread
+ [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+ } else {
+ self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
+ }
+}
+
+- (void)drawView {
+ if (!self.isActive) {
+ printf("draw view not active!\n");
+ return;
+ }
+
+ if (self.useCADisplayLink) {
+ // Pause the CADisplayLink to avoid recursion
+ [self.displayLink setPaused:YES];
+
+ // Process all input events
+ while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
+ ;
+
+ // We are good to go, resume the CADisplayLink
+ [self.displayLink setPaused:NO];
+ }
+
+ [self.renderingLayer renderDisplayLayer];
+
+ if (!self.renderer) {
+ return;
+ }
+
+ if ([self.renderer setupView:self]) {
+ return;
+ }
+
+ [self handleMotion];
+ [self.renderer renderOnView:self];
+}
+
+- (BOOL)canRender {
+ if (self.useCADisplayLink) {
+ return self.displayLink != nil;
+ } else {
+ return self.animationTimer != nil;
+ }
+}
+
+- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
+ _renderingInterval = renderingInterval;
+
+ if (self.canRender) {
+ [self stopRendering];
+ [self startRendering];
+ }
+}
+
+- (void)layoutSubviews {
+ if (self.renderingLayer) {
+ self.renderingLayer.frame = self.bounds;
+ [self.renderingLayer layoutDisplayLayer];
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->resize_window(self.bounds.size);
+ }
+ }
+
+ [super layoutSubviews];
+}
+
+// MARK: - Input
+
+// MARK: Keyboard
+
+- (BOOL)canBecomeFirstResponder {
+ return YES;
+}
+
+- (BOOL)becomeFirstResponderWithString:(String)p_existing {
+ keyboard_text = p_existing;
+ return [self becomeFirstResponder];
+}
+
+- (BOOL)resignFirstResponder {
+ keyboard_text = String();
+ return [super resignFirstResponder];
+}
+
+- (void)deleteBackward {
+ if (keyboard_text.length()) {
+ keyboard_text.erase(keyboard_text.length() - 1, 1);
+ }
+ DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true);
+}
+
+- (BOOL)hasText {
+ return keyboard_text.length() > 0;
+}
+
+- (void)insertText:(NSString *)p_text {
+ String character;
+ character.parse_utf8([p_text UTF8String]);
+ keyboard_text = keyboard_text + character;
+ DisplayServerIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
+}
+
+// MARK: Touches
+
+- (void)initTouches {
+ for (int i = 0; i < max_touches; i++) {
+ godot_touches[i] = NULL;
+ }
+}
+
+- (int)getTouchIDForTouch:(UITouch *)p_touch {
+ int first = -1;
+ for (int i = 0; i < max_touches; i++) {
+ if (first == -1 && godot_touches[i] == NULL) {
+ first = i;
+ continue;
+ }
+ if (godot_touches[i] == p_touch) {
+ return i;
+ }
+ }
+
+ if (first != -1) {
+ godot_touches[first] = p_touch;
+ return first;
+ }
+
+ return -1;
+}
+
+- (int)removeTouch:(UITouch *)p_touch {
+ int remaining = 0;
+ for (int i = 0; i < max_touches; i++) {
+ if (godot_touches[i] == NULL) {
+ continue;
+ }
+ if (godot_touches[i] == p_touch) {
+ godot_touches[i] = NULL;
+ } else {
+ ++remaining;
+ }
+ }
+ return remaining;
+}
+
+- (void)clearTouches {
+ for (int i = 0; i < max_touches; i++) {
+ godot_touches[i] = NULL;
+ }
+}
+
+- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touchesSet containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ CGPoint touchPoint = [touch locationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
+ }
+ }
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touches containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ CGPoint touchPoint = [touch locationInView:self];
+ CGPoint prev_point = [touch previousLocationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
+ }
+ }
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touches containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ [self removeTouch:touch];
+ CGPoint touchPoint = [touch locationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
+ }
+ }
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touches containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ DisplayServerIPhone::get_singleton()->touches_cancelled(tid);
+ }
+ }
+ [self clearTouches];
+}
+
+// MARK: Motion
+
+- (void)handleMotion {
+ if (!self.motionManager) {
+ return;
+ }
+
+ // Just using polling approach for now, we can set this up so it sends
+ // data to us in intervals, might be better. See Apple reference pages
+ // for more details:
+ // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
+
+ // Apple splits our accelerometer date into a gravity and user movement
+ // component. We add them back together
+ CMAcceleration gravity = self.motionManager.deviceMotion.gravity;
+ CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration;
+
+ ///@TODO We don't seem to be getting data here, is my device broken or
+ /// is this code incorrect?
+ CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field;
+
+ ///@TODO we can access rotationRate as a CMRotationRate variable
+ ///(processed date) or CMGyroData (raw data), have to see what works
+ /// best
+ CMRotationRate rotation = self.motionManager.deviceMotion.rotationRate;
+
+ // Adjust for screen orientation.
+ // [[UIDevice currentDevice] orientation] changes even if we've fixed
+ // our orientation which is not a good thing when you're trying to get
+ // your user to move the screen in all directions and want consistent
+ // output
+
+ ///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
+ /// is a bit of a hack. Godot obviously knows the orientation so maybe
+ /// we
+ // can use that instead? (note that left and right seem swapped)
+
+ UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
+
+ if (@available(iOS 13, *)) {
+ interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
+#endif
+ }
+
+ switch (interfaceOrientation) {
+ case UIInterfaceOrientationLandscapeLeft: {
+ DisplayServerIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z);
+ } break;
+ case UIInterfaceOrientationLandscapeRight: {
+ DisplayServerIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z);
+ } break;
+ case UIInterfaceOrientationPortraitUpsideDown: {
+ DisplayServerIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z);
+ } break;
+ default: { // assume portrait
+ DisplayServerIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z);
+ } break;
+ }
+}
+
+@end
diff --git a/platform/iphone/godot_view_gesture_recognizer.h b/platform/iphone/godot_view_gesture_recognizer.h
new file mode 100644
index 0000000000..ca3bd808d1
--- /dev/null
+++ b/platform/iphone/godot_view_gesture_recognizer.h
@@ -0,0 +1,44 @@
+/*************************************************************************/
+/* godot_view_gesture_recognizer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+// GLViewGestureRecognizer allows iOS gestures to work currectly by
+// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer.
+// It catches all gestures incoming to UIView and delays them for 150ms
+// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer)
+// If touch cancelation or end message is fired it fires delayed
+// begin touch immediately as well as last touch signal
+
+#import <UIKit/UIKit.h>
+
+@interface GodotViewGestureRecognizer : UIGestureRecognizer
+
+- (instancetype)init;
+
+@end
diff --git a/platform/iphone/godot_view_gesture_recognizer.m b/platform/iphone/godot_view_gesture_recognizer.m
new file mode 100644
index 0000000000..377ccd52a5
--- /dev/null
+++ b/platform/iphone/godot_view_gesture_recognizer.m
@@ -0,0 +1,171 @@
+/*************************************************************************/
+/* godot_view_gesture_recognizer.m */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view_gesture_recognizer.h"
+
+// Using same delay interval that is used for `UIScrollView`
+const NSTimeInterval kGLGestureDelayInterval = 0.150;
+
+// Minimum distance for touches to move to fire
+// a delay timer before scheduled time.
+// Should be the low enough to not cause issues with dragging
+// but big enough to allow click to work.
+const CGFloat kGLGestureMovementDistance = 0.5;
+
+@interface GodotViewGestureRecognizer ()
+
+// Timer used to delay begin touch message.
+// Should work as simple emulation of UIDelayedAction
+@property(strong, nonatomic) NSTimer *delayTimer;
+
+// Delayed touch parameters
+@property(strong, nonatomic) NSSet *delayedTouches;
+@property(strong, nonatomic) UIEvent *delayedEvent;
+
+@end
+
+@implementation GodotViewGestureRecognizer
+
+- (instancetype)init {
+ self = [super init];
+
+ self.cancelsTouchesInView = YES;
+ self.delaysTouchesBegan = YES;
+ self.delaysTouchesEnded = YES;
+
+ return self;
+}
+
+- (void)dealloc {
+ if (self.delayTimer) {
+ [self.delayTimer invalidate];
+ self.delayTimer = nil;
+ }
+
+ if (self.delayedTouches) {
+ self.delayedTouches = nil;
+ }
+
+ if (self.delayedEvent) {
+ self.delayedEvent = nil;
+ }
+
+ [super dealloc];
+}
+
+- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event {
+ [self.delayTimer fire];
+
+ self.delayedTouches = touches;
+ self.delayedEvent = event;
+
+ self.delayTimer = [NSTimer
+ scheduledTimerWithTimeInterval:kGLGestureDelayInterval
+ target:self
+ selector:@selector(fireDelayedTouches:)
+ userInfo:nil
+ repeats:NO];
+}
+
+- (void)fireDelayedTouches:(id)timer {
+ [self.delayTimer invalidate];
+ self.delayTimer = nil;
+
+ if (self.delayedTouches) {
+ [self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent];
+ }
+
+ self.delayedTouches = nil;
+ self.delayedEvent = nil;
+}
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan];
+ [self delayTouches:cleared andEvent:event];
+ [cleared release];
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseMoved];
+
+ if (self.delayTimer) {
+ // We should check if movement was significant enough to fire an event
+ // for dragging to work correctly.
+ for (UITouch *touch in cleared) {
+ CGPoint from = [touch locationInView:self.view];
+ CGPoint to = [touch previousLocationInView:self.view];
+ CGFloat xDistance = from.x - to.x;
+ CGFloat yDistance = from.y - to.y;
+
+ CGFloat distance = sqrt(xDistance * xDistance + yDistance * yDistance);
+
+ // Early exit, since one of touches has moved enough to fire a drag event.
+ if (distance > kGLGestureMovementDistance) {
+ [self.delayTimer fire];
+ [self.view touchesMoved:cleared withEvent:event];
+ [cleared release];
+ return;
+ }
+ }
+
+ [cleared release];
+ return;
+ }
+
+ [self.view touchesMoved:cleared withEvent:event];
+ [cleared release];
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+ [self.delayTimer fire];
+
+ NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
+ [self.view touchesEnded:cleared withEvent:event];
+ [cleared release];
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+ [self.delayTimer fire];
+ [self.view touchesCancelled:touches withEvent:event];
+};
+
+- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave {
+ NSMutableSet *cleared = [touches mutableCopy];
+
+ for (UITouch *touch in touches) {
+ if (touch.phase != phaseToSave) {
+ [cleared removeObject:touch];
+ }
+ }
+
+ return cleared;
+}
+
+@end
diff --git a/platform/iphone/godot_view_renderer.h b/platform/iphone/godot_view_renderer.h
new file mode 100644
index 0000000000..ea8998c808
--- /dev/null
+++ b/platform/iphone/godot_view_renderer.h
@@ -0,0 +1,44 @@
+/*************************************************************************/
+/* godot_view_renderer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+@protocol GodotViewRendererProtocol <NSObject>
+
+@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
+
+- (BOOL)setupView:(UIView *)view;
+- (void)renderOnView:(UIView *)view;
+
+@end
+
+@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol>
+
+@end
diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm
new file mode 100644
index 0000000000..1fc822b457
--- /dev/null
+++ b/platform/iphone/godot_view_renderer.mm
@@ -0,0 +1,146 @@
+/*************************************************************************/
+/* godot_view_renderer.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "godot_view_renderer.h"
+#include "core/os/keyboard.h"
+#include "core/project_settings.h"
+#import "display_server_iphone.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <CoreMotion/CoreMotion.h>
+#import <GameController/GameController.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+@interface GodotViewRenderer ()
+
+@property(assign, nonatomic) BOOL hasFinishedLocaleSetup;
+@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
+@property(assign, nonatomic) BOOL hasStartedMain;
+@property(assign, nonatomic) BOOL hasFinishedSetup;
+
+@end
+
+@implementation GodotViewRenderer
+
+- (BOOL)setupView:(UIView *)view {
+ if (self.hasFinishedSetup) {
+ return NO;
+ }
+
+ if (!self.hasFinishedLocaleSetup) {
+ [self setupLocaleAndUUID];
+ return YES;
+ }
+
+ if (!self.hasFinishedProjectDataSetup) {
+ [self setupProjectData];
+ return YES;
+ }
+
+ if (!self.hasStartedMain) {
+ self.hasStartedMain = YES;
+ OSIPhone::get_singleton()->start();
+ return YES;
+ }
+
+ self.hasFinishedSetup = YES;
+
+ return NO;
+}
+
+- (void)setupLocaleAndUUID {
+ self.hasFinishedLocaleSetup = YES;
+
+ if (!OS::get_singleton()) {
+ exit(0);
+ }
+
+ NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
+ OSIPhone::get_singleton()->set_locale(String::utf8([locale_code UTF8String]));
+
+ NSString *uuid;
+ if ([[UIDevice currentDevice] respondsToSelector:@selector(identifierForVendor)]) {
+ uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
+ } else {
+ // before iOS 6, so just generate an identifier and store it
+ uuid = [[NSUserDefaults standardUserDefaults] objectForKey:@"identiferForVendor"];
+ if (!uuid) {
+ CFUUIDRef cfuuid = CFUUIDCreate(NULL);
+ uuid = [(NSString *)CFUUIDCreateString(NULL, cfuuid) autorelease];
+ CFRelease(cfuuid);
+ [[NSUserDefaults standardUserDefaults] setObject:uuid forKey:@"identifierForVendor"];
+ }
+ }
+
+ OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
+}
+
+- (void)setupProjectData {
+ self.hasFinishedProjectDataSetup = YES;
+
+ Main::setup2();
+
+ // this might be necessary before here
+ NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
+ for (NSString *key in dict) {
+ NSObject *value = [dict objectForKey:key];
+ String ukey = String::utf8([key UTF8String]);
+
+ // we need a NSObject to Variant conversor
+
+ if ([value isKindOfClass:[NSString class]]) {
+ NSString *str = (NSString *)value;
+ String uval = String::utf8([str UTF8String]);
+
+ ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
+
+ } else if ([value isKindOfClass:[NSNumber class]]) {
+ NSNumber *n = (NSNumber *)value;
+ double dval = [n doubleValue];
+
+ ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
+ };
+ // do stuff
+ }
+}
+
+- (void)renderOnView:(UIView *)view {
+ if (!OSIPhone::get_singleton()) {
+ return;
+ }
+
+ OSIPhone::get_singleton()->iterate();
+}
+
+@end
diff --git a/platform/iphone/icloud.h b/platform/iphone/icloud.h
index b11e22fec6..381edfa718 100644
--- a/platform/iphone/icloud.h
+++ b/platform/iphone/icloud.h
@@ -44,9 +44,9 @@ class ICloud : public Object {
List<Variant> pending_events;
public:
- Error remove_key(Variant p_param);
- Variant set_key_values(Variant p_param);
- Variant get_key_value(Variant p_param);
+ Error remove_key(String p_param);
+ Array set_key_values(Dictionary p_params);
+ Variant get_key_value(String p_param);
Error synchronize_key_values();
Variant get_all_key_values();
diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm
index c768274b1f..d3086e6cea 100644
--- a/platform/iphone/icloud.mm
+++ b/platform/iphone/icloud.mm
@@ -48,8 +48,10 @@ ICloud *ICloud::instance = NULL;
void ICloud::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_key"), &ICloud::remove_key);
+
ClassDB::bind_method(D_METHOD("set_key_values"), &ICloud::set_key_values);
ClassDB::bind_method(D_METHOD("get_key_value"), &ICloud::get_key_value);
+
ClassDB::bind_method(D_METHOD("synchronize_key_values"), &ICloud::synchronize_key_values);
ClassDB::bind_method(D_METHOD("get_all_key_values"), &ICloud::get_all_key_values);
@@ -91,7 +93,7 @@ Variant nsobject_to_variant(NSObject *object) {
} else if ([object isKindOfClass:[NSArray class]]) {
Array result;
NSArray *array = (NSArray *)object;
- for (unsigned int i = 0; i < [array count]; ++i) {
+ for (NSUInteger i = 0; i < [array count]; ++i) {
NSObject *value = [array objectAtIndex:i];
result.push_back(nsobject_to_variant(value));
}
@@ -149,7 +151,7 @@ Variant nsobject_to_variant(NSObject *object) {
NSObject *variant_to_nsobject(Variant v) {
if (v.get_type() == Variant::STRING) {
return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease];
- } else if (v.get_type() == Variant::REAL) {
+ } else if (v.get_type() == Variant::FLOAT) {
return [NSNumber numberWithDouble:(double)v];
} else if (v.get_type() == Variant::INT) {
return [NSNumber numberWithLongLong:(long)(int)v];
@@ -159,7 +161,7 @@ NSObject *variant_to_nsobject(Variant v) {
NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease];
Dictionary dic = v;
Array keys = dic.keys();
- for (unsigned int i = 0; i < keys.size(); ++i) {
+ for (int i = 0; i < keys.size(); ++i) {
NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease];
NSObject *value = variant_to_nsobject(dic[keys[i]]);
@@ -173,7 +175,7 @@ NSObject *variant_to_nsobject(Variant v) {
} else if (v.get_type() == Variant::ARRAY) {
NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
Array arr = v;
- for (unsigned int i = 0; i < arr.size(); ++i) {
+ for (int i = 0; i < arr.size(); ++i) {
NSObject *value = variant_to_nsobject(arr[i]);
if (value == NULL) {
//trying to add something unsupported to the array. cancel the whole array
@@ -192,9 +194,8 @@ NSObject *variant_to_nsobject(Variant v) {
return NULL;
}
-Error ICloud::remove_key(Variant p_param) {
- String param = p_param;
- NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
+Error ICloud::remove_key(String p_param) {
+ NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
@@ -207,15 +208,14 @@ Error ICloud::remove_key(Variant p_param) {
}
//return an array of the keys that could not be set
-Variant ICloud::set_key_values(Variant p_params) {
- Dictionary params = p_params;
- Array keys = params.keys();
+Array ICloud::set_key_values(Dictionary p_params) {
+ Array keys = p_params.keys();
Array error_keys;
- for (unsigned int i = 0; i < keys.size(); ++i) {
+ for (int i = 0; i < keys.size(); ++i) {
String variant_key = keys[i];
- Variant variant_value = params[variant_key];
+ Variant variant_value = p_params[variant_key];
NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease];
if (key == NULL) {
@@ -237,10 +237,8 @@ Variant ICloud::set_key_values(Variant p_params) {
return error_keys;
}
-Variant ICloud::get_key_value(Variant p_param) {
- String param = p_param;
-
- NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
+Variant ICloud::get_key_value(String p_param) {
+ NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
if (![[store dictionaryRepresentation] objectForKey:key]) {
diff --git a/platform/iphone/in_app_store.h b/platform/iphone/in_app_store.h
index 44e65e77ed..beb58af2c7 100644
--- a/platform/iphone/in_app_store.h
+++ b/platform/iphone/in_app_store.h
@@ -44,9 +44,9 @@ class InAppStore : public Object {
List<Variant> pending_events;
public:
- Error request_product_info(Variant p_params);
+ Error request_product_info(Dictionary p_params);
Error restore_purchases();
- Error purchase(Variant p_params);
+ Error purchase(Dictionary p_params);
int get_pending_event_count();
Variant pop_pending_event();
diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm
index 548dcc549d..dfec5d7634 100644
--- a/platform/iphone/in_app_store.mm
+++ b/platform/iphone/in_app_store.mm
@@ -39,8 +39,10 @@ extern "C" {
bool auto_finish_transactions = true;
NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary];
+static NSArray *latestProducts;
@interface SKProduct (LocalizedPrice)
+
@property(nonatomic, readonly) NSString *localizedPrice;
@end
@@ -82,6 +84,8 @@ void InAppStore::_bind_methods() {
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *products = response.products;
+ latestProducts = products;
+
Dictionary ret;
ret["type"] = "product_info";
ret["result"] = "ok";
@@ -126,11 +130,10 @@ void InAppStore::_bind_methods() {
@end
-Error InAppStore::request_product_info(Variant p_params) {
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("product_ids"), ERR_INVALID_PARAMETER);
+Error InAppStore::request_product_info(Dictionary p_params) {
+ ERR_FAIL_COND_V(!p_params.has("product_ids"), ERR_INVALID_PARAMETER);
- PackedStringArray pids = params["product_ids"];
+ PackedStringArray pids = p_params["product_ids"];
printf("************ request product info! %i\n", pids.size());
NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease];
@@ -198,11 +201,11 @@ Error InAppStore::restore_purchases() {
// which is still available in iOS 7.
// Use SKPaymentTransaction's transactionReceipt.
- receipt = transaction.transactionReceipt;
+ receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
}
} else {
- receipt = transaction.transactionReceipt;
+ receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
}
NSString *receipt_to_send = nil;
@@ -254,17 +257,32 @@ Error InAppStore::restore_purchases() {
@end
-Error InAppStore::purchase(Variant p_params) {
+Error InAppStore::purchase(Dictionary p_params) {
ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);
if (![SKPaymentQueue canMakePayments])
return ERR_UNAVAILABLE;
printf("purchasing!\n");
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!p_params.has("product_id"), ERR_INVALID_PARAMETER);
+
+ NSString *pid = [[[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()] autorelease];
+
+ SKProduct *product = nil;
+
+ if (latestProducts) {
+ for (SKProduct *storedProduct in latestProducts) {
+ if ([storedProduct.productIdentifier isEqualToString:pid]) {
+ product = storedProduct;
+ break;
+ }
+ }
+ }
+
+ if (!product) {
+ return ERR_INVALID_PARAMETER;
+ }
- NSString *pid = [[[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()] autorelease];
- SKPayment *payment = [SKPayment paymentWithProductIdentifier:pid];
+ SKPayment *payment = [SKPayment paymentWithProduct:product];
SKPaymentQueue *defq = [SKPaymentQueue defaultQueue];
[defq addPayment:payment];
printf("purchase sent!\n");
diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm
index 5923f558a5..ad26d0ada3 100644
--- a/platform/iphone/ios.mm
+++ b/platform/iphone/ios.mm
@@ -29,17 +29,27 @@
/*************************************************************************/
#include "ios.h"
-#include <sys/sysctl.h>
-
+#import "app_delegate.h"
#import <UIKit/UIKit.h>
+#include <sys/sysctl.h>
void iOS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);
};
void iOS::alert(const char *p_alert, const char *p_title) {
- UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:[NSString stringWithUTF8String:p_title] message:[NSString stringWithUTF8String:p_alert] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] autorelease];
- [alert show];
+ NSString *title = [NSString stringWithUTF8String:p_title];
+ NSString *message = [NSString stringWithUTF8String:p_alert];
+
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
+ UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleCancel
+ handler:^(id){
+ }];
+
+ [alert addAction:button];
+
+ [AppDelegate.viewController presentViewController:alert animated:YES completion:nil];
}
String iOS::get_model() const {
diff --git a/platform/iphone/joypad_iphone.h b/platform/iphone/joypad_iphone.h
new file mode 100644
index 0000000000..85e26e1dc8
--- /dev/null
+++ b/platform/iphone/joypad_iphone.h
@@ -0,0 +1,50 @@
+/*************************************************************************/
+/* joypad_iphone.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import <GameController/GameController.h>
+
+@interface JoypadIPhoneObserver : NSObject
+
+- (void)startObserving;
+- (void)startProcessing;
+- (void)finishObserving;
+
+@end
+
+class JoypadIPhone {
+private:
+ JoypadIPhoneObserver *observer;
+
+public:
+ JoypadIPhone();
+ ~JoypadIPhone();
+
+ void start_processing();
+};
diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm
new file mode 100644
index 0000000000..6088f1c25c
--- /dev/null
+++ b/platform/iphone/joypad_iphone.mm
@@ -0,0 +1,380 @@
+/*************************************************************************/
+/* joypad_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#import "joypad_iphone.h"
+#include "core/project_settings.h"
+#include "drivers/coreaudio/audio_driver_coreaudio.h"
+#include "main/main.h"
+
+#import "godot_view.h"
+
+#include "os_iphone.h"
+
+JoypadIPhone::JoypadIPhone() {
+ observer = [[JoypadIPhoneObserver alloc] init];
+ [observer startObserving];
+}
+
+JoypadIPhone::~JoypadIPhone() {
+ if (observer) {
+ [observer finishObserving];
+ observer = nil;
+ }
+}
+
+void JoypadIPhone::start_processing() {
+ if (observer) {
+ [observer startProcessing];
+ }
+}
+
+@interface JoypadIPhoneObserver ()
+
+@property(assign, nonatomic) BOOL isObserving;
+@property(assign, nonatomic) BOOL isProcessing;
+@property(strong, nonatomic) NSMutableDictionary *connectedJoypads;
+@property(strong, nonatomic) NSMutableArray *joypadsQueue;
+
+@end
+
+@implementation JoypadIPhoneObserver
+
+- (instancetype)init {
+ self = [super init];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.isObserving = NO;
+ self.isProcessing = NO;
+}
+
+- (void)startProcessing {
+ self.isProcessing = YES;
+
+ for (GCController *controller in self.joypadsQueue) {
+ [self addiOSJoypad:controller];
+ }
+
+ [self.joypadsQueue removeAllObjects];
+}
+
+- (void)startObserving {
+ if (self.isObserving) {
+ return;
+ }
+
+ self.isObserving = YES;
+
+ self.connectedJoypads = [NSMutableDictionary dictionary];
+ self.joypadsQueue = [NSMutableArray array];
+
+ // get told when controllers connect, this will be called right away for
+ // already connected controllers
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(controllerWasConnected:)
+ name:GCControllerDidConnectNotification
+ object:nil];
+
+ // get told when controllers disconnect
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(controllerWasDisconnected:)
+ name:GCControllerDidDisconnectNotification
+ object:nil];
+}
+
+- (void)finishObserving {
+ if (self.isObserving) {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ }
+
+ self.isObserving = NO;
+ self.isProcessing = NO;
+
+ self.connectedJoypads = nil;
+ self.joypadsQueue = nil;
+}
+
+- (void)dealloc {
+ [self finishObserving];
+
+ [super dealloc];
+}
+
+- (int)getJoyIdForController:(GCController *)controller {
+ NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+
+ for (NSNumber *key in keys) {
+ int joy_id = [key intValue];
+ return joy_id;
+ };
+
+ return -1;
+};
+
+- (void)addiOSJoypad:(GCController *)controller {
+ // get a new id for our controller
+ int joy_id = Input::get_singleton()->get_unused_joy_id();
+
+ if (joy_id == -1) {
+ printf("Couldn't retrieve new joy id\n");
+ return;
+ }
+
+ // assign our player index
+ if (controller.playerIndex == GCControllerPlayerIndexUnset) {
+ controller.playerIndex = [self getFreePlayerIndex];
+ };
+
+ // tell Godot about our new controller
+ Input::get_singleton()->joy_connection_changed(joy_id, true, [controller.vendorName UTF8String]);
+
+ // add it to our dictionary, this will retain our controllers
+ [self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]];
+
+ // set our input handler
+ [self setControllerInputHandler:controller];
+}
+
+- (void)controllerWasConnected:(NSNotification *)notification {
+ // get our controller
+ GCController *controller = (GCController *)notification.object;
+
+ if (!controller) {
+ printf("Couldn't retrieve new controller\n");
+ return;
+ }
+
+ if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) {
+ printf("Controller is already registered\n");
+ } else if (!self.isProcessing) {
+ [self.joypadsQueue addObject:controller];
+ } else {
+ [self addiOSJoypad:controller];
+ }
+}
+
+- (void)controllerWasDisconnected:(NSNotification *)notification {
+ // find our joystick, there should be only one in our dictionary
+ GCController *controller = (GCController *)notification.object;
+
+ if (!controller) {
+ return;
+ }
+
+ NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+ for (NSNumber *key in keys) {
+ // tell Godot this joystick is no longer there
+ int joy_id = [key intValue];
+ Input::get_singleton()->joy_connection_changed(joy_id, false, "");
+
+ // and remove it from our dictionary
+ [self.connectedJoypads removeObjectForKey:key];
+ };
+};
+
+- (GCControllerPlayerIndex)getFreePlayerIndex {
+ bool have_player_1 = false;
+ bool have_player_2 = false;
+ bool have_player_3 = false;
+ bool have_player_4 = false;
+
+ if (self.connectedJoypads == nil) {
+ NSArray *keys = [self.connectedJoypads allKeys];
+ for (NSNumber *key in keys) {
+ GCController *controller = [self.connectedJoypads objectForKey:key];
+ if (controller.playerIndex == GCControllerPlayerIndex1) {
+ have_player_1 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex2) {
+ have_player_2 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex3) {
+ have_player_3 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex4) {
+ have_player_4 = true;
+ };
+ };
+ };
+
+ if (!have_player_1) {
+ return GCControllerPlayerIndex1;
+ } else if (!have_player_2) {
+ return GCControllerPlayerIndex2;
+ } else if (!have_player_3) {
+ return GCControllerPlayerIndex3;
+ } else if (!have_player_4) {
+ return GCControllerPlayerIndex4;
+ } else {
+ return GCControllerPlayerIndexUnset;
+ };
+}
+
+- (void)setControllerInputHandler:(GCController *)controller {
+ // Hook in the callback handler for the correct gamepad profile.
+ // This is a bit of a weird design choice on Apples part.
+ // You need to select the most capable gamepad profile for the
+ // gamepad attached.
+ if (controller.extendedGamepad != nil) {
+ // The extended gamepad profile has all the input you could possibly find on
+ // a gamepad but will only be active if your gamepad actually has all of
+ // these...
+ controller.extendedGamepad.valueChangedHandler = ^(
+ GCExtendedGamepad *gamepad, GCControllerElement *element) {
+ int joy_id = [self getJoyIdForController:controller];
+
+ if (element == gamepad.buttonA) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+ gamepad.buttonA.isPressed);
+ } else if (element == gamepad.buttonB) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
+ gamepad.buttonB.isPressed);
+ } else if (element == gamepad.buttonX) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+ gamepad.buttonX.isPressed);
+ } else if (element == gamepad.buttonY) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
+ gamepad.buttonY.isPressed);
+ } else if (element == gamepad.leftShoulder) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
+ gamepad.leftShoulder.isPressed);
+ } else if (element == gamepad.rightShoulder) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
+ gamepad.rightShoulder.isPressed);
+ } else if (element == gamepad.dpad) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+ gamepad.dpad.up.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+ gamepad.dpad.down.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+ gamepad.dpad.left.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+ gamepad.dpad.right.isPressed);
+ };
+
+ Input::JoyAxis jx;
+ jx.min = -1;
+ if (element == gamepad.leftThumbstick) {
+ jx.value = gamepad.leftThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx);
+ jx.value = -gamepad.leftThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx);
+ } else if (element == gamepad.rightThumbstick) {
+ jx.value = gamepad.rightThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx);
+ jx.value = -gamepad.rightThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx);
+ } else if (element == gamepad.leftTrigger) {
+ jx.value = gamepad.leftTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx);
+ } else if (element == gamepad.rightTrigger) {
+ jx.value = gamepad.rightTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx);
+ };
+ };
+ }
+ // else if (controller.gamepad != nil) {
+ // // gamepad is the standard profile with 4 buttons, shoulder buttons and a
+ // // D-pad
+ // controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
+ // GCControllerElement *element) {
+ // int joy_id = [self getJoyIdForController:controller];
+ //
+ // if (element == gamepad.buttonA) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+ // gamepad.buttonA.isPressed);
+ // } else if (element == gamepad.buttonB) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
+ // gamepad.buttonB.isPressed);
+ // } else if (element == gamepad.buttonX) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+ // gamepad.buttonX.isPressed);
+ // } else if (element == gamepad.buttonY) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
+ // gamepad.buttonY.isPressed);
+ // } else if (element == gamepad.leftShoulder) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
+ // gamepad.leftShoulder.isPressed);
+ // } else if (element == gamepad.rightShoulder) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
+ // gamepad.rightShoulder.isPressed);
+ // } else if (element == gamepad.dpad) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+ // gamepad.dpad.up.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+ // gamepad.dpad.down.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+ // gamepad.dpad.left.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+ // gamepad.dpad.right.isPressed);
+ // };
+ // };
+ //#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
+ // // while we are setting that as the minimum, seems our
+ // // build environment doesn't like it
+ // } else if (controller.microGamepad != nil) {
+ // // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
+ // controller.microGamepad.valueChangedHandler =
+ // ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
+ // int joy_id = [self getJoyIdForController:controller];
+ //
+ // if (element == gamepad.buttonA) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+ // gamepad.buttonA.isPressed);
+ // } else if (element == gamepad.buttonX) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+ // gamepad.buttonX.isPressed);
+ // } else if (element == gamepad.dpad) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+ // gamepad.dpad.up.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+ // gamepad.dpad.down.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+ // gamepad.dpad.left.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+ // gamepad.dpad.right.isPressed);
+ // };
+ // };
+ //#endif
+ // };
+
+ ///@TODO need to add support for controller.motion which gives us access to
+ /// the orientation of the device (if supported)
+
+ ///@TODO need to add support for controllerPausedHandler which should be a
+ /// toggle
+};
+
+@end
diff --git a/platform/iphone/main.m b/platform/iphone/main.m
index 164db2a74b..c292f02822 100644
--- a/platform/iphone/main.m
+++ b/platform/iphone/main.m
@@ -32,20 +32,25 @@
#import <UIKit/UIKit.h>
#include <stdio.h>
+#include <vulkan/vulkan.h>
int gargc;
char **gargv;
int main(int argc, char *argv[]) {
+#if defined(VULKAN_ENABLED)
+ //MoltenVK - enable full component swizzling support
+ setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
+#endif
+
printf("*********** main.m\n");
gargc = argc;
gargv = argv;
- NSAutoreleasePool *pool = [NSAutoreleasePool new];
- AppDelegate *app = [AppDelegate alloc];
printf("running app main\n");
- UIApplicationMain(argc, argv, nil, @"AppDelegate");
- printf("main done, pool release\n");
- [pool release];
+ @autoreleasepool {
+ UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+ printf("main done\n");
return 0;
}
diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp
deleted file mode 100644
index 41dd623e69..0000000000
--- a/platform/iphone/os_iphone.cpp
+++ /dev/null
@@ -1,632 +0,0 @@
-/*************************************************************************/
-/* os_iphone.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifdef IPHONE_ENABLED
-
-#include "os_iphone.h"
-
-#if defined(OPENGL_ENABLED)
-#include "drivers/gles2/rasterizer_gles2.h"
-#endif
-
-#if defined(VULKAN_ENABLED)
-#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
-// #import <QuartzCore/CAMetalLayer.h>
-#include <vulkan/vulkan_metal.h>
-#endif
-
-#include "servers/rendering/rendering_server_raster.h"
-#include "servers/rendering/rendering_server_wrap_mt.h"
-
-#include "main/main.h"
-
-#include "core/io/file_access_pack.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/project_settings.h"
-#include "drivers/unix/syslog_logger.h"
-
-#include "semaphore_iphone.h"
-
-#include <dlfcn.h>
-
-int OSIPhone::get_video_driver_count() const {
- return 2;
-};
-
-const char *OSIPhone::get_video_driver_name(int p_driver) const {
- switch (p_driver) {
- case VIDEO_DRIVER_GLES2:
- return "GLES2";
- }
- ERR_FAIL_V_MSG(nullptr, "Invalid video driver index: " + itos(p_driver) + ".");
-};
-
-OSIPhone *OSIPhone::get_singleton() {
- return (OSIPhone *)OS::get_singleton();
-};
-
-extern int gl_view_base_fb; // from gl_view.mm
-
-void OSIPhone::set_data_dir(String p_dir) {
- DirAccess *da = DirAccess::open(p_dir);
-
- data_dir = da->get_current_dir();
- printf("setting data dir to %ls from %ls\n", data_dir.c_str(), p_dir.c_str());
- memdelete(da);
-};
-
-void OSIPhone::set_unique_id(String p_id) {
- unique_id = p_id;
-};
-
-String OSIPhone::get_unique_id() const {
- return unique_id;
-};
-
-void OSIPhone::initialize_core() {
- OS_Unix::initialize_core();
-
- set_data_dir(data_dir);
-};
-
-int OSIPhone::get_current_video_driver() const {
- return video_driver_index;
-}
-
-Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
- video_driver_index = p_video_driver;
-
-#if defined(OPENGL_ENABLED)
- bool gl_initialization_error = false;
-
- // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
-
- if (RasterizerGLES2::is_viable() == OK) {
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- } else {
- gl_initialization_error = true;
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.",
- "Unable to initialize video driver");
- return ERR_UNAVAILABLE;
- }
-#endif
-
-#if defined(VULKAN_ENABLED)
- RasterizerRD::make_current();
-#endif
-
- rendering_server = memnew(RenderingServerRaster);
- // FIXME: Reimplement threaded rendering
- if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- rendering_server = memnew(RenderingServerWrapMT(rendering_server, false));
- }
- rendering_server->init();
- //rendering_server->cursor_set_visible(false, 0);
-
-#if defined(OPENGL_ENABLED)
- // reset this to what it should be, it will have been set to 0 after rendering_server->init() is called
- RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
-#endif
-
- AudioDriverManager::initialize(p_audio_driver);
-
- input = memnew(InputDefault);
-
-#ifdef GAME_CENTER_ENABLED
- game_center = memnew(GameCenter);
- Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
- game_center->connect();
-#endif
-
-#ifdef STOREKIT_ENABLED
- store_kit = memnew(InAppStore);
- Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
-#endif
-
-#ifdef ICLOUD_ENABLED
- icloud = memnew(ICloud);
- Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
- //icloud->connect();
-#endif
- ios = memnew(iOS);
- Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
-
- return OK;
-};
-
-MainLoop *OSIPhone::get_main_loop() const {
- return main_loop;
-};
-
-void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
- main_loop = p_main_loop;
-
- if (main_loop) {
- input->set_main_loop(p_main_loop);
- main_loop->init();
- }
-};
-
-bool OSIPhone::iterate() {
- if (!main_loop)
- return true;
-
- if (main_loop) {
- for (int i = 0; i < event_count; i++) {
- input->parse_input_event(event_queue[i]);
- };
- };
- event_count = 0;
-
- return Main::iteration();
-};
-
-void OSIPhone::key(uint32_t p_key, bool p_pressed) {
- Ref<InputEventKey> ev;
- ev.instance();
- ev->set_echo(false);
- ev->set_pressed(p_pressed);
- ev->set_keycode(p_key);
- ev->set_physical_keycode(p_key);
- ev->set_unicode(p_key);
- queue_event(ev);
-};
-
-void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
- if (!GLOBAL_DEF("debug/disable_touch", false)) {
- Ref<InputEventScreenTouch> ev;
- ev.instance();
-
- ev->set_index(p_idx);
- ev->set_pressed(p_pressed);
- ev->set_position(Vector2(p_x, p_y));
- queue_event(ev);
- };
-
- touch_list.pressed[p_idx] = p_pressed;
-};
-
-void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
- if (!GLOBAL_DEF("debug/disable_touch", false)) {
- Ref<InputEventScreenDrag> ev;
- ev.instance();
- ev->set_index(p_idx);
- ev->set_position(Vector2(p_x, p_y));
- ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
- queue_event(ev);
- };
-};
-
-void OSIPhone::queue_event(const Ref<InputEvent> &p_event) {
- ERR_FAIL_INDEX(event_count, MAX_EVENTS);
-
- event_queue[event_count++] = p_event;
-};
-
-void OSIPhone::touches_cancelled() {
- for (int i = 0; i < MAX_MOUSE_COUNT; i++) {
- if (touch_list.pressed[i]) {
- // send a mouse_up outside the screen
- touch_press(i, -1, -1, false, false);
- };
- };
-};
-
-static const float ACCEL_RANGE = 1;
-
-void OSIPhone::update_gravity(float p_x, float p_y, float p_z) {
- input->set_gravity(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_accelerometer(float p_x, float p_y, float p_z) {
- // Found out the Z should not be negated! Pass as is!
- input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE));
-
- /*
- if (p_x != last_accel.x) {
- //printf("updating accel x %f\n", p_x);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_0;
- ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE);
- last_accel.x = p_x;
- queue_event(ev);
- };
- if (p_y != last_accel.y) {
- //printf("updating accel y %f\n", p_y);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_1;
- ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE);
- last_accel.y = p_y;
- queue_event(ev);
- };
- if (p_z != last_accel.z) {
- //printf("updating accel z %f\n", p_z);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_2;
- ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE);
- last_accel.z = p_z;
- queue_event(ev);
- };
- */
-};
-
-void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
- input->set_magnetometer(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
- input->set_gyroscope(Vector3(p_x, p_y, p_z));
-};
-
-int OSIPhone::get_unused_joy_id() {
- return input->get_unused_joy_id();
-};
-
-void OSIPhone::joy_connection_changed(int p_idx, bool p_connected, String p_name) {
- input->joy_connection_changed(p_idx, p_connected, p_name);
-};
-
-void OSIPhone::joy_button(int p_device, int p_button, bool p_pressed) {
- input->joy_button(p_device, p_button, p_pressed);
-};
-
-void OSIPhone::joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value) {
- input->joy_axis(p_device, p_axis, p_value);
-};
-
-void OSIPhone::delete_main_loop() {
- if (main_loop) {
- main_loop->finish();
- memdelete(main_loop);
- };
-
- main_loop = nullptr;
-};
-
-void OSIPhone::finalize() {
- delete_main_loop();
-
- memdelete(input);
- memdelete(ios);
-
-#ifdef GAME_CENTER_ENABLED
- memdelete(game_center);
-#endif
-
-#ifdef STOREKIT_ENABLED
- memdelete(store_kit);
-#endif
-
-#ifdef ICLOUD_ENABLED
- memdelete(icloud);
-#endif
-
- rendering_server->finish();
- memdelete(rendering_server);
- // memdelete(rasterizer);
-
- // Free unhandled events before close
- for (int i = 0; i < MAX_EVENTS; i++) {
- event_queue[i].unref();
- };
- event_count = 0;
-};
-
-void OSIPhone::set_mouse_show(bool p_show) {}
-void OSIPhone::set_mouse_grab(bool p_grab) {}
-
-bool OSIPhone::is_mouse_grab_enabled() const {
- return true;
-};
-
-Point2 OSIPhone::get_mouse_position() const {
- return Point2();
-};
-
-int OSIPhone::get_mouse_button_state() const {
- return 0;
-};
-
-void OSIPhone::set_window_title(const String &p_title) {}
-
-void OSIPhone::alert(const String &p_alert, const String &p_title) {
- const CharString utf8_alert = p_alert.utf8();
- const CharString utf8_title = p_title.utf8();
- iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
-}
-
-Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
- if (p_path.length() == 0) {
- p_library_handle = RTLD_SELF;
- return OK;
- }
- return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
-}
-
-Error OSIPhone::close_dynamic_library(void *p_library_handle) {
- if (p_library_handle == RTLD_SELF) {
- return OK;
- }
- return OS_Unix::close_dynamic_library(p_library_handle);
-}
-
-HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
-void register_dynamic_symbol(char *name, void *address) {
- OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
-}
-
-Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
- if (p_library_handle == RTLD_SELF) {
- void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
- if (ptr) {
- p_symbol_handle = *ptr;
- return OK;
- }
- }
- return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
-}
-
-void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
- video_mode = p_video_mode;
-};
-
-OS::VideoMode OSIPhone::get_video_mode(int p_screen) const {
- return video_mode;
-};
-
-void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
- p_list->push_back(video_mode);
-};
-
-bool OSIPhone::can_draw() const {
- if (native_video_is_playing())
- return false;
- return true;
-};
-
-int OSIPhone::set_base_framebuffer(int p_fb) {
-#if defined(OPENGL_ENABLED)
- // gl_view_base_fb has not been updated yet
- RasterizerStorageGLES2::system_fbo = p_fb;
-#endif
-
- return 0;
-};
-
-bool OSIPhone::has_virtual_keyboard() const {
- return true;
-};
-
-extern void _show_keyboard(String p_existing);
-extern void _hide_keyboard();
-extern Error _shell_open(String p_uri);
-extern void _set_keep_screen_on(bool p_enabled);
-extern void _vibrate();
-
-void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
- _show_keyboard(p_existing_text);
-};
-
-void OSIPhone::hide_virtual_keyboard() {
- _hide_keyboard();
-};
-
-void OSIPhone::set_virtual_keyboard_height(int p_height) {
- virtual_keyboard_height = p_height;
-}
-
-int OSIPhone::get_virtual_keyboard_height() const {
- return virtual_keyboard_height;
-}
-
-Error OSIPhone::shell_open(String p_uri) {
- return _shell_open(p_uri);
-};
-
-void OSIPhone::set_keep_screen_on(bool p_enabled) {
- OS::set_keep_screen_on(p_enabled);
- _set_keep_screen_on(p_enabled);
-};
-
-String OSIPhone::get_user_data_dir() const {
- return data_dir;
-};
-
-String OSIPhone::get_name() const {
- return "iOS";
-};
-
-String OSIPhone::get_model_name() const {
- String model = ios->get_model();
- if (model != "")
- return model;
-
- return OS_Unix::get_model_name();
-}
-
-Size2 OSIPhone::get_window_size() const {
- return Vector2(video_mode.width, video_mode.height);
-}
-
-extern Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height);
-
-Rect2 OSIPhone::get_window_safe_area() const {
- return _get_ios_window_safe_area(video_mode.width, video_mode.height);
-}
-
-bool OSIPhone::has_touchscreen_ui_hint() const {
- return true;
-}
-
-void OSIPhone::set_locale(String p_locale) {
- locale_code = p_locale;
-}
-
-String OSIPhone::get_locale() const {
- return locale_code;
-}
-
-extern bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
-extern bool _is_video_playing();
-extern void _pause_video();
-extern void _unpause_video();
-extern void _stop_video();
-extern void _focus_out_video();
-
-Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
- FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
- bool exists = f && f->is_open();
-
- String tempFile = get_user_data_dir();
- if (!exists)
- return FAILED;
-
- if (p_path.begins_with("res://")) {
- if (PackedData::get_singleton()->has_path(p_path)) {
- print("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str());
- return ERR_INVALID_PARAMETER;
- } else {
- p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
- }
- } else if (p_path.begins_with("user://"))
- p_path = p_path.replace("user:/", get_user_data_dir());
-
- memdelete(f);
-
- print("Playing video: %S\n", p_path.c_str());
- if (_play_video(p_path, p_volume, p_audio_track, p_subtitle_track))
- return OK;
- return FAILED;
-}
-
-bool OSIPhone::native_video_is_playing() const {
- return _is_video_playing();
-}
-
-void OSIPhone::native_video_pause() {
- if (native_video_is_playing())
- _pause_video();
-}
-
-void OSIPhone::native_video_unpause() {
- _unpause_video();
-};
-
-void OSIPhone::native_video_focus_out() {
- _focus_out_video();
-};
-
-void OSIPhone::native_video_stop() {
- if (native_video_is_playing())
- _stop_video();
-}
-
-void OSIPhone::vibrate_handheld(int p_duration_ms) {
- // iOS does not support duration for vibration
- _vibrate();
-}
-
-bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
- return p_feature == "mobile";
-}
-
-// Initialization order between compilation units is not guaranteed,
-// so we use this as a hack to ensure certain code is called before
-// everything else, but after all units are initialized.
-typedef void (*init_callback)();
-static init_callback *ios_init_callbacks = nullptr;
-static int ios_init_callbacks_count = 0;
-static int ios_init_callbacks_capacity = 0;
-
-void add_ios_init_callback(init_callback cb) {
- if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
- void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
- if (new_ptr) {
- ios_init_callbacks = (init_callback *)(new_ptr);
- ios_init_callbacks_capacity += 32;
- }
- }
- if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
- ios_init_callbacks[ios_init_callbacks_count] = cb;
- ++ios_init_callbacks_count;
- }
-}
-
-OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
- for (int i = 0; i < ios_init_callbacks_count; ++i) {
- ios_init_callbacks[i]();
- }
- free(ios_init_callbacks);
- ios_init_callbacks = nullptr;
- ios_init_callbacks_count = 0;
- ios_init_callbacks_capacity = 0;
-
- main_loop = nullptr;
- rendering_server = nullptr;
-
- VideoMode vm;
- vm.fullscreen = true;
- vm.width = width;
- vm.height = height;
- vm.resizable = false;
- set_video_mode(vm);
- event_count = 0;
- virtual_keyboard_height = 0;
-
- // can't call set_data_dir from here, since it requires DirAccess
- // which is initialized in initialize_core
- data_dir = p_data_dir;
-
- Vector<Logger *> loggers;
- loggers.push_back(memnew(SyslogLogger));
-#ifdef DEBUG_ENABLED
- // it seems iOS app's stdout/stderr is only obtainable if you launch it from Xcode
- loggers.push_back(memnew(StdLogger));
-#endif
- _set_logger(memnew(CompositeLogger(loggers)));
-
- AudioDriverManager::add_driver(&audio_driver);
-};
-
-OSIPhone::~OSIPhone() {
-}
-
-#endif
diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h
index 955eb15d57..f3bde46717 100644
--- a/platform/iphone/os_iphone.h
+++ b/platform/iphone/os_iphone.h
@@ -33,16 +33,15 @@
#ifndef OS_IPHONE_H
#define OS_IPHONE_H
-#include "core/input/input.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
#include "game_center.h"
#include "icloud.h"
#include "in_app_store.h"
#include "ios.h"
+#include "joypad_iphone.h"
#include "servers/audio_server.h"
#include "servers/rendering/rasterizer.h"
-#include "servers/rendering_server.h"
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
@@ -51,16 +50,9 @@
class OSIPhone : public OS_Unix {
private:
- enum {
- MAX_MOUSE_COUNT = 8,
- MAX_EVENTS = 64,
- };
-
static HashMap<String, void *> dynamic_symbol_lookup_table;
friend void register_dynamic_symbol(char *name, void *address);
- RenderingServer *rendering_server;
-
AudioDriverCoreAudio audio_driver;
#ifdef GAME_CENTER_ENABLED
@@ -74,139 +66,72 @@ private:
#endif
iOS *ios;
- MainLoop *main_loop;
-
-#if defined(VULKAN_ENABLED)
- VulkanContextIPhone *context_vulkan;
- RenderingDeviceVulkan *rendering_device_vulkan;
-#endif
- VideoMode video_mode;
-
- virtual int get_video_driver_count() const;
- virtual const char *get_video_driver_name(int p_driver) const;
+ JoypadIPhone *joypad_iphone;
- virtual int get_current_video_driver() const;
-
- virtual void initialize_core();
- virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
-
- virtual void set_main_loop(MainLoop *p_main_loop);
- virtual MainLoop *get_main_loop() const;
-
- virtual void delete_main_loop();
+ MainLoop *main_loop;
- virtual void finalize();
+ virtual void initialize_core() override;
+ virtual void initialize() override;
- struct MouseList {
- bool pressed[MAX_MOUSE_COUNT];
- MouseList() {
- for (int i = 0; i < MAX_MOUSE_COUNT; i++)
- pressed[i] = false;
- };
- };
+ virtual void initialize_joypads() override {
+ }
- MouseList touch_list;
+ virtual void set_main_loop(MainLoop *p_main_loop) override;
+ virtual MainLoop *get_main_loop() const override;
- Vector3 last_accel;
+ virtual void delete_main_loop() override;
- Ref<InputEvent> event_queue[MAX_EVENTS];
- int event_count;
- void queue_event(const Ref<InputEvent> &p_event);
+ virtual void finalize() override;
- String data_dir;
+ String user_data_dir;
String unique_id;
String locale_code;
- InputDefault *input;
+ bool is_focused = false;
- int virtual_keyboard_height;
-
- int video_driver_index;
+ void deinitialize_modules();
public:
- bool iterate();
-
- uint8_t get_orientations() const;
-
- void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
- void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
- void touches_cancelled();
- void key(uint32_t p_key, bool p_pressed);
- void set_virtual_keyboard_height(int p_height);
-
- int set_base_framebuffer(int p_fb);
-
- void update_gravity(float p_x, float p_y, float p_z);
- void update_accelerometer(float p_x, float p_y, float p_z);
- void update_magnetometer(float p_x, float p_y, float p_z);
- void update_gyroscope(float p_x, float p_y, float p_z);
-
- int get_unused_joy_id();
- void joy_connection_changed(int p_idx, bool p_connected, String p_name);
- void joy_button(int p_device, int p_button, bool p_pressed);
- void joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value);
-
static OSIPhone *get_singleton();
- virtual void set_mouse_show(bool p_show);
- virtual void set_mouse_grab(bool p_grab);
- virtual bool is_mouse_grab_enabled() const;
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
- virtual void set_window_title(const String &p_title);
-
- virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
+ OSIPhone(String p_data_dir);
+ ~OSIPhone();
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
- virtual Error close_dynamic_library(void *p_library_handle);
- virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
+ void initialize_modules();
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
- virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
+ bool iterate();
- virtual void set_keep_screen_on(bool p_enabled);
+ void start();
- virtual bool can_draw() const;
+ virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
+ virtual Error close_dynamic_library(void *p_library_handle) override;
+ virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
- virtual bool has_virtual_keyboard() const;
- virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
- virtual void hide_virtual_keyboard();
- virtual int get_virtual_keyboard_height() const;
+ virtual void alert(const String &p_alert,
+ const String &p_title = "ALERT!") override;
- virtual Size2 get_window_size() const;
- virtual Rect2 get_window_safe_area() const;
+ virtual String get_name() const override;
+ virtual String get_model_name() const override;
- virtual bool has_touchscreen_ui_hint() const;
+ virtual Error shell_open(String p_uri) override;
- void set_data_dir(String p_dir);
+ void set_user_data_dir(String p_dir);
+ virtual String get_user_data_dir() const override;
- virtual String get_name() const;
- virtual String get_model_name() const;
+ void set_locale(String p_locale);
+ virtual String get_locale() const override;
- Error shell_open(String p_uri);
+ void set_unique_id(String p_id);
+ virtual String get_unique_id() const override;
- String get_user_data_dir() const;
+ virtual void vibrate_handheld(int p_duration_ms = 500) override;
- void set_locale(String p_locale);
- String get_locale() const;
+ virtual bool _check_internal_feature_support(const String &p_feature) override;
- void set_unique_id(String p_id);
- String get_unique_id() const;
-
- virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
- virtual bool native_video_is_playing() const;
- virtual void native_video_pause();
- virtual void native_video_unpause();
- virtual void native_video_focus_out();
- virtual void native_video_stop();
- virtual void vibrate_handheld(int p_duration_ms = 500);
-
- virtual bool _check_internal_feature_support(const String &p_feature);
- OSIPhone(int width, int height, String p_data_dir);
- ~OSIPhone();
+ void on_focus_out();
+ void on_focus_in();
};
#endif // OS_IPHONE_H
-#endif
+#endif // IPHONE_ENABLED
diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm
new file mode 100644
index 0000000000..f0bbbd39ca
--- /dev/null
+++ b/platform/iphone/os_iphone.mm
@@ -0,0 +1,369 @@
+/*************************************************************************/
+/* os_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifdef IPHONE_ENABLED
+
+#include "os_iphone.h"
+#import "app_delegate.h"
+#include "core/io/file_access_pack.h"
+#include "core/os/dir_access.h"
+#include "core/os/file_access.h"
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#include "drivers/unix/syslog_logger.h"
+#import "godot_view.h"
+#include "main/main.h"
+#import "view_controller.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <UIKit/UIKit.h>
+#import <dlfcn.h>
+
+#if defined(OPENGL_ENABLED)
+#include "drivers/gles2/rasterizer_gles2.h"
+#endif
+
+#if defined(VULKAN_ENABLED)
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+#import <QuartzCore/CAMetalLayer.h>
+#include <vulkan/vulkan_metal.h>
+#endif
+
+// Initialization order between compilation units is not guaranteed,
+// so we use this as a hack to ensure certain code is called before
+// everything else, but after all units are initialized.
+typedef void (*init_callback)();
+static init_callback *ios_init_callbacks = nullptr;
+static int ios_init_callbacks_count = 0;
+static int ios_init_callbacks_capacity = 0;
+HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
+
+void add_ios_init_callback(init_callback cb) {
+ if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
+ void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
+ if (new_ptr) {
+ ios_init_callbacks = (init_callback *)(new_ptr);
+ ios_init_callbacks_capacity += 32;
+ }
+ }
+ if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
+ ios_init_callbacks[ios_init_callbacks_count] = cb;
+ ++ios_init_callbacks_count;
+ }
+}
+
+void register_dynamic_symbol(char *name, void *address) {
+ OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
+}
+
+OSIPhone *OSIPhone::get_singleton() {
+ return (OSIPhone *)OS::get_singleton();
+}
+
+OSIPhone::OSIPhone(String p_data_dir) {
+ for (int i = 0; i < ios_init_callbacks_count; ++i) {
+ ios_init_callbacks[i]();
+ }
+ free(ios_init_callbacks);
+ ios_init_callbacks = nullptr;
+ ios_init_callbacks_count = 0;
+ ios_init_callbacks_capacity = 0;
+
+ main_loop = nullptr;
+
+ // can't call set_data_dir from here, since it requires DirAccess
+ // which is initialized in initialize_core
+ user_data_dir = p_data_dir;
+
+ Vector<Logger *> loggers;
+ loggers.push_back(memnew(SyslogLogger));
+#ifdef DEBUG_ENABLED
+ // it seems iOS app's stdout/stderr is only obtainable if you launch it from
+ // Xcode
+ loggers.push_back(memnew(StdLogger));
+#endif
+ _set_logger(memnew(CompositeLogger(loggers)));
+
+ AudioDriverManager::add_driver(&audio_driver);
+
+ DisplayServerIPhone::register_iphone_driver();
+}
+
+OSIPhone::~OSIPhone() {}
+
+void OSIPhone::initialize_core() {
+ OS_Unix::initialize_core();
+
+ set_user_data_dir(user_data_dir);
+}
+
+void OSIPhone::initialize() {
+ initialize_core();
+}
+
+void OSIPhone::initialize_modules() {
+#ifdef GAME_CENTER_ENABLED
+ game_center = memnew(GameCenter);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
+ game_center->connect();
+#endif
+
+#ifdef STOREKIT_ENABLED
+ store_kit = memnew(InAppStore);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
+#endif
+
+#ifdef ICLOUD_ENABLED
+ icloud = memnew(ICloud);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
+#endif
+
+ ios = memnew(iOS);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
+
+ joypad_iphone = memnew(JoypadIPhone);
+}
+
+void OSIPhone::deinitialize_modules() {
+ if (joypad_iphone) {
+ memdelete(joypad_iphone);
+ }
+
+ if (ios) {
+ memdelete(ios);
+ }
+
+#ifdef GAME_CENTER_ENABLED
+ if (game_center) {
+ memdelete(game_center);
+ }
+#endif
+
+#ifdef STOREKIT_ENABLED
+ if (store_kit) {
+ memdelete(store_kit);
+ }
+#endif
+
+#ifdef ICLOUD_ENABLED
+ if (icloud) {
+ memdelete(icloud);
+ }
+#endif
+}
+
+void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
+ main_loop = p_main_loop;
+
+ if (main_loop) {
+ main_loop->init();
+ }
+}
+
+MainLoop *OSIPhone::get_main_loop() const {
+ return main_loop;
+}
+
+void OSIPhone::delete_main_loop() {
+ if (main_loop) {
+ main_loop->finish();
+ memdelete(main_loop);
+ };
+
+ main_loop = nullptr;
+}
+
+bool OSIPhone::iterate() {
+ if (!main_loop) {
+ return true;
+ }
+
+ if (DisplayServer::get_singleton()) {
+ DisplayServer::get_singleton()->process_events();
+ }
+
+ return Main::iteration();
+}
+
+void OSIPhone::start() {
+ Main::start();
+
+ if (joypad_iphone) {
+ joypad_iphone->start_processing();
+ }
+}
+
+void OSIPhone::finalize() {
+ deinitialize_modules();
+
+ // Already gets called
+ // delete_main_loop();
+}
+
+// MARK: Dynamic Libraries
+
+Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+ if (p_path.length() == 0) {
+ p_library_handle = RTLD_SELF;
+ return OK;
+ }
+ return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
+}
+
+Error OSIPhone::close_dynamic_library(void *p_library_handle) {
+ if (p_library_handle == RTLD_SELF) {
+ return OK;
+ }
+ return OS_Unix::close_dynamic_library(p_library_handle);
+}
+
+Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
+ if (p_library_handle == RTLD_SELF) {
+ void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
+ if (ptr) {
+ p_symbol_handle = *ptr;
+ return OK;
+ }
+ }
+ return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
+}
+
+void OSIPhone::alert(const String &p_alert, const String &p_title) {
+ const CharString utf8_alert = p_alert.utf8();
+ const CharString utf8_title = p_title.utf8();
+ iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
+}
+
+String OSIPhone::get_name() const {
+ return "iOS";
+};
+
+String OSIPhone::get_model_name() const {
+ String model = ios->get_model();
+ if (model != "")
+ return model;
+
+ return OS_Unix::get_model_name();
+}
+
+Error OSIPhone::shell_open(String p_uri) {
+ NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
+ NSURL *url = [NSURL URLWithString:urlPath];
+ [urlPath release];
+
+ if (![[UIApplication sharedApplication] canOpenURL:url]) {
+ return ERR_CANT_OPEN;
+ }
+
+ printf("opening url %ls\n", p_uri.c_str());
+
+ // if (@available(iOS 10, *)) {
+ [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
+ // } else {
+ // [[UIApplication sharedApplication] openURL:url];
+ // }
+
+ return OK;
+};
+
+void OSIPhone::set_user_data_dir(String p_dir) {
+ DirAccess *da = DirAccess::open(p_dir);
+
+ user_data_dir = da->get_current_dir();
+ printf("setting data dir to %ls from %ls\n", user_data_dir.c_str(), p_dir.c_str());
+ memdelete(da);
+}
+
+String OSIPhone::get_user_data_dir() const {
+ return user_data_dir;
+}
+
+void OSIPhone::set_locale(String p_locale) {
+ locale_code = p_locale;
+}
+
+String OSIPhone::get_locale() const {
+ return locale_code;
+}
+
+void OSIPhone::set_unique_id(String p_id) {
+ unique_id = p_id;
+}
+
+String OSIPhone::get_unique_id() const {
+ return unique_id;
+}
+
+void OSIPhone::vibrate_handheld(int p_duration_ms) {
+ // iOS does not support duration for vibration
+ AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+}
+
+bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
+ return p_feature == "mobile";
+}
+
+void OSIPhone::on_focus_out() {
+ if (is_focused) {
+ is_focused = false;
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
+ }
+
+ [AppDelegate.viewController.godotView stopRendering];
+
+ if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
+ DisplayServerIPhone::get_singleton()->native_video_pause();
+ }
+
+ audio_driver.stop();
+ }
+}
+
+void OSIPhone::on_focus_in() {
+ if (!is_focused) {
+ is_focused = true;
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
+ }
+
+ [AppDelegate.viewController.godotView startRendering];
+
+ if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
+ DisplayServerIPhone::get_singleton()->native_video_unpause();
+ }
+
+ audio_driver.start();
+ }
+}
+
+#endif // IPHONE_ENABLED
diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h
index f6bbe11d97..dffdc01d4a 100644
--- a/platform/iphone/view_controller.h
+++ b/platform/iphone/view_controller.h
@@ -31,20 +31,18 @@
#import <GameKit/GameKit.h>
#import <UIKit/UIKit.h>
-@interface ViewController : UIViewController <GKGameCenterControllerDelegate> {
-};
+@class GodotView;
-- (BOOL)shouldAutorotateToInterfaceOrientation:
- (UIInterfaceOrientation)p_orientation;
+@interface ViewController : UIViewController <GKGameCenterControllerDelegate>
-- (void)didReceiveMemoryWarning;
+- (GodotView *)godotView;
-- (void)viewDidLoad;
+// MARK: Native Video Player
-- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures;
-
-- (BOOL)prefersStatusBarHidden;
-
-- (BOOL)prefersHomeIndicatorAutoHidden;
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
+- (BOOL)isVideoPlaying;
+- (void)pauseVideo;
+- (void)unpauseVideo;
+- (void)stopVideo;
@end
diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm
index 279bcc1226..31597f7856 100644
--- a/platform/iphone/view_controller.mm
+++ b/platform/iphone/view_controller.mm
@@ -29,96 +29,174 @@
/*************************************************************************/
#import "view_controller.h"
-
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#import "godot_view.h"
+#import "godot_view_renderer.h"
#include "os_iphone.h"
-#include "core/project_settings.h"
+#import <GameController/GameController.h>
-extern "C" {
+@interface ViewController ()
-int add_path(int, char **);
-int add_cmdline(int, char **);
+@property(strong, nonatomic) GodotViewRenderer *renderer;
-int add_path(int p_argc, char **p_args) {
- NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
- if (!str)
- return p_argc;
+// TODO: separate view to handle video
+// AVPlayer-related properties
+@property(strong, nonatomic) AVAsset *avAsset;
+@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
+@property(strong, nonatomic) AVPlayer *avPlayer;
+@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
+@property(assign, nonatomic) CMTime videoCurrentTime;
+@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying;
+@property(assign, nonatomic) BOOL videoHasFoundError;
- p_args[p_argc++] = "--path";
- [str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
- p_args[p_argc++] = (char *)[str cString];
- p_args[p_argc] = NULL;
+@end
- return p_argc;
-};
+@implementation ViewController
-int add_cmdline(int p_argc, char **p_args) {
- NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
- if (!arr)
- return p_argc;
+- (GodotView *)godotView {
+ return (GodotView *)self.view;
+}
- for (int i = 0; i < [arr count]; i++) {
- NSString *str = [arr objectAtIndex:i];
- if (!str)
- continue;
- [str retain]; // @todo delete these at some point
- p_args[p_argc++] = (char *)[str cString];
- };
+- (void)loadView {
+ GodotView *view = [[GodotView alloc] init];
+ GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
- p_args[p_argc] = NULL;
+ self.renderer = renderer;
+ self.view = view;
- return p_argc;
-};
-}; // extern "C"
+ view.renderer = self.renderer;
-@interface ViewController ()
+ [renderer release];
+ [view release];
+}
-@end
+- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
+ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
-@implementation ViewController
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.isVideoCurrentlyPlaying = NO;
+ self.videoCurrentTime = kCMTimeZero;
+ self.videoHasFoundError = false;
+}
- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
printf("*********** did receive memory warning!\n");
-};
+}
- (void)viewDidLoad {
[super viewDidLoad];
+ [self observeKeyboard];
+ [self observeAudio];
+
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
}
+- (void)observeKeyboard {
+ printf("******** adding observer for keyboard show/hide\n");
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardOnScreen:)
+ name:UIKeyboardDidShowNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardHidden:)
+ name:UIKeyboardDidHideNotification
+ object:nil];
+}
+
+- (void)observeAudio {
+ printf("******** adding observer for sound routing changes\n");
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(audioRouteChangeListenerCallback:)
+ name:AVAudioSessionRouteChangeNotification
+ object:nil];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+ if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) {
+ [self handleVideoOrPlayerStatus];
+ }
+
+ if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) {
+ [self handleVideoPlayRate];
+ }
+}
+
+- (void)dealloc {
+ [self stopVideo];
+
+ self.renderer = nil;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [super dealloc];
+}
+
+// MARK: Orientation
+
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeAll;
}
- (BOOL)shouldAutorotate {
- switch (OS::get_singleton()->get_screen_orientation()) {
- case OS::SCREEN_SENSOR:
- case OS::SCREEN_SENSOR_LANDSCAPE:
- case OS::SCREEN_SENSOR_PORTRAIT:
+ if (!DisplayServerIPhone::get_singleton()) {
+ return NO;
+ }
+
+ switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+ case DisplayServer::SCREEN_SENSOR:
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return YES;
default:
return NO;
}
-};
+}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
- switch (OS::get_singleton()->get_screen_orientation()) {
- case OS::SCREEN_PORTRAIT:
+ if (!DisplayServerIPhone::get_singleton()) {
+ return UIInterfaceOrientationMaskAll;
+ }
+
+ switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+ case DisplayServer::SCREEN_PORTRAIT:
return UIInterfaceOrientationMaskPortrait;
- case OS::SCREEN_REVERSE_LANDSCAPE:
+ case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeRight;
- case OS::SCREEN_REVERSE_PORTRAIT:
+ case DisplayServer::SCREEN_REVERSE_PORTRAIT:
return UIInterfaceOrientationMaskPortraitUpsideDown;
- case OS::SCREEN_SENSOR_LANDSCAPE:
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
return UIInterfaceOrientationMaskLandscape;
- case OS::SCREEN_SENSOR_PORTRAIT:
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
- case OS::SCREEN_SENSOR:
+ case DisplayServer::SCREEN_SENSOR:
return UIInterfaceOrientationMaskAll;
- case OS::SCREEN_LANDSCAPE:
+ case DisplayServer::SCREEN_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeLeft;
}
};
@@ -135,6 +213,190 @@ int add_cmdline(int p_argc, char **p_args) {
}
}
+// MARK: Keyboard
+
+- (void)keyboardOnScreen:(NSNotification *)notification {
+ NSDictionary *info = notification.userInfo;
+ NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
+
+ CGRect rawFrame = [value CGRectValue];
+ CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil];
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
+ }
+}
+
+- (void)keyboardHidden:(NSNotification *)notification {
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(0);
+ }
+}
+
+// MARK: Audio
+
+- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
+ printf("*********** route changed!\n");
+ NSDictionary *interuptionDict = notification.userInfo;
+
+ NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
+
+ switch (routeChangeReason) {
+ case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
+ NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
+ NSLog(@"Headphone/Line plugged in");
+ } break;
+ case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
+ NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
+ NSLog(@"Headphone/Line was pulled. Resuming video play....");
+ if ([self isVideoPlaying]) {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ [self.avPlayer play]; // NOTE: change this line according your current player implementation
+ NSLog(@"resumed play");
+ });
+ }
+ } break;
+ case AVAudioSessionRouteChangeReasonCategoryChange: {
+ // called at start - also when other audio wants to play
+ NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
+ } break;
+ }
+}
+
+// MARK: Native Video Player
+
+- (void)handleVideoOrPlayerStatus {
+ if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) {
+ [self stopVideo];
+ self.videoHasFoundError = true;
+ }
+
+ if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) {
+ // NSLog(@"time: %@", self.video_current_time);
+ [self.avPlayer seekToTime:self.videoCurrentTime];
+ self.videoCurrentTime = kCMTimeZero;
+ }
+}
+
+- (void)handleVideoPlayRate {
+ NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate);
+ if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ [self.avPlayer play]; // NOTE: change this line according your current player implementation
+ NSLog(@"resumed play");
+ });
+
+ NSLog(@" . . . PAUSED (or just started)");
+ }
+}
+
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
+ self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]];
+
+ self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset];
+ [self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil];
+
+ self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem];
+ self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
+
+ [self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(playerItemDidReachEnd:)
+ name:AVPlayerItemDidPlayToEndTimeNotification
+ object:[self.avPlayer currentItem]];
+
+ [self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
+
+ [self.avPlayerLayer setFrame:self.view.bounds];
+ [self.view.layer addSublayer:self.avPlayerLayer];
+ [self.avPlayer play];
+
+ AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
+
+ NSMutableArray *allAudioParams = [NSMutableArray array];
+ for (id track in audioGroup.options) {
+ NSString *language = [[track locale] localeIdentifier];
+ NSLog(@"subtitle lang: %@", language);
+
+ if ([language isEqualToString:audioTrack]) {
+ AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
+ [audioInputParams setVolume:videoVolume atTime:kCMTimeZero];
+ [audioInputParams setTrackID:[track trackID]];
+ [allAudioParams addObject:audioInputParams];
+
+ AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
+ [audioMix setInputParameters:allAudioParams];
+
+ [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
+ [self.avPlayer.currentItem setAudioMix:audioMix];
+
+ break;
+ }
+ }
+
+ AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
+ NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
+
+ for (id track in useableTracks) {
+ NSString *language = [[track locale] localeIdentifier];
+ NSLog(@"subtitle lang: %@", language);
+
+ if ([language isEqualToString:subtitleTrack]) {
+ [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
+ break;
+ }
+ }
+
+ self.isVideoCurrentlyPlaying = YES;
+
+ return true;
+}
+
+- (BOOL)isVideoPlaying {
+ if (self.avPlayer.error) {
+ printf("Error during playback\n");
+ }
+ return (self.avPlayer.rate > 0 && !self.avPlayer.error);
+}
+
+- (void)pauseVideo {
+ self.videoCurrentTime = self.avPlayer.currentTime;
+ [self.avPlayer pause];
+ self.isVideoCurrentlyPlaying = NO;
+}
+
+- (void)unpauseVideo {
+ [self.avPlayer play];
+ self.isVideoCurrentlyPlaying = YES;
+}
+
+- (void)playerItemDidReachEnd:(NSNotification *)notification {
+ [self stopVideo];
+}
+
+- (void)stopVideo {
+ [self.avPlayer pause];
+ [self.avPlayerLayer removeFromSuperlayer];
+ self.avPlayerLayer = nil;
+
+ if (self.avPlayerItem) {
+ [self.avPlayerItem removeObserver:self forKeyPath:@"status"];
+ self.avPlayerItem = nil;
+ }
+
+ if (self.avPlayer) {
+ [self.avPlayer removeObserver:self forKeyPath:@"status"];
+ self.avPlayer = nil;
+ }
+
+ self.avAsset = nil;
+
+ self.isVideoCurrentlyPlaying = NO;
+}
+
+// MARK: Delegates
+
#ifdef GAME_CENTER_ENABLED
- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone
diff --git a/platform/iphone/vulkan_context_iphone.h b/platform/iphone/vulkan_context_iphone.h
index cadd701636..5c3d5fe33e 100644
--- a/platform/iphone/vulkan_context_iphone.h
+++ b/platform/iphone/vulkan_context_iphone.h
@@ -32,13 +32,14 @@
#define VULKAN_CONTEXT_IPHONE_H
#include "drivers/vulkan/vulkan_context.h"
-// #import <UIKit/UIKit.h>
+
+#import <UIKit/UIKit.h>
class VulkanContextIPhone : public VulkanContext {
virtual const char *_get_platform_surface_extension() const;
public:
- int window_create(void *p_window, int p_width, int p_height);
+ Error window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height);
VulkanContextIPhone();
~VulkanContextIPhone();
diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm
index 44c940dc3a..cb4dbe7f85 100644
--- a/platform/iphone/vulkan_context_iphone.mm
+++ b/platform/iphone/vulkan_context_iphone.mm
@@ -35,21 +35,23 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const {
return VK_MVK_IOS_SURFACE_EXTENSION_NAME;
}
-int VulkanContextIPhone::window_create(void *p_window, int p_width, int p_height) {
+Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id,
+ CALayer *p_metal_layer, int p_width,
+ int p_height) {
VkIOSSurfaceCreateInfoMVK createInfo;
- createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
+ createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
createInfo.pNext = NULL;
createInfo.flags = 0;
- createInfo.pView = p_window;
+ createInfo.pView = p_metal_layer;
VkSurfaceKHR surface;
- VkResult err = vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
- ERR_FAIL_COND_V(err, -1);
- return _window_create(surface, p_width, p_height);
-}
+ VkResult err =
+ vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
+ ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
-VulkanContextIPhone::VulkanContextIPhone() {
+ return _window_create(p_window_id, surface, p_width, p_height);
}
-VulkanContextIPhone::~VulkanContextIPhone() {
-}
+VulkanContextIPhone::VulkanContextIPhone() {}
+
+VulkanContextIPhone::~VulkanContextIPhone() {}
diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp
index 99672745e7..f697887f08 100644
--- a/platform/javascript/javascript_main.cpp
+++ b/platform/javascript/javascript_main.cpp
@@ -131,6 +131,10 @@ int main(int argc, char *argv[]) {
/* clang-format on */
os = new OS_JavaScript();
+
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
+
Main::setup(argv[0], argc - 1, &argv[1], false);
emscripten_set_main_loop(main_loop_callback, -1, false);
emscripten_pause_main_loop(); // Will need to wait for FS sync.
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index 07fa06bc06..3eb4c44bc1 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -109,7 +109,7 @@ def configure(env):
elif env["target"] == "debug":
env.Prepend(CCFLAGS=["-g3"])
- env.Prepend(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"])
+ env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
env.Append(LINKFLAGS=["-rdynamic"])
## Architecture
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index 21b3bcec30..874a3a6392 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -2372,7 +2372,7 @@ void DisplayServerX11::process_events() {
uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_no_focus;
if (delta > 250) {
- //X11 can go between windows and have no focus for a while, when creating them or something else. Use this as safety to avoid unnecesary focus in/outs.
+ //X11 can go between windows and have no focus for a while, when creating them or something else. Use this as safety to avoid unnecessary focus in/outs.
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
}
@@ -3332,7 +3332,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u
if (make_utility) {
//this one seems to disable the fade animations for regular windows
//but has the drawback that will not get focus by default, so
- //we need fo force it, unless no focus requested
+ //we need to force it, unless no focus requested
Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);
diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp
index 3ed64e9d46..e1796ccefe 100644
--- a/platform/linuxbsd/godot_linuxbsd.cpp
+++ b/platform/linuxbsd/godot_linuxbsd.cpp
@@ -41,6 +41,9 @@ int main(int argc, char *argv[]) {
setlocale(LC_CTYPE, "");
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
+
char *cwd = (char *)malloc(PATH_MAX);
ERR_FAIL_COND_V(!cwd, ERR_OUT_OF_MEMORY);
char *ret = getcwd(cwd, PATH_MAX);
diff --git a/platform/osx/detect.py b/platform/osx/detect.py
index ff4c024551..d700bcd7f6 100644
--- a/platform/osx/detect.py
+++ b/platform/osx/detect.py
@@ -24,7 +24,7 @@ def get_opts():
from SCons.Variables import BoolVariable, EnumVariable
return [
- ("osxcross_sdk", "OSXCross SDK version", "darwin14"),
+ ("osxcross_sdk", "OSXCross SDK version", "darwin16"),
("MACOS_SDK_PATH", "Path to the macOS SDK", ""),
BoolVariable(
"use_static_mvk",
@@ -50,9 +50,11 @@ def configure(env):
if env["target"] == "release":
if env["optimize"] == "speed": # optimize for speed (default)
- env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize", "-msse2"])
+ env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize"])
else: # optimize for size
- env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize", "-msse2"])
+ env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"])
+ if env["arch"] != "arm64":
+ env.Prepend(CCFLAGS=["-msse2"])
if env["debug_symbols"] == "yes":
env.Prepend(CCFLAGS=["-g1"])
@@ -72,7 +74,7 @@ def configure(env):
elif env["target"] == "debug":
env.Prepend(CCFLAGS=["-g3"])
- env.Prepend(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"])
+ env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
## Architecture
@@ -86,16 +88,16 @@ def configure(env):
if "OSXCROSS_ROOT" in os.environ:
env["osxcross"] = True
- if not "osxcross" in env: # regular native build
- if env["arch"] == "arm64":
- print("Building for macOS 10.15+, platform arm64.")
- env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"])
- env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"])
- else:
- print("Building for macOS 10.12+, platform x86-64.")
- env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
- env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
+ if env["arch"] == "arm64":
+ print("Building for macOS 10.15+, platform arm64.")
+ env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"])
+ env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"])
+ else:
+ print("Building for macOS 10.12+, platform x86-64.")
+ env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
+ env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
+ if not "osxcross" in env: # regular native build
if env["macports_clang"] != "no":
mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local")
mpclangver = env["macports_clang"]
@@ -116,7 +118,10 @@ def configure(env):
else: # osxcross build
root = os.environ.get("OSXCROSS_ROOT", 0)
- basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-"
+ if env["arch"] == "arm64":
+ basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-"
+ else:
+ basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-"
ccache_path = os.environ.get("CCACHE")
if ccache_path is None:
diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm
index 5cc2b25910..ffe60ad582 100644
--- a/platform/osx/display_server_osx.mm
+++ b/platform/osx/display_server_osx.mm
@@ -2782,7 +2782,7 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
switch (p_flag) {
case WINDOW_FLAG_RESIZE_DISABLED: {
wd.resize_disabled = p_enabled;
- if (wd.fullscreen) { //fullscreen window should be resizable, style will be applyed on exiting fs
+ if (wd.fullscreen) { //fullscreen window should be resizable, style will be applied on exiting fs
return;
}
if (p_enabled) {
@@ -2793,7 +2793,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
} break;
case WINDOW_FLAG_BORDERLESS: {
// OrderOut prevents a lose focus bug with the window
- [wd.window_object orderOut:nil];
+ if ([wd.window_object isVisible]) {
+ [wd.window_object orderOut:nil];
+ }
wd.borderless = p_enabled;
if (p_enabled) {
[wd.window_object setStyleMask:NSWindowStyleMaskBorderless];
@@ -2807,7 +2809,13 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
[wd.window_object setFrame:frameRect display:NO];
}
_update_window(wd);
- [wd.window_object makeKeyAndOrderFront:nil];
+ if ([wd.window_object isVisible]) {
+ if (wd.no_focus) {
+ [wd.window_object orderFront:nil];
+ } else {
+ [wd.window_object makeKeyAndOrderFront:nil];
+ }
+ }
} break;
case WINDOW_FLAG_ALWAYS_ON_TOP: {
wd.on_top = p_enabled;
@@ -2875,7 +2883,11 @@ void DisplayServerOSX::window_move_to_foreground(WindowID p_window) {
const WindowData &wd = windows[p_window];
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
- [wd.window_object makeKeyAndOrderFront:nil];
+ if (wd.no_focus) {
+ [wd.window_object orderFront:nil];
+ } else {
+ [wd.window_object makeKeyAndOrderFront:nil];
+ }
}
bool DisplayServerOSX::window_can_draw(WindowID p_window) const {
@@ -3492,9 +3504,7 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, c
wd.window_view = [[GodotContentView alloc] init];
ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view");
[wd.window_view setWindowID:window_id_counter];
- if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_14) {
- [wd.window_view setWantsLayer:TRUE];
- }
+ [wd.window_view setWantsLayer:TRUE];
[wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
[wd.window_object setContentView:wd.window_view];
diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm
index 93d0d6168c..4e73d5441c 100644
--- a/platform/osx/godot_main_osx.mm
+++ b/platform/osx/godot_main_osx.mm
@@ -37,7 +37,7 @@
int main(int argc, char **argv) {
#if defined(VULKAN_ENABLED)
- //MoltenVK - enable full component swizzling support
+ // MoltenVK - enable full component swizzling support
setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
#endif
@@ -60,6 +60,9 @@ int main(int argc, char **argv) {
OS_OSX os;
Error err;
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
+
if (os.open_with_filename != "") {
char *argv_c = (char *)malloc(os.open_with_filename.utf8().size());
memcpy(argv_c, os.open_with_filename.utf8().get_data(), os.open_with_filename.utf8().size());
diff --git a/platform/server/detect.py b/platform/server/detect.py
index a73810cdf4..4c5a4527ae 100644
--- a/platform/server/detect.py
+++ b/platform/server/detect.py
@@ -79,7 +79,7 @@ def configure(env):
elif env["target"] == "debug":
env.Prepend(CCFLAGS=["-g3"])
- env.Prepend(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"])
+ env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
env.Append(LINKFLAGS=["-rdynamic"])
## Architecture
diff --git a/platform/server/godot_server.cpp b/platform/server/godot_server.cpp
index 32bd943ac3..9f22240a80 100644
--- a/platform/server/godot_server.cpp
+++ b/platform/server/godot_server.cpp
@@ -34,6 +34,9 @@
int main(int argc, char *argv[]) {
OS_Server os;
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
+
Error err = Main::setup(argv[0], argc - 1, &argv[1]);
if (err != OK)
return 255;
diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py
index 669bfe6814..c23a65ef75 100644
--- a/platform/uwp/detect.py
+++ b/platform/uwp/detect.py
@@ -69,7 +69,7 @@ def configure(env):
elif env["target"] == "debug":
env.Append(CCFLAGS=["/Zi"])
env.Append(CCFLAGS=["/MDd"])
- env.Append(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"])
+ env.Append(CPPDEFINES=["DEBUG_ENABLED"])
env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"])
env.Append(LINKFLAGS=["/DEBUG"])
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 9f79e92dcb..0ab0e1ed3c 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -196,7 +196,7 @@ def configure_msvc(env, manual_msvc_config):
elif env["target"] == "debug":
env.AppendUnique(CCFLAGS=["/Z7", "/Od", "/EHsc"])
- env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED", "D3D_DEBUG_INFO"])
+ env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"])
env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"])
env.Append(LINKFLAGS=["/DEBUG"])
@@ -333,7 +333,7 @@ def configure_mingw(env):
elif env["target"] == "debug":
env.Append(CCFLAGS=["-g3"])
- env.Append(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"])
+ env.Append(CPPDEFINES=["DEBUG_ENABLED"])
## Compiler configuration
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 0251ffe664..6ee9b6d698 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -170,7 +170,7 @@ void DisplayServerWindows::clipboard_set(const String &p_text) {
// Convert LF line endings to CRLF in clipboard content
// Otherwise, line endings won't be visible when pasted in other software
- String text = p_text.replace("\n", "\r\n");
+ String text = p_text.replace("\r\n", "\n").replace("\n", "\r\n"); // avoid \r\r\n
if (!OpenClipboard(windows[last_focused_window].hWnd)) {
ERR_FAIL_MSG("Unable to open clipboard.");
diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp
index 910059a9fc..add559a717 100644
--- a/platform/windows/godot_windows.cpp
+++ b/platform/windows/godot_windows.cpp
@@ -146,6 +146,8 @@ int widechar_main(int argc, wchar_t **argv) {
argv_utf8[i] = wc_to_utf8(argv[i]);
}
+ TEST_MAIN_PARAM_OVERRIDE(argc, argv_utf8)
+
Error err = Main::setup(argv_utf8[0], argc - 1, &argv_utf8[1]);
if (err != OK) {
@@ -186,10 +188,12 @@ int _main() {
return result;
}
-int main(int _argc, char **_argv) {
+int main(int argc, char **argv) {
+ // override the arguments for the test handler / if symbol is provided
+ // TEST_MAIN_OVERRIDE
+
// _argc and _argv are ignored
// we are going to use the WideChar version of them instead
-
#ifdef CRASH_HANDLER_EXCEPTION
__try {
return _main();
diff --git a/scene/3d/navigation_3d.cpp b/scene/3d/navigation_3d.cpp
index 07a4824c28..851966db2b 100644
--- a/scene/3d/navigation_3d.cpp
+++ b/scene/3d/navigation_3d.cpp
@@ -109,7 +109,7 @@ Navigation3D::Navigation3D() {
map = NavigationServer3D::get_singleton()->map_create();
set_cell_size(0.3);
- set_edge_connection_margin(5.0); // Five meters, depends alot on the agents radius
+ set_edge_connection_margin(5.0); // Five meters, depends a lot on the agent's radius
up = Vector3(0, 1, 0);
}
diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp
index 72ae75b236..fc021e5532 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -358,6 +358,7 @@ void RigidBody3D::_direct_state_changed(Object *p_state) {
set_global_transform(state->get_transform());
linear_velocity = state->get_linear_velocity();
angular_velocity = state->get_angular_velocity();
+ inverse_inertia_tensor = state->get_inverse_inertia_tensor();
if (sleeping != state->is_sleeping()) {
sleeping = state->is_sleeping();
emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed);
@@ -594,6 +595,10 @@ Vector3 RigidBody3D::get_angular_velocity() const {
return angular_velocity;
}
+Basis RigidBody3D::get_inverse_inertia_tensor() {
+ return inverse_inertia_tensor;
+}
+
void RigidBody3D::set_use_custom_integrator(bool p_enable) {
if (custom_integrator == p_enable) {
return;
@@ -760,6 +765,8 @@ void RigidBody3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_angular_velocity", "angular_velocity"), &RigidBody3D::set_angular_velocity);
ClassDB::bind_method(D_METHOD("get_angular_velocity"), &RigidBody3D::get_angular_velocity);
+ ClassDB::bind_method(D_METHOD("get_inverse_inertia_tensor"), &RigidBody3D::get_inverse_inertia_tensor);
+
ClassDB::bind_method(D_METHOD("set_gravity_scale", "gravity_scale"), &RigidBody3D::set_gravity_scale);
ClassDB::bind_method(D_METHOD("get_gravity_scale"), &RigidBody3D::get_gravity_scale);
diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h
index 413b587fc4..9830a55183 100644
--- a/scene/3d/physics_body_3d.h
+++ b/scene/3d/physics_body_3d.h
@@ -123,6 +123,7 @@ protected:
Vector3 linear_velocity;
Vector3 angular_velocity;
+ Basis inverse_inertia_tensor;
real_t gravity_scale;
real_t linear_damp;
real_t angular_damp;
@@ -201,6 +202,8 @@ public:
void set_angular_velocity(const Vector3 &p_velocity);
Vector3 get_angular_velocity() const override;
+ Basis get_inverse_inertia_tensor();
+
void set_gravity_scale(real_t p_gravity_scale);
real_t get_gravity_scale() const;
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index 854db5fee2..bd4396d680 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -701,21 +701,21 @@ void Tween::_tween_process(float p_delta) {
}
// Are all of the tweens complete?
- bool all_finished = true;
+ int any_unfinished = 0;
// For each tween we wish to interpolate...
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Get the data from it
InterpolateData &data = E->get();
- // Track if we hit one that isn't finished yet
- all_finished = all_finished && data.finish;
-
// Is the data not active or already finished? No need to go any further
if (!data.active || data.finish) {
continue;
}
+ // Track if we hit one that isn't finished yet
+ any_unfinished++;
+
// Get the target object for this interpolation
Object *object = ObjectDB::get_instance(data.id);
if (object == nullptr) {
@@ -802,17 +802,15 @@ void Tween::_tween_process(float p_delta) {
// If we are not repeating the tween, remove it
if (!repeat) {
call_deferred("_remove_by_uid", data.uid);
+ any_unfinished--;
}
- } else if (!repeat) {
- // Check whether all tweens are finished
- all_finished = all_finished && data.finish;
}
}
// One less update left to go
pending_update--;
// If all tweens are completed, we no longer need to be active
- if (all_finished) {
+ if (any_unfinished == 0) {
set_active(false);
emit_signal("tween_all_completed");
}
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
index 75d04dba61..f130726837 100644
--- a/scene/gui/box_container.cpp
+++ b/scene/gui/box_container.cpp
@@ -96,7 +96,7 @@ void BoxContainer::_resort() {
}
stretch_avail += stretch_diff; //available stretch space.
- /** Second, pass sucessively to discard elements that can't be stretched, this will run while stretchable
+ /** Second, pass successively to discard elements that can't be stretched, this will run while stretchable
elements exist */
bool has_stretched = false;
@@ -104,6 +104,7 @@ void BoxContainer::_resort() {
has_stretched = true;
bool refit_successful = true; //assume refit-test will go well
+ float error = 0; // Keep track of accumulated error in pixels
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -119,8 +120,9 @@ void BoxContainer::_resort() {
if (msc.will_stretch) { //wants to stretch
//let's see if it can really stretch
-
- int final_pixel_size = stretch_avail * c->get_stretch_ratio() / stretch_ratio_total;
+ float final_pixel_size = stretch_avail * c->get_stretch_ratio() / stretch_ratio_total;
+ // Add leftover fractional pixels to error accumulator
+ error += final_pixel_size - (int)final_pixel_size;
if (final_pixel_size < msc.min_size) {
//if available stretching area is too small for widget,
//then remove it from stretching area
@@ -132,6 +134,11 @@ void BoxContainer::_resort() {
break;
} else {
msc.final_size = final_pixel_size;
+ // Dump accumulated error if one pixel or more
+ if (error >= 1) {
+ msc.final_size += 1;
+ error -= 1;
+ }
}
}
}
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 21eb3d558c..4aea2928f4 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -272,7 +272,7 @@ void FileDialog::_action_pressed() {
}
if (dir_access->file_exists(f)) {
- confirm_save->set_text(RTR("File Exists, Overwrite?"));
+ confirm_save->set_text(RTR("File exists, overwrite?"));
confirm_save->popup_centered(Size2(200, 80));
} else {
emit_signal("file_selected", f);
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 27c2c70708..646f9f6095 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -118,7 +118,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
selection.creating = false;
selection.doubleclick = false;
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
if (selection.enabled) {
DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), max_length, selection.begin, selection.end);
} else {
@@ -309,7 +309,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
case KEY_KP_ENTER:
case KEY_ENTER: {
emit_signal("text_entered", text);
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
}
@@ -699,7 +699,7 @@ void LineEdit::_notification(int p_what) {
update();
} break;
case NOTIFICATION_DRAW: {
- if ((!has_focus() && !menu->has_focus()) || !window_has_focus) {
+ if ((!has_focus() && !menu->has_focus() && !caret_force_displayed) || !window_has_focus) {
draw_caret = false;
}
@@ -925,10 +925,14 @@ void LineEdit::_notification(int p_what) {
}
} break;
case NOTIFICATION_FOCUS_ENTER: {
- if (caret_blink_enabled) {
- caret_blink_timer->start();
- } else {
- draw_caret = true;
+ if (!caret_force_displayed) {
+ if (caret_blink_enabled) {
+ if (caret_blink_timer->is_stopped()) {
+ caret_blink_timer->start();
+ }
+ } else {
+ draw_caret = true;
+ }
}
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
@@ -937,7 +941,7 @@ void LineEdit::_notification(int p_what) {
DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
}
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
if (selection.enabled) {
DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), max_length, selection.begin, selection.end);
} else {
@@ -947,7 +951,7 @@ void LineEdit::_notification(int p_what) {
} break;
case NOTIFICATION_FOCUS_EXIT: {
- if (caret_blink_enabled) {
+ if (caret_blink_enabled && !caret_force_displayed) {
caret_blink_timer->stop();
}
@@ -958,7 +962,7 @@ void LineEdit::_notification(int p_what) {
ime_text = "";
ime_selection = Point2();
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
}
@@ -1167,9 +1171,11 @@ bool LineEdit::cursor_get_blink_enabled() const {
void LineEdit::cursor_set_blink_enabled(const bool p_enabled) {
caret_blink_enabled = p_enabled;
- if (has_focus()) {
+ if (has_focus() || caret_force_displayed) {
if (p_enabled) {
- caret_blink_timer->start();
+ if (caret_blink_timer->is_stopped()) {
+ caret_blink_timer->start();
+ }
} else {
caret_blink_timer->stop();
}
@@ -1178,6 +1184,16 @@ void LineEdit::cursor_set_blink_enabled(const bool p_enabled) {
draw_caret = true;
}
+bool LineEdit::cursor_get_force_displayed() const {
+ return caret_force_displayed;
+}
+
+void LineEdit::cursor_set_force_displayed(const bool p_enabled) {
+ caret_force_displayed = p_enabled;
+ cursor_set_blink_enabled(caret_blink_enabled);
+ update();
+}
+
float LineEdit::cursor_get_blink_speed() const {
return caret_blink_timer->get_wait_time();
}
@@ -1200,7 +1216,7 @@ void LineEdit::_reset_caret_blink_timer() {
void LineEdit::_toggle_draw_caret() {
draw_caret = !draw_caret;
- if (is_visible_in_tree() && has_focus() && window_has_focus) {
+ if (is_visible_in_tree() && ((has_focus() && window_has_focus) || caret_force_displayed)) {
update();
}
}
@@ -1658,6 +1674,14 @@ bool LineEdit::is_shortcut_keys_enabled() const {
return shortcut_keys_enabled;
}
+void LineEdit::set_virtual_keyboard_enabled(bool p_enable) {
+ virtual_keyboard_enabled = p_enable;
+}
+
+bool LineEdit::is_virtual_keyboard_enabled() const {
+ return virtual_keyboard_enabled;
+}
+
void LineEdit::set_selecting_enabled(bool p_enabled) {
selecting_enabled = p_enabled;
@@ -1796,6 +1820,8 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_expand_to_text_length"), &LineEdit::get_expand_to_text_length);
ClassDB::bind_method(D_METHOD("cursor_set_blink_enabled", "enabled"), &LineEdit::cursor_set_blink_enabled);
ClassDB::bind_method(D_METHOD("cursor_get_blink_enabled"), &LineEdit::cursor_get_blink_enabled);
+ ClassDB::bind_method(D_METHOD("cursor_set_force_displayed", "enabled"), &LineEdit::cursor_set_force_displayed);
+ ClassDB::bind_method(D_METHOD("cursor_get_force_displayed"), &LineEdit::cursor_get_force_displayed);
ClassDB::bind_method(D_METHOD("cursor_set_blink_speed", "blink_speed"), &LineEdit::cursor_set_blink_speed);
ClassDB::bind_method(D_METHOD("cursor_get_blink_speed"), &LineEdit::cursor_get_blink_speed);
ClassDB::bind_method(D_METHOD("set_max_length", "chars"), &LineEdit::set_max_length);
@@ -1813,6 +1839,8 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_menu"), &LineEdit::get_menu);
ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &LineEdit::set_context_menu_enabled);
ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &LineEdit::is_context_menu_enabled);
+ ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enable"), &LineEdit::set_virtual_keyboard_enabled);
+ ClassDB::bind_method(D_METHOD("is_virtual_keyboard_enabled"), &LineEdit::is_virtual_keyboard_enabled);
ClassDB::bind_method(D_METHOD("set_clear_button_enabled", "enable"), &LineEdit::set_clear_button_enabled);
ClassDB::bind_method(D_METHOD("is_clear_button_enabled"), &LineEdit::is_clear_button_enabled);
ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enable"), &LineEdit::set_shortcut_keys_enabled);
@@ -1848,6 +1876,7 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "secret_character"), "set_secret_character", "get_secret_character");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_to_text_length"), "set_expand_to_text_length", "get_expand_to_text_length");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clear_button_enabled"), "set_clear_button_enabled", "is_clear_button_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
@@ -1859,6 +1888,7 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "cursor_set_blink_enabled", "cursor_get_blink_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "cursor_set_blink_speed", "cursor_get_blink_speed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_position"), "set_cursor_position", "get_cursor_position");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "cursor_set_force_displayed", "cursor_get_force_displayed");
}
LineEdit::LineEdit() {
@@ -1888,6 +1918,7 @@ LineEdit::LineEdit() {
draw_caret = true;
caret_blink_enabled = false;
+ caret_force_displayed = false;
caret_blink_timer = memnew(Timer);
add_child(caret_blink_timer);
caret_blink_timer->set_wait_time(0.65);
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index e6c964c906..d6cc1f1f11 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -90,6 +90,8 @@ private:
bool shortcut_keys_enabled;
+ bool virtual_keyboard_enabled = true;
+
Ref<Texture2D> right_icon;
struct Selection {
@@ -134,6 +136,7 @@ private:
void update_placeholder_width();
bool caret_blink_enabled;
+ bool caret_force_displayed;
bool draw_caret;
bool window_has_focus;
@@ -201,6 +204,9 @@ public:
float cursor_get_blink_speed() const;
void cursor_set_blink_speed(const float p_speed);
+ bool cursor_get_force_displayed() const;
+ void cursor_set_force_displayed(const bool p_enabled);
+
void copy_text();
void cut_text();
void paste_text();
@@ -227,6 +233,9 @@ public:
void set_shortcut_keys_enabled(bool p_enabled);
bool is_shortcut_keys_enabled() const;
+ void set_virtual_keyboard_enabled(bool p_enable);
+ bool is_virtual_keyboard_enabled() const;
+
void set_selecting_enabled(bool p_enabled);
bool is_selecting_enabled() const;
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 8d7bccf814..572be8d901 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -1020,7 +1020,8 @@ void RichTextLabel::_notification(int p_what) {
visible_line_count = 0;
while (y < size.height && from_line < main->lines.size()) {
- visible_line_count += _process_line(main, text_rect.get_position(), y, text_rect.get_size().width - scroll_w, from_line, PROCESS_DRAW, base_font, base_color, font_color_shadow, use_outline, shadow_ofs, Point2i(), nullptr, nullptr, nullptr, total_chars);
+ visible_line_count++;
+ _process_line(main, text_rect.get_position(), y, text_rect.get_size().width - scroll_w, from_line, PROCESS_DRAW, base_font, base_color, font_color_shadow, use_outline, shadow_ofs, Point2i(), nullptr, nullptr, nullptr, total_chars);
total_chars += main->lines[from_line].char_count;
from_line++;
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index 4db6ca2949..0e9ef71892 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -522,7 +522,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) {
drag_node_accum = Vector2();
last_drag_node_accum = Vector2();
drag_node_from = Vector2(orientation == HORIZONTAL ? get_value() : 0, orientation == VERTICAL ? get_value() : 0);
- drag_node_touching = !DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
+ drag_node_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
drag_node_touching_deaccel = false;
time_since_motion = 0;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 47761d724e..eeb7f9430d 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -3419,6 +3419,8 @@ void Tree::scroll_to_item(TreeItem *p_item) {
TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards) {
TreeItem *from = p_at;
+ TreeItem *loop = nullptr; // Safe-guard against infinite loop.
+
while (p_at) {
for (int i = 0; i < columns.size(); i++) {
if (p_at->get_text(i).findn(p_find) == 0 && (!p_selectable || p_at->is_selectable(i))) {
@@ -3438,6 +3440,12 @@ TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_c
if ((p_at) == from) {
break;
}
+
+ if (!loop) {
+ loop = p_at;
+ } else if (loop == p_at) {
+ break;
+ }
}
return nullptr;
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 3cbc64c075..db80dbe814 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -372,7 +372,7 @@ void register_scene_types() {
OS::get_singleton()->yield(); //may take time to init
- AcceptDialog::set_swap_cancel_ok(GLOBAL_DEF("gui/common/swap_cancel_ok", bool(DisplayServer::get_singleton()->get_swap_cancel_ok())));
+ AcceptDialog::set_swap_cancel_ok(GLOBAL_DEF_NOVAL("gui/common/swap_cancel_ok", bool(DisplayServer::get_singleton()->get_swap_cancel_ok())));
#endif
/* REGISTER 3D */
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index 479d97aadc..b8edd70712 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -752,7 +752,9 @@ int Animation::_insert(float p_time, T &p_keys, const V &p_value) {
while (true) {
// Condition for replacement.
if (idx > 0 && Math::is_equal_approx(p_keys[idx - 1].time, p_time)) {
+ float transition = p_keys[idx - 1].transition;
p_keys.write[idx - 1] = p_value;
+ p_keys.write[idx - 1].transition = transition;
return idx - 1;
// Condition for insert.
diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp
index 3d99556a10..99f87dd6ed 100644
--- a/scene/resources/dynamic_font.cpp
+++ b/scene/resources/dynamic_font.cpp
@@ -290,6 +290,21 @@ Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const V
return ret;
}
+String DynamicFontAtSize::get_available_chars() const {
+ String chars;
+
+ FT_UInt gindex;
+ FT_ULong charcode = FT_Get_First_Char(face, &gindex);
+ while (gindex != 0) {
+ if (charcode != 0) {
+ chars += CharType(charcode);
+ }
+ charcode = FT_Get_Next_Char(face, charcode, &gindex);
+ }
+
+ return chars;
+}
+
float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks, bool p_advance_only, bool p_outline) const {
if (!valid) {
return 0;
@@ -849,6 +864,25 @@ Size2 DynamicFont::get_char_size(CharType p_char, CharType p_next) const {
return ret;
}
+String DynamicFont::get_available_chars() const {
+ if (!data_at_size.is_valid()) {
+ return "";
+ }
+
+ String chars = data_at_size->get_available_chars();
+
+ for (int i = 0; i < fallback_data_at_size.size(); i++) {
+ String fallback_chars = fallback_data_at_size[i]->get_available_chars();
+ for (int j = 0; j < fallback_chars.length(); j++) {
+ if (chars.find_char(fallback_chars[j]) == -1) {
+ chars += fallback_chars[j];
+ }
+ }
+ }
+
+ return chars;
+}
+
bool DynamicFont::is_distance_field_hint() const {
return false;
}
@@ -964,6 +998,8 @@ void DynamicFont::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_font_data", "data"), &DynamicFont::set_font_data);
ClassDB::bind_method(D_METHOD("get_font_data"), &DynamicFont::get_font_data);
+ ClassDB::bind_method(D_METHOD("get_available_chars"), &DynamicFont::get_available_chars);
+
ClassDB::bind_method(D_METHOD("set_size", "data"), &DynamicFont::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &DynamicFont::get_size);
diff --git a/scene/resources/dynamic_font.h b/scene/resources/dynamic_font.h
index 0afad428b3..e8637e7e34 100644
--- a/scene/resources/dynamic_font.h
+++ b/scene/resources/dynamic_font.h
@@ -189,6 +189,7 @@ public:
float get_underline_thickness() const;
Size2 get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks) const;
+ String get_available_chars() const;
float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks, bool p_advance_only = false, bool p_outline = false) const;
@@ -277,6 +278,7 @@ public:
virtual float get_underline_thickness() const override;
virtual Size2 get_char_size(CharType p_char, CharType p_next = 0) const override;
+ String get_available_chars() const;
virtual bool is_distance_field_hint() const override;
diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp
index 331cffed5d..5681613c04 100644
--- a/scene/resources/texture.cpp
+++ b/scene/resources/texture.cpp
@@ -2079,7 +2079,7 @@ Error StreamTextureLayered::_load_data(const String &p_path, Vector<Ref<Image>>
uint32_t df = f->get_32(); //data format
mipmap_limit = int(f->get_32());
- //reserverd
+ //reserved
f->get_32();
f->get_32();
f->get_32();
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 8236f9a9e3..792e1ac2d7 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -63,6 +63,21 @@ bool VisualShaderNode::is_port_separator(int p_index) const {
return false;
}
+bool VisualShaderNode::is_output_port_connected(int p_port) const {
+ if (connected_output_ports.has(p_port)) {
+ return connected_output_ports[p_port];
+ }
+ return false;
+}
+
+void VisualShaderNode::set_output_port_connected(int p_port, bool p_connected) {
+ connected_output_ports[p_port] = p_connected;
+}
+
+bool VisualShaderNode::is_code_generated() const {
+ return true;
+}
+
Vector<VisualShader::DefaultTextureParam> VisualShaderNode::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const {
return Vector<VisualShader::DefaultTextureParam>();
}
@@ -526,6 +541,7 @@ void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from
c.to_port = p_to_port;
g->connections.push_back(c);
g->nodes[p_to_node].prev_connected_nodes.push_back(p_from_node);
+ g->nodes[p_from_node].node->set_output_port_connected(p_from_port, true);
_queue_update();
}
@@ -557,6 +573,7 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port,
c.to_port = p_to_port;
g->connections.push_back(c);
g->nodes[p_to_node].prev_connected_nodes.push_back(p_from_node);
+ g->nodes[p_from_node].node->set_output_port_connected(p_from_port, true);
_queue_update();
return OK;
@@ -570,6 +587,7 @@ void VisualShader::disconnect_nodes(Type p_type, int p_from_node, int p_from_por
if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) {
g->connections.erase(E);
g->nodes[p_to_node].prev_connected_nodes.erase(p_from_node);
+ g->nodes[p_from_node].node->set_output_port_connected(p_from_port, false);
_queue_update();
return;
}
@@ -1105,6 +1123,35 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui
// then this node
+ Vector<VisualShader::DefaultTextureParam> params = vsnode->get_default_texture_parameters(type, node);
+ for (int i = 0; i < params.size(); i++) {
+ def_tex_params.push_back(params[i]);
+ }
+
+ Ref<VisualShaderNodeInput> input = vsnode;
+ bool skip_global = input.is_valid() && for_preview;
+
+ if (!skip_global) {
+ global_code += vsnode->generate_global(get_mode(), type, node);
+
+ String class_name = vsnode->get_class_name();
+ if (class_name == "VisualShaderNodeCustom") {
+ class_name = vsnode->get_script_instance()->get_script()->get_path();
+ }
+ if (!r_classes.has(class_name)) {
+ global_code_per_node += vsnode->generate_global_per_node(get_mode(), type, node);
+ for (int i = 0; i < TYPE_MAX; i++) {
+ global_code_per_func[Type(i)] += vsnode->generate_global_per_func(get_mode(), Type(i), node);
+ }
+ r_classes.insert(class_name);
+ }
+ }
+
+ if (!vsnode->is_code_generated()) { // just generate globals and ignore locals
+ processed.insert(node);
+ return OK;
+ }
+
code += "// " + vsnode->get_caption() + ":" + itos(node) + "\n";
Vector<String> input_vars;
@@ -1255,30 +1302,6 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui
}
}
- Vector<VisualShader::DefaultTextureParam> params = vsnode->get_default_texture_parameters(type, node);
- for (int i = 0; i < params.size(); i++) {
- def_tex_params.push_back(params[i]);
- }
-
- Ref<VisualShaderNodeInput> input = vsnode;
- bool skip_global = input.is_valid() && for_preview;
-
- if (!skip_global) {
- global_code += vsnode->generate_global(get_mode(), type, node);
-
- String class_name = vsnode->get_class_name();
- if (class_name == "VisualShaderNodeCustom") {
- class_name = vsnode->get_script_instance()->get_script()->get_path();
- }
- if (!r_classes.has(class_name)) {
- global_code_per_node += vsnode->generate_global_per_node(get_mode(), type, node);
- for (int i = 0; i < TYPE_MAX; i++) {
- global_code_per_func[Type(i)] += vsnode->generate_global_per_func(get_mode(), Type(i), node);
- }
- r_classes.insert(class_name);
- }
- }
-
code += vsnode->generate_code(get_mode(), type, node, inputs, outputs, for_preview);
code += "\n"; //
diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h
index dbb8d1d28c..cad567d32f 100644
--- a/scene/resources/visual_shader.h
+++ b/scene/resources/visual_shader.h
@@ -182,6 +182,7 @@ class VisualShaderNode : public Resource {
int port_preview;
Map<int, Variant> default_input_values;
+ Map<int, bool> connected_output_ports;
protected:
bool simple_decl;
@@ -222,6 +223,11 @@ public:
virtual bool is_port_separator(int p_index) const;
+ bool is_output_port_connected(int p_port) const;
+ void set_output_port_connected(int p_port, bool p_connected);
+
+ virtual bool is_code_generated() const;
+
virtual Vector<StringName> get_editable_properties() const;
virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const;
diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp
index 88f5287831..4cf382a933 100644
--- a/scene/resources/visual_shader_nodes.cpp
+++ b/scene/resources/visual_shader_nodes.cpp
@@ -3915,6 +3915,10 @@ String VisualShaderNodeTextureUniform::generate_global(Shader::Mode p_mode, Visu
return code;
}
+bool VisualShaderNodeTextureUniform::is_code_generated() const {
+ return is_output_port_connected(0) || is_output_port_connected(1); // rgb or alpha
+}
+
String VisualShaderNodeTextureUniform::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
String id = get_uniform_name();
String code = "\t{\n";
diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h
index 13a132c60e..b82d5c65c9 100644
--- a/scene/resources/visual_shader_nodes.h
+++ b/scene/resources/visual_shader_nodes.h
@@ -1715,6 +1715,8 @@ public:
virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override;
virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty
+ virtual bool is_code_generated() const override;
+
Vector<StringName> get_editable_properties() const override;
void set_texture_type(TextureType p_type);
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 65db0de001..f46e56cd5a 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -223,7 +223,7 @@ void DisplayServer::virtual_keyboard_hide() {
// returns height of the currently shown keyboard (0 if keyboard is hidden)
int DisplayServer::virtual_keyboard_get_height() const {
- ERR_FAIL_V_MSG(0, "Virtual keyboad not supported by this display server.");
+ ERR_FAIL_V_MSG(0, "Virtual keyboard not supported by this display server.");
}
void DisplayServer::cursor_set_shape(CursorShape p_shape) {
diff --git a/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp
index 3f594ab264..e620ad954d 100644
--- a/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp
+++ b/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp
@@ -508,7 +508,7 @@ void RasterizerEffectsRD::screen_space_reflection(RID p_diffuse, RID p_normal_ro
}
if (p_roughness_quality != RS::ENV_SSR_ROUGNESS_QUALITY_DISABLED) {
- //blurr
+ //blur
RD::get_singleton()->compute_list_add_barrier(compute_list);
diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp
index 890ada019f..873f74e3be 100644
--- a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp
+++ b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp
@@ -33,69 +33,6 @@
#include "servers/rendering/rendering_device.h"
#include "servers/rendering/rendering_server_raster.h"
-static _FORCE_INLINE_ void store_transform(const Transform &p_mtx, float *p_array) {
- p_array[0] = p_mtx.basis.elements[0][0];
- p_array[1] = p_mtx.basis.elements[1][0];
- p_array[2] = p_mtx.basis.elements[2][0];
- p_array[3] = 0;
- p_array[4] = p_mtx.basis.elements[0][1];
- p_array[5] = p_mtx.basis.elements[1][1];
- p_array[6] = p_mtx.basis.elements[2][1];
- p_array[7] = 0;
- p_array[8] = p_mtx.basis.elements[0][2];
- p_array[9] = p_mtx.basis.elements[1][2];
- p_array[10] = p_mtx.basis.elements[2][2];
- p_array[11] = 0;
- p_array[12] = p_mtx.origin.x;
- p_array[13] = p_mtx.origin.y;
- p_array[14] = p_mtx.origin.z;
- p_array[15] = 1;
-}
-
-static _FORCE_INLINE_ void store_basis_3x4(const Basis &p_mtx, float *p_array) {
- p_array[0] = p_mtx.elements[0][0];
- p_array[1] = p_mtx.elements[1][0];
- p_array[2] = p_mtx.elements[2][0];
- p_array[3] = 0;
- p_array[4] = p_mtx.elements[0][1];
- p_array[5] = p_mtx.elements[1][1];
- p_array[6] = p_mtx.elements[2][1];
- p_array[7] = 0;
- p_array[8] = p_mtx.elements[0][2];
- p_array[9] = p_mtx.elements[1][2];
- p_array[10] = p_mtx.elements[2][2];
- p_array[11] = 0;
-}
-
-static _FORCE_INLINE_ void store_transform_3x3(const Basis &p_mtx, float *p_array) {
- p_array[0] = p_mtx.elements[0][0];
- p_array[1] = p_mtx.elements[1][0];
- p_array[2] = p_mtx.elements[2][0];
- p_array[3] = 0;
- p_array[4] = p_mtx.elements[0][1];
- p_array[5] = p_mtx.elements[1][1];
- p_array[6] = p_mtx.elements[2][1];
- p_array[7] = 0;
- p_array[8] = p_mtx.elements[0][2];
- p_array[9] = p_mtx.elements[1][2];
- p_array[10] = p_mtx.elements[2][2];
- p_array[11] = 0;
-}
-
-static _FORCE_INLINE_ void store_camera(const CameraMatrix &p_mtx, float *p_array) {
- for (int i = 0; i < 4; i++) {
- for (int j = 0; j < 4; j++) {
- p_array[i * 4 + j] = p_mtx.matrix[i][j];
- }
- }
-}
-
-static _FORCE_INLINE_ void store_soft_shadow_kernel(const float *p_kernel, float *p_array) {
- for (int i = 0; i < 128; i++) {
- p_array[i] = p_kernel[i];
- }
-}
-
/* SCENE SHADER */
void RasterizerSceneHighEndRD::ShaderData::set_code(const String &p_code) {
//compile
@@ -845,8 +782,8 @@ void RasterizerSceneHighEndRD::_fill_instances(RenderList::Element **p_elements,
for (int i = 0; i < p_element_count; i++) {
const RenderList::Element *e = p_elements[i];
InstanceData &id = scene_state.instances[i];
- store_transform(e->instance->transform, id.transform);
- store_transform(Transform(e->instance->transform.basis.inverse().transposed()), id.normal_transform);
+ RasterizerStorageRD::store_transform(e->instance->transform, id.transform);
+ RasterizerStorageRD::store_transform(Transform(e->instance->transform.basis.inverse().transposed()), id.normal_transform);
id.flags = 0;
id.mask = e->instance->layer_mask;
id.instance_uniforms_ofs = e->instance->instance_allocated_shader_parameters_offset >= 0 ? e->instance->instance_allocated_shader_parameters_offset : 0;
@@ -1171,20 +1108,20 @@ void RasterizerSceneHighEndRD::_setup_environment(RID p_environment, RID p_rende
CameraMatrix projection = correction * p_cam_projection;
//store camera into ubo
- store_camera(projection, scene_state.ubo.projection_matrix);
- store_camera(projection.inverse(), scene_state.ubo.inv_projection_matrix);
- store_transform(p_cam_transform, scene_state.ubo.camera_matrix);
- store_transform(p_cam_transform.affine_inverse(), scene_state.ubo.inv_camera_matrix);
+ RasterizerStorageRD::store_camera(projection, scene_state.ubo.projection_matrix);
+ RasterizerStorageRD::store_camera(projection.inverse(), scene_state.ubo.inv_projection_matrix);
+ RasterizerStorageRD::store_transform(p_cam_transform, scene_state.ubo.camera_matrix);
+ RasterizerStorageRD::store_transform(p_cam_transform.affine_inverse(), scene_state.ubo.inv_camera_matrix);
scene_state.ubo.z_far = p_zfar;
scene_state.ubo.z_near = p_znear;
scene_state.ubo.pancake_shadows = p_pancake_shadows;
- store_soft_shadow_kernel(directional_penumbra_shadow_kernel_get(), scene_state.ubo.directional_penumbra_shadow_kernel);
- store_soft_shadow_kernel(directional_soft_shadow_kernel_get(), scene_state.ubo.directional_soft_shadow_kernel);
- store_soft_shadow_kernel(penumbra_shadow_kernel_get(), scene_state.ubo.penumbra_shadow_kernel);
- store_soft_shadow_kernel(soft_shadow_kernel_get(), scene_state.ubo.soft_shadow_kernel);
+ RasterizerStorageRD::store_soft_shadow_kernel(directional_penumbra_shadow_kernel_get(), scene_state.ubo.directional_penumbra_shadow_kernel);
+ RasterizerStorageRD::store_soft_shadow_kernel(directional_soft_shadow_kernel_get(), scene_state.ubo.directional_soft_shadow_kernel);
+ RasterizerStorageRD::store_soft_shadow_kernel(penumbra_shadow_kernel_get(), scene_state.ubo.penumbra_shadow_kernel);
+ RasterizerStorageRD::store_soft_shadow_kernel(soft_shadow_kernel_get(), scene_state.ubo.soft_shadow_kernel);
scene_state.ubo.directional_penumbra_shadow_samples = directional_penumbra_shadow_samples_get();
scene_state.ubo.directional_soft_shadow_samples = directional_soft_shadow_samples_get();
@@ -1310,7 +1247,7 @@ void RasterizerSceneHighEndRD::_setup_environment(RID p_environment, RID p_rende
Basis sky_transform = environment_get_sky_orientation(p_environment);
sky_transform = sky_transform.inverse() * p_cam_transform.basis;
- store_transform_3x3(sky_transform, scene_state.ubo.radiance_inverse_xform);
+ RasterizerStorageRD::store_transform_3x3(sky_transform, scene_state.ubo.radiance_inverse_xform);
scene_state.ubo.use_ambient_cubemap = (ambient_src == RS::ENV_AMBIENT_SOURCE_BG && env_bg == RS::ENV_BG_SKY) || ambient_src == RS::ENV_AMBIENT_SOURCE_SKY;
scene_state.ubo.use_ambient_light = scene_state.ubo.use_ambient_cubemap || ambient_src == RS::ENV_AMBIENT_SOURCE_COLOR;
@@ -1582,66 +1519,6 @@ void RasterizerSceneHighEndRD::_fill_render_list(InstanceBase **p_cull_result, i
}
}
-void RasterizerSceneHighEndRD::_setup_reflections(RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, const Transform &p_camera_inverse_transform, RID p_environment) {
- for (int i = 0; i < p_reflection_probe_cull_count; i++) {
- RID rpi = p_reflection_probe_cull_result[i];
-
- if (i >= (int)scene_state.max_reflections) {
- reflection_probe_instance_set_render_index(rpi, 0); //invalid, but something needs to be set
- continue;
- }
-
- reflection_probe_instance_set_render_index(rpi, i);
-
- RID base_probe = reflection_probe_instance_get_probe(rpi);
-
- ReflectionData &reflection_ubo = scene_state.reflections[i];
-
- Vector3 extents = storage->reflection_probe_get_extents(base_probe);
-
- reflection_ubo.box_extents[0] = extents.x;
- reflection_ubo.box_extents[1] = extents.y;
- reflection_ubo.box_extents[2] = extents.z;
- reflection_ubo.index = reflection_probe_instance_get_atlas_index(rpi);
-
- Vector3 origin_offset = storage->reflection_probe_get_origin_offset(base_probe);
-
- reflection_ubo.box_offset[0] = origin_offset.x;
- reflection_ubo.box_offset[1] = origin_offset.y;
- reflection_ubo.box_offset[2] = origin_offset.z;
- reflection_ubo.mask = storage->reflection_probe_get_cull_mask(base_probe);
-
- float intensity = storage->reflection_probe_get_intensity(base_probe);
- bool interior = storage->reflection_probe_is_interior(base_probe);
- bool box_projection = storage->reflection_probe_is_box_projection(base_probe);
-
- reflection_ubo.params[0] = intensity;
- reflection_ubo.params[1] = 0;
- reflection_ubo.params[2] = interior ? 1.0 : 0.0;
- reflection_ubo.params[3] = box_projection ? 1.0 : 0.0;
-
- Color ambient_linear = storage->reflection_probe_get_ambient_color(base_probe).to_linear();
- float interior_ambient_energy = storage->reflection_probe_get_ambient_color_energy(base_probe);
- uint32_t ambient_mode = storage->reflection_probe_get_ambient_mode(base_probe);
- reflection_ubo.ambient[0] = ambient_linear.r * interior_ambient_energy;
- reflection_ubo.ambient[1] = ambient_linear.g * interior_ambient_energy;
- reflection_ubo.ambient[2] = ambient_linear.b * interior_ambient_energy;
- reflection_ubo.ambient_mode = ambient_mode;
-
- Transform transform = reflection_probe_instance_get_transform(rpi);
- Transform proj = (p_camera_inverse_transform * transform).inverse();
- store_transform(proj, reflection_ubo.local_matrix);
-
- cluster_builder.add_reflection_probe(transform, extents);
-
- reflection_probe_instance_set_render_pass(rpi, render_pass);
- }
-
- if (p_reflection_probe_cull_count) {
- RD::get_singleton()->buffer_update(scene_state.reflection_buffer, 0, MIN(scene_state.max_reflections, (unsigned int)p_reflection_probe_cull_count) * sizeof(ReflectionData), scene_state.reflections, true);
- }
-}
-
void RasterizerSceneHighEndRD::_setup_lightmaps(InstanceBase **p_lightmap_cull_result, int p_lightmap_cull_count, const Transform &p_cam_transform) {
uint32_t lightmaps_used = 0;
for (int i = 0; i < p_lightmap_cull_count; i++) {
@@ -1652,7 +1529,7 @@ void RasterizerSceneHighEndRD::_setup_lightmaps(InstanceBase **p_lightmap_cull_r
InstanceBase *lm = p_lightmap_cull_result[i];
Basis to_lm = lm->transform.basis.inverse() * p_cam_transform.basis;
to_lm = to_lm.inverse().transposed(); //will transform normals
- store_transform_3x3(to_lm, scene_state.lightmaps[i].normal_xform);
+ RasterizerStorageRD::store_transform_3x3(to_lm, scene_state.lightmaps[i].normal_xform);
lm->lightmap_cull_index = i;
lightmaps_used++;
}
@@ -1661,480 +1538,7 @@ void RasterizerSceneHighEndRD::_setup_lightmaps(InstanceBase **p_lightmap_cull_r
}
}
-void RasterizerSceneHighEndRD::_setup_lights(RID *p_light_cull_result, int p_light_cull_count, const Transform &p_camera_inverse_transform, RID p_shadow_atlas, bool p_using_shadows) {
- uint32_t light_count = 0;
- scene_state.ubo.directional_light_count = 0;
- sky_scene_state.directional_light_count = 0;
-
- for (int i = 0; i < p_light_cull_count; i++) {
- RID li = p_light_cull_result[i];
- RID base = light_instance_get_base_light(li);
-
- ERR_CONTINUE(base.is_null());
-
- RS::LightType type = storage->light_get_type(base);
- switch (type) {
- case RS::LIGHT_DIRECTIONAL: {
- if (scene_state.ubo.directional_light_count >= scene_state.max_directional_lights) {
- continue;
- }
-
- DirectionalLightData &light_data = scene_state.directional_lights[scene_state.ubo.directional_light_count];
-
- Transform light_transform = light_instance_get_base_transform(li);
-
- Vector3 direction = p_camera_inverse_transform.basis.xform(light_transform.basis.xform(Vector3(0, 0, 1))).normalized();
-
- light_data.direction[0] = direction.x;
- light_data.direction[1] = direction.y;
- light_data.direction[2] = direction.z;
-
- float sign = storage->light_is_negative(base) ? -1 : 1;
-
- light_data.energy = sign * storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY) * Math_PI;
-
- Color linear_col = storage->light_get_color(base).to_linear();
- light_data.color[0] = linear_col.r;
- light_data.color[1] = linear_col.g;
- light_data.color[2] = linear_col.b;
-
- light_data.specular = storage->light_get_param(base, RS::LIGHT_PARAM_SPECULAR);
- light_data.mask = storage->light_get_cull_mask(base);
-
- float size = storage->light_get_param(base, RS::LIGHT_PARAM_SIZE);
-
- light_data.size = 1.0 - Math::cos(Math::deg2rad(size)); //angle to cosine offset
-
- Color shadow_col = storage->light_get_shadow_color(base).to_linear();
-
- if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_PSSM_SPLITS) {
- light_data.shadow_color1[0] = 1.0;
- light_data.shadow_color1[1] = 0.0;
- light_data.shadow_color1[2] = 0.0;
- light_data.shadow_color1[3] = 1.0;
- light_data.shadow_color2[0] = 0.0;
- light_data.shadow_color2[1] = 1.0;
- light_data.shadow_color2[2] = 0.0;
- light_data.shadow_color2[3] = 1.0;
- light_data.shadow_color3[0] = 0.0;
- light_data.shadow_color3[1] = 0.0;
- light_data.shadow_color3[2] = 1.0;
- light_data.shadow_color3[3] = 1.0;
- light_data.shadow_color4[0] = 1.0;
- light_data.shadow_color4[1] = 1.0;
- light_data.shadow_color4[2] = 0.0;
- light_data.shadow_color4[3] = 1.0;
-
- } else {
- light_data.shadow_color1[0] = shadow_col.r;
- light_data.shadow_color1[1] = shadow_col.g;
- light_data.shadow_color1[2] = shadow_col.b;
- light_data.shadow_color1[3] = 1.0;
- light_data.shadow_color2[0] = shadow_col.r;
- light_data.shadow_color2[1] = shadow_col.g;
- light_data.shadow_color2[2] = shadow_col.b;
- light_data.shadow_color2[3] = 1.0;
- light_data.shadow_color3[0] = shadow_col.r;
- light_data.shadow_color3[1] = shadow_col.g;
- light_data.shadow_color3[2] = shadow_col.b;
- light_data.shadow_color3[3] = 1.0;
- light_data.shadow_color4[0] = shadow_col.r;
- light_data.shadow_color4[1] = shadow_col.g;
- light_data.shadow_color4[2] = shadow_col.b;
- light_data.shadow_color4[3] = 1.0;
- }
-
- light_data.shadow_enabled = p_using_shadows && storage->light_has_shadow(base);
-
- float angular_diameter = storage->light_get_param(base, RS::LIGHT_PARAM_SIZE);
- if (angular_diameter > 0.0) {
- // I know tan(0) is 0, but let's not risk it with numerical precision.
- // technically this will keep expanding until reaching the sun, but all we care
- // is expand until we reach the radius of the near plane (there can't be more occluders than that)
- angular_diameter = Math::tan(Math::deg2rad(angular_diameter));
- } else {
- angular_diameter = 0.0;
- }
-
- if (light_data.shadow_enabled) {
- RS::LightDirectionalShadowMode smode = storage->light_directional_get_shadow_mode(base);
-
- int limit = smode == RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL ? 0 : (smode == RS::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_2_SPLITS ? 1 : 3);
- light_data.blend_splits = storage->light_directional_get_blend_splits(base);
- for (int j = 0; j < 4; j++) {
- Rect2 atlas_rect = light_instance_get_directional_shadow_atlas_rect(li, j);
- CameraMatrix matrix = light_instance_get_shadow_camera(li, j);
- float split = light_instance_get_directional_shadow_split(li, MIN(limit, j));
-
- CameraMatrix bias;
- bias.set_light_bias();
- CameraMatrix rectm;
- rectm.set_light_atlas_rect(atlas_rect);
-
- Transform modelview = (p_camera_inverse_transform * light_instance_get_shadow_transform(li, j)).inverse();
-
- CameraMatrix shadow_mtx = rectm * bias * matrix * modelview;
- light_data.shadow_split_offsets[j] = split;
- float bias_scale = light_instance_get_shadow_bias_scale(li, j);
- light_data.shadow_bias[j] = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS) * bias_scale;
- light_data.shadow_normal_bias[j] = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS) * light_instance_get_directional_shadow_texel_size(li, j);
- light_data.shadow_transmittance_bias[j] = storage->light_get_transmittance_bias(base) * bias_scale;
- light_data.shadow_transmittance_z_scale[j] = light_instance_get_shadow_range(li, j);
- light_data.shadow_range_begin[j] = light_instance_get_shadow_range_begin(li, j);
- store_camera(shadow_mtx, light_data.shadow_matrices[j]);
-
- Vector2 uv_scale = light_instance_get_shadow_uv_scale(li, j);
- uv_scale *= atlas_rect.size; //adapt to atlas size
- switch (j) {
- case 0: {
- light_data.uv_scale1[0] = uv_scale.x;
- light_data.uv_scale1[1] = uv_scale.y;
- } break;
- case 1: {
- light_data.uv_scale2[0] = uv_scale.x;
- light_data.uv_scale2[1] = uv_scale.y;
- } break;
- case 2: {
- light_data.uv_scale3[0] = uv_scale.x;
- light_data.uv_scale3[1] = uv_scale.y;
- } break;
- case 3: {
- light_data.uv_scale4[0] = uv_scale.x;
- light_data.uv_scale4[1] = uv_scale.y;
- } break;
- }
- }
-
- float fade_start = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_FADE_START);
- light_data.fade_from = -light_data.shadow_split_offsets[3] * MIN(fade_start, 0.999); //using 1.0 would break smoothstep
- light_data.fade_to = -light_data.shadow_split_offsets[3];
-
- light_data.soft_shadow_scale = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BLUR);
- light_data.softshadow_angle = angular_diameter;
-
- if (angular_diameter <= 0.0) {
- light_data.soft_shadow_scale *= directional_shadow_quality_radius_get(); // Only use quality radius for PCF
- }
- }
-
- // Copy to SkyDirectionalLightData
- if (sky_scene_state.directional_light_count < sky_scene_state.max_directional_lights) {
- SkyDirectionalLightData &sky_light_data = sky_scene_state.directional_lights[sky_scene_state.directional_light_count];
-
- Vector3 world_direction = light_transform.basis.xform(Vector3(0, 0, 1)).normalized();
-
- sky_light_data.direction[0] = world_direction.x;
- sky_light_data.direction[1] = world_direction.y;
- sky_light_data.direction[2] = -world_direction.z;
-
- sky_light_data.energy = light_data.energy / Math_PI;
-
- sky_light_data.color[0] = light_data.color[0];
- sky_light_data.color[1] = light_data.color[1];
- sky_light_data.color[2] = light_data.color[2];
-
- sky_light_data.enabled = true;
- sky_light_data.size = angular_diameter;
- sky_scene_state.directional_light_count++;
- }
-
- scene_state.ubo.directional_light_count++;
- } break;
- case RS::LIGHT_SPOT:
- case RS::LIGHT_OMNI: {
- if (light_count >= scene_state.max_lights) {
- continue;
- }
-
- Transform light_transform = light_instance_get_base_transform(li);
-
- LightData &light_data = scene_state.lights[light_count];
-
- float sign = storage->light_is_negative(base) ? -1 : 1;
- Color linear_col = storage->light_get_color(base).to_linear();
-
- light_data.attenuation_energy[0] = Math::make_half_float(storage->light_get_param(base, RS::LIGHT_PARAM_ATTENUATION));
- light_data.attenuation_energy[1] = Math::make_half_float(sign * storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY) * Math_PI);
-
- light_data.color_specular[0] = MIN(uint32_t(linear_col.r * 255), 255);
- light_data.color_specular[1] = MIN(uint32_t(linear_col.g * 255), 255);
- light_data.color_specular[2] = MIN(uint32_t(linear_col.b * 255), 255);
- light_data.color_specular[3] = MIN(uint32_t(storage->light_get_param(base, RS::LIGHT_PARAM_SPECULAR) * 255), 255);
-
- float radius = MAX(0.001, storage->light_get_param(base, RS::LIGHT_PARAM_RANGE));
- light_data.inv_radius = 1.0 / radius;
-
- Vector3 pos = p_camera_inverse_transform.xform(light_transform.origin);
-
- light_data.position[0] = pos.x;
- light_data.position[1] = pos.y;
- light_data.position[2] = pos.z;
-
- Vector3 direction = p_camera_inverse_transform.basis.xform(light_transform.basis.xform(Vector3(0, 0, -1))).normalized();
-
- light_data.direction[0] = direction.x;
- light_data.direction[1] = direction.y;
- light_data.direction[2] = direction.z;
-
- float size = storage->light_get_param(base, RS::LIGHT_PARAM_SIZE);
-
- light_data.size = size;
-
- light_data.cone_attenuation_angle[0] = Math::make_half_float(storage->light_get_param(base, RS::LIGHT_PARAM_SPOT_ATTENUATION));
- float spot_angle = storage->light_get_param(base, RS::LIGHT_PARAM_SPOT_ANGLE);
- light_data.cone_attenuation_angle[1] = Math::make_half_float(Math::cos(Math::deg2rad(spot_angle)));
-
- light_data.mask = storage->light_get_cull_mask(base);
-
- light_data.atlas_rect[0] = 0;
- light_data.atlas_rect[1] = 0;
- light_data.atlas_rect[2] = 0;
- light_data.atlas_rect[3] = 0;
-
- RID projector = storage->light_get_projector(base);
-
- if (projector.is_valid()) {
- Rect2 rect = storage->decal_atlas_get_texture_rect(projector);
-
- if (type == RS::LIGHT_SPOT) {
- light_data.projector_rect[0] = rect.position.x;
- light_data.projector_rect[1] = rect.position.y + rect.size.height; //flip because shadow is flipped
- light_data.projector_rect[2] = rect.size.width;
- light_data.projector_rect[3] = -rect.size.height;
- } else {
- light_data.projector_rect[0] = rect.position.x;
- light_data.projector_rect[1] = rect.position.y;
- light_data.projector_rect[2] = rect.size.width;
- light_data.projector_rect[3] = rect.size.height * 0.5; //used by dp, so needs to be half
- }
- } else {
- light_data.projector_rect[0] = 0;
- light_data.projector_rect[1] = 0;
- light_data.projector_rect[2] = 0;
- light_data.projector_rect[3] = 0;
- }
-
- if (p_using_shadows && p_shadow_atlas.is_valid() && shadow_atlas_owns_light_instance(p_shadow_atlas, li)) {
- // fill in the shadow information
-
- Color shadow_color = storage->light_get_shadow_color(base);
-
- light_data.shadow_color_enabled[0] = MIN(uint32_t(shadow_color.r * 255), 255);
- light_data.shadow_color_enabled[1] = MIN(uint32_t(shadow_color.g * 255), 255);
- light_data.shadow_color_enabled[2] = MIN(uint32_t(shadow_color.b * 255), 255);
- light_data.shadow_color_enabled[3] = 255;
-
- if (type == RS::LIGHT_SPOT) {
- light_data.shadow_bias = (storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS) * radius / 10.0);
- float shadow_texel_size = Math::tan(Math::deg2rad(spot_angle)) * radius * 2.0;
- shadow_texel_size *= light_instance_get_shadow_texel_size(li, p_shadow_atlas);
-
- light_data.shadow_normal_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS) * shadow_texel_size;
-
- } else { //omni
- light_data.shadow_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS) * radius / 10.0;
- float shadow_texel_size = light_instance_get_shadow_texel_size(li, p_shadow_atlas);
- light_data.shadow_normal_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS) * shadow_texel_size * 2.0; // applied in -1 .. 1 space
- }
-
- light_data.transmittance_bias = storage->light_get_transmittance_bias(base);
-
- Rect2 rect = light_instance_get_shadow_atlas_rect(li, p_shadow_atlas);
-
- light_data.atlas_rect[0] = rect.position.x;
- light_data.atlas_rect[1] = rect.position.y;
- light_data.atlas_rect[2] = rect.size.width;
- light_data.atlas_rect[3] = rect.size.height;
-
- light_data.soft_shadow_scale = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BLUR);
-
- if (type == RS::LIGHT_OMNI) {
- light_data.atlas_rect[3] *= 0.5; //one paraboloid on top of another
- Transform proj = (p_camera_inverse_transform * light_transform).inverse();
-
- store_transform(proj, light_data.shadow_matrix);
-
- if (size > 0.0) {
- light_data.soft_shadow_size = size;
- } else {
- light_data.soft_shadow_size = 0.0;
- light_data.soft_shadow_scale *= shadows_quality_radius_get(); // Only use quality radius for PCF
- }
-
- } else if (type == RS::LIGHT_SPOT) {
- Transform modelview = (p_camera_inverse_transform * light_transform).inverse();
- CameraMatrix bias;
- bias.set_light_bias();
-
- CameraMatrix shadow_mtx = bias * light_instance_get_shadow_camera(li, 0) * modelview;
- store_camera(shadow_mtx, light_data.shadow_matrix);
-
- if (size > 0.0) {
- CameraMatrix cm = light_instance_get_shadow_camera(li, 0);
- float half_np = cm.get_z_near() * Math::tan(Math::deg2rad(spot_angle));
- light_data.soft_shadow_size = (size * 0.5 / radius) / (half_np / cm.get_z_near()) * rect.size.width;
- } else {
- light_data.soft_shadow_size = 0.0;
- light_data.soft_shadow_scale *= shadows_quality_radius_get(); // Only use quality radius for PCF
- }
- }
- } else {
- light_data.shadow_color_enabled[3] = 0;
- }
-
- light_instance_set_index(li, light_count);
-
- cluster_builder.add_light(type == RS::LIGHT_SPOT ? LightClusterBuilder::LIGHT_TYPE_SPOT : LightClusterBuilder::LIGHT_TYPE_OMNI, light_transform, radius, spot_angle);
-
- light_count++;
- } break;
- }
-
- light_instance_set_render_pass(li, render_pass);
-
- //update UBO for forward rendering, blit to texture for clustered
- }
-
- if (light_count) {
- RD::get_singleton()->buffer_update(scene_state.light_buffer, 0, sizeof(LightData) * light_count, scene_state.lights, true);
- }
-
- if (scene_state.ubo.directional_light_count) {
- RD::get_singleton()->buffer_update(scene_state.directional_light_buffer, 0, sizeof(DirectionalLightData) * scene_state.ubo.directional_light_count, scene_state.directional_lights, true);
- }
-}
-
-void RasterizerSceneHighEndRD::_setup_decals(const RID *p_decal_instances, int p_decal_count, const Transform &p_camera_inverse_xform) {
- Transform uv_xform;
- uv_xform.basis.scale(Vector3(2.0, 1.0, 2.0));
- uv_xform.origin = Vector3(-1.0, 0.0, -1.0);
-
- p_decal_count = MIN((uint32_t)p_decal_count, scene_state.max_decals);
- int idx = 0;
- for (int i = 0; i < p_decal_count; i++) {
- RID di = p_decal_instances[i];
- RID decal = decal_instance_get_base(di);
-
- Transform xform = decal_instance_get_transform(di);
-
- float fade = 1.0;
-
- if (storage->decal_is_distance_fade_enabled(decal)) {
- real_t distance = -p_camera_inverse_xform.xform(xform.origin).z;
- float fade_begin = storage->decal_get_distance_fade_begin(decal);
- float fade_length = storage->decal_get_distance_fade_length(decal);
-
- if (distance > fade_begin) {
- if (distance > fade_begin + fade_length) {
- continue; // do not use this decal, its invisible
- }
-
- fade = 1.0 - (distance - fade_begin) / fade_length;
- }
- }
-
- DecalData &dd = scene_state.decals[idx];
-
- Vector3 decal_extents = storage->decal_get_extents(decal);
-
- Transform scale_xform;
- scale_xform.basis.scale(Vector3(decal_extents.x, decal_extents.y, decal_extents.z));
- Transform to_decal_xform = (p_camera_inverse_xform * decal_instance_get_transform(di) * scale_xform * uv_xform).affine_inverse();
- store_transform(to_decal_xform, dd.xform);
-
- Vector3 normal = xform.basis.get_axis(Vector3::AXIS_Y).normalized();
- normal = p_camera_inverse_xform.basis.xform(normal); //camera is normalized, so fine
-
- dd.normal[0] = normal.x;
- dd.normal[1] = normal.y;
- dd.normal[2] = normal.z;
- dd.normal_fade = storage->decal_get_normal_fade(decal);
-
- RID albedo_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_ALBEDO);
- RID emission_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_EMISSION);
- if (albedo_tex.is_valid()) {
- Rect2 rect = storage->decal_atlas_get_texture_rect(albedo_tex);
- dd.albedo_rect[0] = rect.position.x;
- dd.albedo_rect[1] = rect.position.y;
- dd.albedo_rect[2] = rect.size.x;
- dd.albedo_rect[3] = rect.size.y;
- } else {
- if (!emission_tex.is_valid()) {
- continue; //no albedo, no emission, no decal.
- }
- dd.albedo_rect[0] = 0;
- dd.albedo_rect[1] = 0;
- dd.albedo_rect[2] = 0;
- dd.albedo_rect[3] = 0;
- }
-
- RID normal_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_NORMAL);
-
- if (normal_tex.is_valid()) {
- Rect2 rect = storage->decal_atlas_get_texture_rect(normal_tex);
- dd.normal_rect[0] = rect.position.x;
- dd.normal_rect[1] = rect.position.y;
- dd.normal_rect[2] = rect.size.x;
- dd.normal_rect[3] = rect.size.y;
-
- Basis normal_xform = p_camera_inverse_xform.basis * xform.basis.orthonormalized();
- store_basis_3x4(normal_xform, dd.normal_xform);
- } else {
- dd.normal_rect[0] = 0;
- dd.normal_rect[1] = 0;
- dd.normal_rect[2] = 0;
- dd.normal_rect[3] = 0;
- }
-
- RID orm_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_ORM);
- if (orm_tex.is_valid()) {
- Rect2 rect = storage->decal_atlas_get_texture_rect(orm_tex);
- dd.orm_rect[0] = rect.position.x;
- dd.orm_rect[1] = rect.position.y;
- dd.orm_rect[2] = rect.size.x;
- dd.orm_rect[3] = rect.size.y;
- } else {
- dd.orm_rect[0] = 0;
- dd.orm_rect[1] = 0;
- dd.orm_rect[2] = 0;
- dd.orm_rect[3] = 0;
- }
-
- if (emission_tex.is_valid()) {
- Rect2 rect = storage->decal_atlas_get_texture_rect(emission_tex);
- dd.emission_rect[0] = rect.position.x;
- dd.emission_rect[1] = rect.position.y;
- dd.emission_rect[2] = rect.size.x;
- dd.emission_rect[3] = rect.size.y;
- } else {
- dd.emission_rect[0] = 0;
- dd.emission_rect[1] = 0;
- dd.emission_rect[2] = 0;
- dd.emission_rect[3] = 0;
- }
-
- Color modulate = storage->decal_get_modulate(decal);
- dd.modulate[0] = modulate.r;
- dd.modulate[1] = modulate.g;
- dd.modulate[2] = modulate.b;
- dd.modulate[3] = modulate.a * fade;
- dd.emission_energy = storage->decal_get_emission_energy(decal) * fade;
- dd.albedo_mix = storage->decal_get_albedo_mix(decal);
- dd.mask = storage->decal_get_cull_mask(decal);
- dd.upper_fade = storage->decal_get_upper_fade(decal);
- dd.lower_fade = storage->decal_get_lower_fade(decal);
-
- cluster_builder.add_decal(xform, decal_extents);
-
- idx++;
- }
-
- if (idx > 0) {
- RD::get_singleton()->buffer_update(scene_state.decal_buffer, 0, sizeof(DecalData) * idx, scene_state.decals, true);
- }
-}
-
-void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID *p_decal_cull_result, int p_decal_cull_count, InstanceBase **p_lightmap_cull_result, int p_lightmap_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color) {
+void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, int p_directional_light_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, InstanceBase **p_lightmap_cull_result, int p_lightmap_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color) {
RenderBufferDataHighEnd *render_buffer = nullptr;
if (p_render_buffer.is_valid()) {
render_buffer = (RenderBufferDataHighEnd *)render_buffers_get_data(p_render_buffer);
@@ -2147,19 +1551,8 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor
RENDER_TIMESTAMP("Setup 3D Scene");
- if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_UNSHADED) {
- p_light_cull_count = 0;
- p_reflection_probe_cull_count = 0;
- p_gi_probe_cull_count = 0;
- }
-
- bool using_shadows = true;
-
if (p_reflection_probe.is_valid()) {
scene_state.ubo.reflection_multiplier = 0.0;
- if (!storage->reflection_probe_renders_shadows(reflection_probe_instance_get_probe(p_reflection_probe))) {
- using_shadows = false;
- }
} else {
scene_state.ubo.reflection_multiplier = 1.0;
}
@@ -2169,6 +1562,7 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor
Vector2 vp_he = p_cam_projection.get_viewport_half_extents();
scene_state.ubo.viewport_size[0] = vp_he.x;
scene_state.ubo.viewport_size[1] = vp_he.y;
+ scene_state.ubo.directional_light_count = p_directional_light_count;
Size2 screen_pixel_size;
Size2i screen_size;
@@ -2259,16 +1653,9 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor
ERR_FAIL(); //bug?
}
- cluster_builder.begin(p_cam_transform.affine_inverse(), p_cam_projection); //prepare cluster
-
- _setup_lights(p_light_cull_result, p_light_cull_count, p_cam_transform.affine_inverse(), p_shadow_atlas, using_shadows);
- _setup_decals(p_decal_cull_result, p_decal_cull_count, p_cam_transform.affine_inverse());
- _setup_reflections(p_reflection_probe_cull_result, p_reflection_probe_cull_count, p_cam_transform.affine_inverse(), p_environment);
_setup_lightmaps(p_lightmap_cull_result, p_lightmap_cull_count, p_cam_transform);
_setup_environment(p_environment, p_render_buffer, p_cam_projection, p_cam_transform, p_reflection_probe, p_reflection_probe.is_valid(), screen_pixel_size, p_shadow_atlas, !p_reflection_probe.is_valid(), p_default_bg_color, p_cam_projection.get_z_near(), p_cam_projection.get_z_far(), false);
- cluster_builder.bake_cluster(); //bake to cluster
-
_update_render_base_uniform_set(); //may have changed due to the above (light buffer enlarged, as an example)
render_list.clear();
@@ -2745,7 +2132,7 @@ void RasterizerSceneHighEndRD::_render_sdfgi(RID p_render_buffers, const Vector3
to_bounds.origin = p_bounds.position;
to_bounds.basis.scale(p_bounds.size);
- store_transform(to_bounds.affine_inverse() * cam_xform, scene_state.ubo.sdf_to_bounds);
+ RasterizerStorageRD::store_transform(to_bounds.affine_inverse() * cam_xform, scene_state.ubo.sdf_to_bounds);
_setup_environment(RID(), RID(), camera_proj, cam_xform, RID(), true, Vector2(1, 1), RID(), false, Color(), 0, 0);
@@ -2826,22 +2213,22 @@ void RasterizerSceneHighEndRD::_update_render_base_uniform_set() {
RD::Uniform u;
u.binding = 5;
u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
- u.ids.push_back(scene_state.light_buffer);
+ u.ids.push_back(get_positional_light_buffer());
uniforms.push_back(u);
}
{
RD::Uniform u;
u.binding = 6;
- u.type = RD::UNIFORM_TYPE_UNIFORM_BUFFER;
- u.ids.push_back(scene_state.reflection_buffer);
+ u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.ids.push_back(get_reflection_probe_buffer());
uniforms.push_back(u);
}
{
RD::Uniform u;
u.binding = 7;
u.type = RD::UNIFORM_TYPE_UNIFORM_BUFFER;
- u.ids.push_back(scene_state.directional_light_buffer);
+ u.ids.push_back(get_directional_light_buffer());
uniforms.push_back(u);
}
{
@@ -2885,7 +2272,7 @@ void RasterizerSceneHighEndRD::_update_render_base_uniform_set() {
RD::Uniform u;
u.binding = 15;
u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
- u.ids.push_back(scene_state.decal_buffer);
+ u.ids.push_back(get_decal_buffer());
uniforms.push_back(u);
}
@@ -2893,14 +2280,14 @@ void RasterizerSceneHighEndRD::_update_render_base_uniform_set() {
RD::Uniform u;
u.binding = 16;
u.type = RD::UNIFORM_TYPE_TEXTURE;
- u.ids.push_back(cluster_builder.get_cluster_texture());
+ u.ids.push_back(get_cluster_builder_texture());
uniforms.push_back(u);
}
{
RD::Uniform u;
u.binding = 17;
u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
- u.ids.push_back(cluster_builder.get_cluster_indices_buffer());
+ u.ids.push_back(get_cluster_builder_indices_buffer());
uniforms.push_back(u);
}
@@ -3141,37 +2528,8 @@ RasterizerSceneHighEndRD::RasterizerSceneHighEndRD(RasterizerStorageRD *p_storag
defines += "\n#define USE_RADIANCE_CUBEMAP_ARRAY \n";
}
defines += "\n#define SDFGI_OCT_SIZE " + itos(sdfgi_get_lightprobe_octahedron_size()) + "\n";
+ defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(get_max_directional_lights()) + "\n";
- uint32_t uniform_max_size = RD::get_singleton()->limit_get(RD::LIMIT_MAX_UNIFORM_BUFFER_SIZE);
-
- { //reflections
- uint32_t reflection_buffer_size;
- if (uniform_max_size < 65536) {
- //Yes, you guessed right, ARM again
- reflection_buffer_size = uniform_max_size;
- } else {
- reflection_buffer_size = 65536;
- }
-
- scene_state.max_reflections = reflection_buffer_size / sizeof(ReflectionData);
- scene_state.reflections = memnew_arr(ReflectionData, scene_state.max_reflections);
- scene_state.reflection_buffer = RD::get_singleton()->uniform_buffer_create(reflection_buffer_size);
- defines += "\n#define MAX_REFLECTION_DATA_STRUCTS " + itos(scene_state.max_reflections) + "\n";
- }
-
- { //lights
- scene_state.max_lights = MIN(1024 * 1024, uniform_max_size) / sizeof(LightData); //1mb of lights
- uint32_t light_buffer_size = scene_state.max_lights * sizeof(LightData);
- scene_state.lights = memnew_arr(LightData, scene_state.max_lights);
- scene_state.light_buffer = RD::get_singleton()->storage_buffer_create(light_buffer_size);
- //defines += "\n#define MAX_LIGHT_DATA_STRUCTS " + itos(scene_state.max_lights) + "\n";
-
- scene_state.max_directional_lights = 8;
- uint32_t directional_light_buffer_size = scene_state.max_directional_lights * sizeof(DirectionalLightData);
- scene_state.directional_lights = memnew_arr(DirectionalLightData, scene_state.max_directional_lights);
- scene_state.directional_light_buffer = RD::get_singleton()->uniform_buffer_create(directional_light_buffer_size);
- defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(scene_state.max_directional_lights) + "\n";
- }
{
//lightmaps
scene_state.max_lightmaps = storage->lightmap_array_get_size();
@@ -3187,13 +2545,6 @@ RasterizerSceneHighEndRD::RasterizerSceneHighEndRD(RasterizerStorageRD *p_storag
scene_state.lightmap_captures = memnew_arr(LightmapCaptureData, scene_state.max_lightmap_captures);
scene_state.lightmap_capture_buffer = RD::get_singleton()->storage_buffer_create(sizeof(LightmapCaptureData) * scene_state.max_lightmap_captures);
}
- { //decals
- scene_state.max_decals = MIN(1024 * 1024, uniform_max_size) / sizeof(DecalData); //1mb of decals
- uint32_t decal_buffer_size = scene_state.max_decals * sizeof(DecalData);
- scene_state.decals = memnew_arr(DecalData, scene_state.max_decals);
- scene_state.decal_buffer = RD::get_singleton()->storage_buffer_create(decal_buffer_size);
- }
-
{
defines += "\n#define MATERIAL_UNIFORM_SET " + itos(MATERIAL_UNIFORM_SET) + "\n";
}
@@ -3467,8 +2818,6 @@ RasterizerSceneHighEndRD::RasterizerSceneHighEndRD(RasterizerStorageRD *p_storag
default_render_buffers_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, default_shader_rd, RENDER_BUFFERS_UNIFORM_SET);
}
-
- cluster_builder.setup(16, 8, 24);
}
RasterizerSceneHighEndRD::~RasterizerSceneHighEndRD() {
@@ -3495,19 +2844,11 @@ RasterizerSceneHighEndRD::~RasterizerSceneHighEndRD() {
{
RD::get_singleton()->free(scene_state.uniform_buffer);
RD::get_singleton()->free(scene_state.instance_buffer);
- RD::get_singleton()->free(scene_state.directional_light_buffer);
- RD::get_singleton()->free(scene_state.light_buffer);
RD::get_singleton()->free(scene_state.lightmap_buffer);
RD::get_singleton()->free(scene_state.lightmap_capture_buffer);
- RD::get_singleton()->free(scene_state.reflection_buffer);
- RD::get_singleton()->free(scene_state.decal_buffer);
memdelete_arr(scene_state.instances);
- memdelete_arr(scene_state.directional_lights);
- memdelete_arr(scene_state.lights);
memdelete_arr(scene_state.lightmaps);
memdelete_arr(scene_state.lightmap_captures);
- memdelete_arr(scene_state.reflections);
- memdelete_arr(scene_state.decals);
}
while (sdfgi_framebuffer_size_cache.front()) {
diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h
index cb03da48c1..a49173de98 100644
--- a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h
+++ b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h
@@ -31,7 +31,6 @@
#ifndef RASTERIZER_SCENE_HIGHEND_RD_H
#define RASTERIZER_SCENE_HIGHEND_RD_H
-#include "servers/rendering/rasterizer_rd/light_cluster_builder.h"
#include "servers/rendering/rasterizer_rd/rasterizer_scene_rd.h"
#include "servers/rendering/rasterizer_rd/rasterizer_storage_rd.h"
#include "servers/rendering/rasterizer_rd/render_pipeline_vertex_format_cache_rd.h"
@@ -264,92 +263,10 @@ class RasterizerSceneHighEndRD : public RasterizerSceneRD {
void _setup_view_dependant_uniform_set(RID p_shadow_atlas, RID p_reflection_atlas, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count);
void _update_render_buffers_uniform_set(RID p_render_buffers);
- /* Scene State UBO */
-
- struct ReflectionData { //should always be 128 bytes
- float box_extents[3];
- float index;
- float box_offset[3];
- uint32_t mask;
- float params[4]; // intensity, 0, interior , boxproject
- float ambient[3]; // ambient color,
- uint32_t ambient_mode;
- float local_matrix[16]; // up to here for spot and omni, rest is for directional
- };
-
- struct LightData {
- float position[3];
- float inv_radius;
- float direction[3];
- float size;
- uint16_t attenuation_energy[2]; //16 bits attenuation, then energy
- uint8_t color_specular[4]; //rgb color, a specular (8 bit unorm)
- uint16_t cone_attenuation_angle[2]; // attenuation and angle, (16bit float)
- uint8_t shadow_color_enabled[4]; //shadow rgb color, a>0.5 enabled (8bit unorm)
- float atlas_rect[4]; // in omni, used for atlas uv, in spot, used for projector uv
- float shadow_matrix[16];
- float shadow_bias;
- float shadow_normal_bias;
- float transmittance_bias;
- float soft_shadow_size;
- float soft_shadow_scale;
- uint32_t mask;
- uint32_t pad[2];
- float projector_rect[4];
- };
-
- struct DirectionalLightData {
- float direction[3];
- float energy;
- float color[3];
- float size;
- float specular;
- uint32_t mask;
- float softshadow_angle;
- float soft_shadow_scale;
- uint32_t blend_splits;
- uint32_t shadow_enabled;
- float fade_from;
- float fade_to;
- float shadow_bias[4];
- float shadow_normal_bias[4];
- float shadow_transmittance_bias[4];
- float shadow_transmittance_z_scale[4];
- float shadow_range_begin[4];
- float shadow_split_offsets[4];
- float shadow_matrices[4][16];
- float shadow_color1[4];
- float shadow_color2[4];
- float shadow_color3[4];
- float shadow_color4[4];
- float uv_scale1[2];
- float uv_scale2[2];
- float uv_scale3[2];
- float uv_scale4[2];
- };
-
struct LightmapData {
float normal_xform[12];
};
- struct DecalData {
- float xform[16];
- float inv_extents[3];
- float albedo_mix;
- float albedo_rect[4];
- float normal_rect[4];
- float orm_rect[4];
- float emission_rect[4];
- float modulate[4];
- float emission_energy;
- uint32_t mask;
- float upper_fade;
- float lower_fade;
- float normal_xform[12];
- float normal[3];
- float normal_fade;
- };
-
struct LightmapCaptureData {
float sh[9 * 4];
};
@@ -448,27 +365,10 @@ class RasterizerSceneHighEndRD : public RasterizerSceneRD {
RID uniform_buffer;
- ReflectionData *reflections;
- uint32_t max_reflections;
- RID reflection_buffer;
- uint32_t max_reflection_probes_per_instance;
-
LightmapData *lightmaps;
uint32_t max_lightmaps;
RID lightmap_buffer;
- DecalData *decals;
- uint32_t max_decals;
- RID decal_buffer;
-
- LightData *lights;
- uint32_t max_lights;
- RID light_buffer;
-
- DirectionalLightData *directional_lights;
- uint32_t max_directional_lights;
- RID directional_light_buffer;
-
LightmapCaptureData *lightmap_captures;
uint32_t max_lightmap_captures;
RID lightmap_capture_buffer;
@@ -635,8 +535,6 @@ class RasterizerSceneHighEndRD : public RasterizerSceneRD {
RID default_vec4_xform_buffer;
RID default_vec4_xform_uniform_set;
- LightClusterBuilder cluster_builder;
-
enum PassMode {
PASS_MODE_COLOR,
PASS_MODE_COLOR_SPECULAR,
@@ -651,9 +549,6 @@ class RasterizerSceneHighEndRD : public RasterizerSceneRD {
};
void _setup_environment(RID p_environment, RID p_render_buffers, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, RID p_reflection_probe, bool p_no_fog, const Size2 &p_screen_pixel_size, RID p_shadow_atlas, bool p_flip_y, const Color &p_default_bg_color, float p_znear, float p_zfar, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false);
- void _setup_lights(RID *p_light_cull_result, int p_light_cull_count, const Transform &p_camera_inverse_transform, RID p_shadow_atlas, bool p_using_shadows);
- void _setup_decals(const RID *p_decal_instances, int p_decal_count, const Transform &p_camera_inverse_xform);
- void _setup_reflections(RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, const Transform &p_camera_inverse_transform, RID p_environment);
void _setup_lightmaps(InstanceBase **p_lightmap_cull_result, int p_lightmap_cull_count, const Transform &p_cam_transform);
void _fill_instances(RenderList::Element **p_elements, int p_element_count, bool p_for_depth, bool p_has_sdfgi = false, bool p_has_opaque_gi = false);
@@ -666,7 +561,7 @@ class RasterizerSceneHighEndRD : public RasterizerSceneRD {
Map<Size2i, RID> sdfgi_framebuffer_size_cache;
protected:
- virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID *p_decal_cull_result, int p_decal_cull_count, InstanceBase **p_lightmap_cull_result, int p_lightmap_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color);
+ virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, int p_directional_light_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, InstanceBase **p_lightmap_cull_result, int p_lightmap_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color);
virtual void _render_shadow(RID p_framebuffer, InstanceBase **p_cull_result, int p_cull_count, const CameraMatrix &p_projection, const Transform &p_transform, float p_zfar, float p_bias, float p_normal_bias, bool p_use_dp, bool p_use_dp_flip, bool p_use_pancake);
virtual void _render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region);
virtual void _render_uv2(InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region);
diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp
index 4ebed78996..bdf9b71c56 100644
--- a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp
+++ b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp
@@ -238,7 +238,7 @@ void RasterizerSceneRD::_sdfgi_erase(RenderBuffers *rb) {
const Vector3i RasterizerSceneRD::SDFGI::Cascade::DIRTY_ALL = Vector3i(0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF);
void RasterizerSceneRD::sdfgi_update(RID p_render_buffers, RID p_environment, const Vector3 &p_world_position) {
- Environent *env = environment_owner.getornull(p_environment);
+ Environment *env = environment_owner.getornull(p_environment);
RenderBuffers *rb = render_buffers_owner.getornull(p_render_buffers);
bool needs_sdfgi = env && env->sdfgi_enabled;
@@ -925,7 +925,7 @@ void RasterizerSceneRD::sdfgi_update(RID p_render_buffers, RID p_environment, co
if (i < sdfgi->cascades.size() - 1) {
parent_average = sdfgi->cascades[i + 1].lightprobe_average_tex;
} else {
- parent_average = sdfgi->cascades[i - 1].lightprobe_average_tex; //to use something, but it wont be used
+ parent_average = sdfgi->cascades[i - 1].lightprobe_average_tex; //to use something, but it won't be used
}
u.ids.push_back(parent_average);
uniforms.push_back(u);
@@ -1142,7 +1142,7 @@ void RasterizerSceneRD::sdfgi_update_probes(RID p_render_buffers, RID p_environm
if (rb->sdfgi == nullptr) {
return;
}
- Environent *env = environment_owner.getornull(p_environment);
+ Environment *env = environment_owner.getornull(p_environment);
RENDER_TIMESTAMP(">SDFGI Update Probes");
@@ -1380,7 +1380,7 @@ void RasterizerSceneRD::_process_gi(RID p_render_buffers, RID p_normal_roughness
RenderBuffers *rb = render_buffers_owner.getornull(p_render_buffers);
ERR_FAIL_COND(rb == nullptr);
- Environent *env = environment_owner.getornull(p_environment);
+ Environment *env = environment_owner.getornull(p_environment);
GI::PushConstant push_constant;
@@ -1881,7 +1881,7 @@ void RasterizerSceneRD::_update_dirty_skys() {
texture_set_dirty = true;
}
- // Create subpass buffers if they havent been created already
+ // Create subpass buffers if they haven't been created already
if (sky->half_res_pass.is_null() && !RD::get_singleton()->texture_is_valid(sky->half_res_pass) && sky->screen_size.x >= 4 && sky->screen_size.y >= 4) {
RD::TextureFormat tformat;
tformat.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
@@ -2695,53 +2695,53 @@ RasterizerStorageRD::MaterialData *RasterizerSceneRD::_create_sky_material_func(
}
RID RasterizerSceneRD::environment_create() {
- return environment_owner.make_rid(Environent());
+ return environment_owner.make_rid(Environment());
}
void RasterizerSceneRD::environment_set_background(RID p_env, RS::EnvironmentBG p_bg) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->background = p_bg;
}
void RasterizerSceneRD::environment_set_sky(RID p_env, RID p_sky) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->sky = p_sky;
}
void RasterizerSceneRD::environment_set_sky_custom_fov(RID p_env, float p_scale) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->sky_custom_fov = p_scale;
}
void RasterizerSceneRD::environment_set_sky_orientation(RID p_env, const Basis &p_orientation) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->sky_orientation = p_orientation;
}
void RasterizerSceneRD::environment_set_bg_color(RID p_env, const Color &p_color) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->bg_color = p_color;
}
void RasterizerSceneRD::environment_set_bg_energy(RID p_env, float p_energy) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->bg_energy = p_energy;
}
void RasterizerSceneRD::environment_set_canvas_max_layer(RID p_env, int p_max_layer) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->canvas_max_layer = p_max_layer;
}
void RasterizerSceneRD::environment_set_ambient_light(RID p_env, const Color &p_color, RS::EnvironmentAmbientSource p_ambient, float p_energy, float p_sky_contribution, RS::EnvironmentReflectionSource p_reflection_source, const Color &p_ao_color) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->ambient_light = p_color;
env->ambient_source = p_ambient;
@@ -2752,85 +2752,85 @@ void RasterizerSceneRD::environment_set_ambient_light(RID p_env, const Color &p_
}
RS::EnvironmentBG RasterizerSceneRD::environment_get_background(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, RS::ENV_BG_MAX);
return env->background;
}
RID RasterizerSceneRD::environment_get_sky(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, RID());
return env->sky;
}
float RasterizerSceneRD::environment_get_sky_custom_fov(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, 0);
return env->sky_custom_fov;
}
Basis RasterizerSceneRD::environment_get_sky_orientation(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, Basis());
return env->sky_orientation;
}
Color RasterizerSceneRD::environment_get_bg_color(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, Color());
return env->bg_color;
}
float RasterizerSceneRD::environment_get_bg_energy(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, 0);
return env->bg_energy;
}
int RasterizerSceneRD::environment_get_canvas_max_layer(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, 0);
return env->canvas_max_layer;
}
Color RasterizerSceneRD::environment_get_ambient_light_color(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, Color());
return env->ambient_light;
}
RS::EnvironmentAmbientSource RasterizerSceneRD::environment_get_ambient_source(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, RS::ENV_AMBIENT_SOURCE_BG);
return env->ambient_source;
}
float RasterizerSceneRD::environment_get_ambient_light_energy(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, 0);
return env->ambient_light_energy;
}
float RasterizerSceneRD::environment_get_ambient_sky_contribution(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, 0);
return env->ambient_sky_contribution;
}
RS::EnvironmentReflectionSource RasterizerSceneRD::environment_get_reflection_source(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, RS::ENV_REFLECTION_SOURCE_DISABLED);
return env->reflection_source;
}
Color RasterizerSceneRD::environment_get_ao_color(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, Color());
return env->ao_color;
}
void RasterizerSceneRD::environment_set_tonemap(RID p_env, RS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white, bool p_auto_exposure, float p_min_luminance, float p_max_luminance, float p_auto_exp_speed, float p_auto_exp_scale) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->exposure = p_exposure;
env->tone_mapper = p_tone_mapper;
@@ -2846,7 +2846,7 @@ void RasterizerSceneRD::environment_set_tonemap(RID p_env, RS::EnvironmentToneMa
}
void RasterizerSceneRD::environment_set_glow(RID p_env, bool p_enable, int p_level_flags, float p_intensity, float p_strength, float p_mix, float p_bloom_threshold, RS::EnvironmentGlowBlendMode p_blend_mode, float p_hdr_bleed_threshold, float p_hdr_bleed_scale, float p_hdr_luminance_cap) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->glow_enabled = p_enable;
env->glow_levels = p_level_flags;
@@ -2865,7 +2865,7 @@ void RasterizerSceneRD::environment_glow_set_use_bicubic_upscale(bool p_enable)
}
void RasterizerSceneRD::environment_set_sdfgi(RID p_env, bool p_enable, RS::EnvironmentSDFGICascades p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, bool p_use_multibounce, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->sdfgi_enabled = p_enable;
@@ -2889,7 +2889,7 @@ void RasterizerSceneRD::environment_set_sdfgi_frames_to_converge(RS::Environment
}
void RasterizerSceneRD::environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_int, float p_fade_out, float p_depth_tolerance) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->ssr_enabled = p_enable;
@@ -2908,7 +2908,7 @@ RS::EnvironmentSSRRoughnessQuality RasterizerSceneRD::environment_get_ssr_roughn
}
void RasterizerSceneRD::environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_bias, float p_light_affect, float p_ao_channel_affect, RS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);
env->ssao_enabled = p_enable;
@@ -2926,30 +2926,30 @@ void RasterizerSceneRD::environment_set_ssao_quality(RS::EnvironmentSSAOQuality
}
bool RasterizerSceneRD::environment_is_ssao_enabled(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, false);
return env->ssao_enabled;
}
float RasterizerSceneRD::environment_get_ssao_ao_affect(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, false);
return env->ssao_ao_channel_affect;
}
float RasterizerSceneRD::environment_get_ssao_light_affect(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, false);
return env->ssao_direct_light_affect;
}
bool RasterizerSceneRD::environment_is_ssr_enabled(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, false);
return env->ssr_enabled;
}
bool RasterizerSceneRD::environment_is_sdfgi_enabled(RID p_env) const {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, false);
return env->sdfgi_enabled;
}
@@ -2959,7 +2959,7 @@ bool RasterizerSceneRD::is_environment(RID p_env) const {
}
Ref<Image> RasterizerSceneRD::environment_bake_panorama(RID p_env, bool p_bake_irradiance, const Size2i &p_size) {
- Environent *env = environment_owner.getornull(p_env);
+ Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND_V(!env, Ref<Image>());
if (env->background == RS::ENV_BG_CAMERA_FEED || env->background == RS::ENV_BG_CANVAS || env->background == RS::ENV_BG_KEEP) {
@@ -4903,7 +4903,7 @@ void RasterizerSceneRD::_process_ssr(RID p_render_buffers, RID p_dest_framebuffe
return;
}
- Environent *env = environment_owner.getornull(p_environment);
+ Environment *env = environment_owner.getornull(p_environment);
ERR_FAIL_COND(!env);
ERR_FAIL_COND(!env->ssr_enabled);
@@ -4948,7 +4948,7 @@ void RasterizerSceneRD::_process_ssao(RID p_render_buffers, RID p_environment, R
RenderBuffers *rb = render_buffers_owner.getornull(p_render_buffers);
ERR_FAIL_COND(!rb);
- Environent *env = environment_owner.getornull(p_environment);
+ Environment *env = environment_owner.getornull(p_environment);
ERR_FAIL_COND(!env);
if (rb->ssao.ao[0].is_valid() && rb->ssao.ao_full.is_valid() != ssao_half_size) {
@@ -5015,7 +5015,7 @@ void RasterizerSceneRD::_render_buffers_post_process_and_tonemap(RID p_render_bu
RenderBuffers *rb = render_buffers_owner.getornull(p_render_buffers);
ERR_FAIL_COND(!rb);
- Environent *env = environment_owner.getornull(p_environment);
+ Environment *env = environment_owner.getornull(p_environment);
//glow (if enabled)
CameraEffects *camfx = camera_effects_owner.getornull(p_camera_effects);
@@ -5619,6 +5619,539 @@ RasterizerSceneRD::RenderBufferData *RasterizerSceneRD::render_buffers_get_data(
return rb->data;
}
+void RasterizerSceneRD::_setup_reflections(RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, const Transform &p_camera_inverse_transform, RID p_environment) {
+ for (int i = 0; i < p_reflection_probe_cull_count; i++) {
+ RID rpi = p_reflection_probe_cull_result[i];
+
+ if (i >= (int)cluster.max_reflections) {
+ reflection_probe_instance_set_render_index(rpi, 0); //invalid, but something needs to be set
+ continue;
+ }
+
+ reflection_probe_instance_set_render_index(rpi, i);
+
+ RID base_probe = reflection_probe_instance_get_probe(rpi);
+
+ Cluster::ReflectionData &reflection_ubo = cluster.reflections[i];
+
+ Vector3 extents = storage->reflection_probe_get_extents(base_probe);
+
+ reflection_ubo.box_extents[0] = extents.x;
+ reflection_ubo.box_extents[1] = extents.y;
+ reflection_ubo.box_extents[2] = extents.z;
+ reflection_ubo.index = reflection_probe_instance_get_atlas_index(rpi);
+
+ Vector3 origin_offset = storage->reflection_probe_get_origin_offset(base_probe);
+
+ reflection_ubo.box_offset[0] = origin_offset.x;
+ reflection_ubo.box_offset[1] = origin_offset.y;
+ reflection_ubo.box_offset[2] = origin_offset.z;
+ reflection_ubo.mask = storage->reflection_probe_get_cull_mask(base_probe);
+
+ float intensity = storage->reflection_probe_get_intensity(base_probe);
+ bool interior = storage->reflection_probe_is_interior(base_probe);
+ bool box_projection = storage->reflection_probe_is_box_projection(base_probe);
+
+ reflection_ubo.params[0] = intensity;
+ reflection_ubo.params[1] = 0;
+ reflection_ubo.params[2] = interior ? 1.0 : 0.0;
+ reflection_ubo.params[3] = box_projection ? 1.0 : 0.0;
+
+ Color ambient_linear = storage->reflection_probe_get_ambient_color(base_probe).to_linear();
+ float interior_ambient_energy = storage->reflection_probe_get_ambient_color_energy(base_probe);
+ uint32_t ambient_mode = storage->reflection_probe_get_ambient_mode(base_probe);
+ reflection_ubo.ambient[0] = ambient_linear.r * interior_ambient_energy;
+ reflection_ubo.ambient[1] = ambient_linear.g * interior_ambient_energy;
+ reflection_ubo.ambient[2] = ambient_linear.b * interior_ambient_energy;
+ reflection_ubo.ambient_mode = ambient_mode;
+
+ Transform transform = reflection_probe_instance_get_transform(rpi);
+ Transform proj = (p_camera_inverse_transform * transform).inverse();
+ RasterizerStorageRD::store_transform(proj, reflection_ubo.local_matrix);
+
+ cluster.builder.add_reflection_probe(transform, extents);
+
+ reflection_probe_instance_set_render_pass(rpi, RSG::rasterizer->get_frame_number());
+ }
+
+ if (p_reflection_probe_cull_count) {
+ RD::get_singleton()->buffer_update(cluster.reflection_buffer, 0, MIN(cluster.max_reflections, (unsigned int)p_reflection_probe_cull_count) * sizeof(ReflectionData), cluster.reflections, true);
+ }
+}
+
+void RasterizerSceneRD::_setup_lights(RID *p_light_cull_result, int p_light_cull_count, const Transform &p_camera_inverse_transform, RID p_shadow_atlas, bool p_using_shadows, uint32_t &r_directional_light_count) {
+ uint32_t light_count = 0;
+ r_directional_light_count = 0;
+ sky_scene_state.directional_light_count = 0;
+
+ for (int i = 0; i < p_light_cull_count; i++) {
+ RID li = p_light_cull_result[i];
+ RID base = light_instance_get_base_light(li);
+
+ ERR_CONTINUE(base.is_null());
+
+ RS::LightType type = storage->light_get_type(base);
+ switch (type) {
+ case RS::LIGHT_DIRECTIONAL: {
+ if (r_directional_light_count >= cluster.max_directional_lights) {
+ continue;
+ }
+
+ Cluster::DirectionalLightData &light_data = cluster.directional_lights[r_directional_light_count];
+
+ Transform light_transform = light_instance_get_base_transform(li);
+
+ Vector3 direction = p_camera_inverse_transform.basis.xform(light_transform.basis.xform(Vector3(0, 0, 1))).normalized();
+
+ light_data.direction[0] = direction.x;
+ light_data.direction[1] = direction.y;
+ light_data.direction[2] = direction.z;
+
+ float sign = storage->light_is_negative(base) ? -1 : 1;
+
+ light_data.energy = sign * storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY) * Math_PI;
+
+ Color linear_col = storage->light_get_color(base).to_linear();
+ light_data.color[0] = linear_col.r;
+ light_data.color[1] = linear_col.g;
+ light_data.color[2] = linear_col.b;
+
+ light_data.specular = storage->light_get_param(base, RS::LIGHT_PARAM_SPECULAR);
+ light_data.mask = storage->light_get_cull_mask(base);
+
+ float size = storage->light_get_param(base, RS::LIGHT_PARAM_SIZE);
+
+ light_data.size = 1.0 - Math::cos(Math::deg2rad(size)); //angle to cosine offset
+
+ Color shadow_col = storage->light_get_shadow_color(base).to_linear();
+
+ if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_PSSM_SPLITS) {
+ light_data.shadow_color1[0] = 1.0;
+ light_data.shadow_color1[1] = 0.0;
+ light_data.shadow_color1[2] = 0.0;
+ light_data.shadow_color1[3] = 1.0;
+ light_data.shadow_color2[0] = 0.0;
+ light_data.shadow_color2[1] = 1.0;
+ light_data.shadow_color2[2] = 0.0;
+ light_data.shadow_color2[3] = 1.0;
+ light_data.shadow_color3[0] = 0.0;
+ light_data.shadow_color3[1] = 0.0;
+ light_data.shadow_color3[2] = 1.0;
+ light_data.shadow_color3[3] = 1.0;
+ light_data.shadow_color4[0] = 1.0;
+ light_data.shadow_color4[1] = 1.0;
+ light_data.shadow_color4[2] = 0.0;
+ light_data.shadow_color4[3] = 1.0;
+
+ } else {
+ light_data.shadow_color1[0] = shadow_col.r;
+ light_data.shadow_color1[1] = shadow_col.g;
+ light_data.shadow_color1[2] = shadow_col.b;
+ light_data.shadow_color1[3] = 1.0;
+ light_data.shadow_color2[0] = shadow_col.r;
+ light_data.shadow_color2[1] = shadow_col.g;
+ light_data.shadow_color2[2] = shadow_col.b;
+ light_data.shadow_color2[3] = 1.0;
+ light_data.shadow_color3[0] = shadow_col.r;
+ light_data.shadow_color3[1] = shadow_col.g;
+ light_data.shadow_color3[2] = shadow_col.b;
+ light_data.shadow_color3[3] = 1.0;
+ light_data.shadow_color4[0] = shadow_col.r;
+ light_data.shadow_color4[1] = shadow_col.g;
+ light_data.shadow_color4[2] = shadow_col.b;
+ light_data.shadow_color4[3] = 1.0;
+ }
+
+ light_data.shadow_enabled = p_using_shadows && storage->light_has_shadow(base);
+
+ float angular_diameter = storage->light_get_param(base, RS::LIGHT_PARAM_SIZE);
+ if (angular_diameter > 0.0) {
+ // I know tan(0) is 0, but let's not risk it with numerical precision.
+ // technically this will keep expanding until reaching the sun, but all we care
+ // is expand until we reach the radius of the near plane (there can't be more occluders than that)
+ angular_diameter = Math::tan(Math::deg2rad(angular_diameter));
+ } else {
+ angular_diameter = 0.0;
+ }
+
+ if (light_data.shadow_enabled) {
+ RS::LightDirectionalShadowMode smode = storage->light_directional_get_shadow_mode(base);
+
+ int limit = smode == RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL ? 0 : (smode == RS::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_2_SPLITS ? 1 : 3);
+ light_data.blend_splits = storage->light_directional_get_blend_splits(base);
+ for (int j = 0; j < 4; j++) {
+ Rect2 atlas_rect = light_instance_get_directional_shadow_atlas_rect(li, j);
+ CameraMatrix matrix = light_instance_get_shadow_camera(li, j);
+ float split = light_instance_get_directional_shadow_split(li, MIN(limit, j));
+
+ CameraMatrix bias;
+ bias.set_light_bias();
+ CameraMatrix rectm;
+ rectm.set_light_atlas_rect(atlas_rect);
+
+ Transform modelview = (p_camera_inverse_transform * light_instance_get_shadow_transform(li, j)).inverse();
+
+ CameraMatrix shadow_mtx = rectm * bias * matrix * modelview;
+ light_data.shadow_split_offsets[j] = split;
+ float bias_scale = light_instance_get_shadow_bias_scale(li, j);
+ light_data.shadow_bias[j] = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS) * bias_scale;
+ light_data.shadow_normal_bias[j] = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS) * light_instance_get_directional_shadow_texel_size(li, j);
+ light_data.shadow_transmittance_bias[j] = storage->light_get_transmittance_bias(base) * bias_scale;
+ light_data.shadow_transmittance_z_scale[j] = light_instance_get_shadow_range(li, j);
+ light_data.shadow_range_begin[j] = light_instance_get_shadow_range_begin(li, j);
+ RasterizerStorageRD::store_camera(shadow_mtx, light_data.shadow_matrices[j]);
+
+ Vector2 uv_scale = light_instance_get_shadow_uv_scale(li, j);
+ uv_scale *= atlas_rect.size; //adapt to atlas size
+ switch (j) {
+ case 0: {
+ light_data.uv_scale1[0] = uv_scale.x;
+ light_data.uv_scale1[1] = uv_scale.y;
+ } break;
+ case 1: {
+ light_data.uv_scale2[0] = uv_scale.x;
+ light_data.uv_scale2[1] = uv_scale.y;
+ } break;
+ case 2: {
+ light_data.uv_scale3[0] = uv_scale.x;
+ light_data.uv_scale3[1] = uv_scale.y;
+ } break;
+ case 3: {
+ light_data.uv_scale4[0] = uv_scale.x;
+ light_data.uv_scale4[1] = uv_scale.y;
+ } break;
+ }
+ }
+
+ float fade_start = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_FADE_START);
+ light_data.fade_from = -light_data.shadow_split_offsets[3] * MIN(fade_start, 0.999); //using 1.0 would break smoothstep
+ light_data.fade_to = -light_data.shadow_split_offsets[3];
+
+ light_data.soft_shadow_scale = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BLUR);
+ light_data.softshadow_angle = angular_diameter;
+
+ if (angular_diameter <= 0.0) {
+ light_data.soft_shadow_scale *= directional_shadow_quality_radius_get(); // Only use quality radius for PCF
+ }
+ }
+
+ // Copy to SkyDirectionalLightData
+ if (r_directional_light_count < sky_scene_state.max_directional_lights) {
+ SkyDirectionalLightData &sky_light_data = sky_scene_state.directional_lights[r_directional_light_count];
+
+ Vector3 world_direction = light_transform.basis.xform(Vector3(0, 0, 1)).normalized();
+
+ sky_light_data.direction[0] = world_direction.x;
+ sky_light_data.direction[1] = world_direction.y;
+ sky_light_data.direction[2] = -world_direction.z;
+
+ sky_light_data.energy = light_data.energy / Math_PI;
+
+ sky_light_data.color[0] = light_data.color[0];
+ sky_light_data.color[1] = light_data.color[1];
+ sky_light_data.color[2] = light_data.color[2];
+
+ sky_light_data.enabled = true;
+ sky_light_data.size = angular_diameter;
+ sky_scene_state.directional_light_count++;
+ }
+
+ r_directional_light_count++;
+ } break;
+ case RS::LIGHT_SPOT:
+ case RS::LIGHT_OMNI: {
+ if (light_count >= cluster.max_lights) {
+ continue;
+ }
+
+ Transform light_transform = light_instance_get_base_transform(li);
+
+ Cluster::LightData &light_data = cluster.lights[light_count];
+
+ float sign = storage->light_is_negative(base) ? -1 : 1;
+ Color linear_col = storage->light_get_color(base).to_linear();
+
+ light_data.attenuation_energy[0] = Math::make_half_float(storage->light_get_param(base, RS::LIGHT_PARAM_ATTENUATION));
+ light_data.attenuation_energy[1] = Math::make_half_float(sign * storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY) * Math_PI);
+
+ light_data.color_specular[0] = MIN(uint32_t(linear_col.r * 255), 255);
+ light_data.color_specular[1] = MIN(uint32_t(linear_col.g * 255), 255);
+ light_data.color_specular[2] = MIN(uint32_t(linear_col.b * 255), 255);
+ light_data.color_specular[3] = MIN(uint32_t(storage->light_get_param(base, RS::LIGHT_PARAM_SPECULAR) * 255), 255);
+
+ float radius = MAX(0.001, storage->light_get_param(base, RS::LIGHT_PARAM_RANGE));
+ light_data.inv_radius = 1.0 / radius;
+
+ Vector3 pos = p_camera_inverse_transform.xform(light_transform.origin);
+
+ light_data.position[0] = pos.x;
+ light_data.position[1] = pos.y;
+ light_data.position[2] = pos.z;
+
+ Vector3 direction = p_camera_inverse_transform.basis.xform(light_transform.basis.xform(Vector3(0, 0, -1))).normalized();
+
+ light_data.direction[0] = direction.x;
+ light_data.direction[1] = direction.y;
+ light_data.direction[2] = direction.z;
+
+ float size = storage->light_get_param(base, RS::LIGHT_PARAM_SIZE);
+
+ light_data.size = size;
+
+ light_data.cone_attenuation_angle[0] = Math::make_half_float(storage->light_get_param(base, RS::LIGHT_PARAM_SPOT_ATTENUATION));
+ float spot_angle = storage->light_get_param(base, RS::LIGHT_PARAM_SPOT_ANGLE);
+ light_data.cone_attenuation_angle[1] = Math::make_half_float(Math::cos(Math::deg2rad(spot_angle)));
+
+ light_data.mask = storage->light_get_cull_mask(base);
+
+ light_data.atlas_rect[0] = 0;
+ light_data.atlas_rect[1] = 0;
+ light_data.atlas_rect[2] = 0;
+ light_data.atlas_rect[3] = 0;
+
+ RID projector = storage->light_get_projector(base);
+
+ if (projector.is_valid()) {
+ Rect2 rect = storage->decal_atlas_get_texture_rect(projector);
+
+ if (type == RS::LIGHT_SPOT) {
+ light_data.projector_rect[0] = rect.position.x;
+ light_data.projector_rect[1] = rect.position.y + rect.size.height; //flip because shadow is flipped
+ light_data.projector_rect[2] = rect.size.width;
+ light_data.projector_rect[3] = -rect.size.height;
+ } else {
+ light_data.projector_rect[0] = rect.position.x;
+ light_data.projector_rect[1] = rect.position.y;
+ light_data.projector_rect[2] = rect.size.width;
+ light_data.projector_rect[3] = rect.size.height * 0.5; //used by dp, so needs to be half
+ }
+ } else {
+ light_data.projector_rect[0] = 0;
+ light_data.projector_rect[1] = 0;
+ light_data.projector_rect[2] = 0;
+ light_data.projector_rect[3] = 0;
+ }
+
+ if (p_using_shadows && p_shadow_atlas.is_valid() && shadow_atlas_owns_light_instance(p_shadow_atlas, li)) {
+ // fill in the shadow information
+
+ Color shadow_color = storage->light_get_shadow_color(base);
+
+ light_data.shadow_color_enabled[0] = MIN(uint32_t(shadow_color.r * 255), 255);
+ light_data.shadow_color_enabled[1] = MIN(uint32_t(shadow_color.g * 255), 255);
+ light_data.shadow_color_enabled[2] = MIN(uint32_t(shadow_color.b * 255), 255);
+ light_data.shadow_color_enabled[3] = 255;
+
+ if (type == RS::LIGHT_SPOT) {
+ light_data.shadow_bias = (storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS) * radius / 10.0);
+ float shadow_texel_size = Math::tan(Math::deg2rad(spot_angle)) * radius * 2.0;
+ shadow_texel_size *= light_instance_get_shadow_texel_size(li, p_shadow_atlas);
+
+ light_data.shadow_normal_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS) * shadow_texel_size;
+
+ } else { //omni
+ light_data.shadow_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS) * radius / 10.0;
+ float shadow_texel_size = light_instance_get_shadow_texel_size(li, p_shadow_atlas);
+ light_data.shadow_normal_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS) * shadow_texel_size * 2.0; // applied in -1 .. 1 space
+ }
+
+ light_data.transmittance_bias = storage->light_get_transmittance_bias(base);
+
+ Rect2 rect = light_instance_get_shadow_atlas_rect(li, p_shadow_atlas);
+
+ light_data.atlas_rect[0] = rect.position.x;
+ light_data.atlas_rect[1] = rect.position.y;
+ light_data.atlas_rect[2] = rect.size.width;
+ light_data.atlas_rect[3] = rect.size.height;
+
+ light_data.soft_shadow_scale = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BLUR);
+
+ if (type == RS::LIGHT_OMNI) {
+ light_data.atlas_rect[3] *= 0.5; //one paraboloid on top of another
+ Transform proj = (p_camera_inverse_transform * light_transform).inverse();
+
+ RasterizerStorageRD::store_transform(proj, light_data.shadow_matrix);
+
+ if (size > 0.0) {
+ light_data.soft_shadow_size = size;
+ } else {
+ light_data.soft_shadow_size = 0.0;
+ light_data.soft_shadow_scale *= shadows_quality_radius_get(); // Only use quality radius for PCF
+ }
+
+ } else if (type == RS::LIGHT_SPOT) {
+ Transform modelview = (p_camera_inverse_transform * light_transform).inverse();
+ CameraMatrix bias;
+ bias.set_light_bias();
+
+ CameraMatrix shadow_mtx = bias * light_instance_get_shadow_camera(li, 0) * modelview;
+ RasterizerStorageRD::store_camera(shadow_mtx, light_data.shadow_matrix);
+
+ if (size > 0.0) {
+ CameraMatrix cm = light_instance_get_shadow_camera(li, 0);
+ float half_np = cm.get_z_near() * Math::tan(Math::deg2rad(spot_angle));
+ light_data.soft_shadow_size = (size * 0.5 / radius) / (half_np / cm.get_z_near()) * rect.size.width;
+ } else {
+ light_data.soft_shadow_size = 0.0;
+ light_data.soft_shadow_scale *= shadows_quality_radius_get(); // Only use quality radius for PCF
+ }
+ }
+ } else {
+ light_data.shadow_color_enabled[3] = 0;
+ }
+
+ light_instance_set_index(li, light_count);
+
+ cluster.builder.add_light(type == RS::LIGHT_SPOT ? LightClusterBuilder::LIGHT_TYPE_SPOT : LightClusterBuilder::LIGHT_TYPE_OMNI, light_transform, radius, spot_angle);
+
+ light_count++;
+ } break;
+ }
+
+ light_instance_set_render_pass(li, RSG::rasterizer->get_frame_number());
+
+ //update UBO for forward rendering, blit to texture for clustered
+ }
+
+ if (light_count) {
+ RD::get_singleton()->buffer_update(cluster.light_buffer, 0, sizeof(Cluster::LightData) * light_count, cluster.lights, true);
+ }
+
+ if (r_directional_light_count) {
+ RD::get_singleton()->buffer_update(cluster.directional_light_buffer, 0, sizeof(Cluster::DirectionalLightData) * r_directional_light_count, cluster.directional_lights, true);
+ }
+}
+
+void RasterizerSceneRD::_setup_decals(const RID *p_decal_instances, int p_decal_count, const Transform &p_camera_inverse_xform) {
+ Transform uv_xform;
+ uv_xform.basis.scale(Vector3(2.0, 1.0, 2.0));
+ uv_xform.origin = Vector3(-1.0, 0.0, -1.0);
+
+ p_decal_count = MIN((uint32_t)p_decal_count, cluster.max_decals);
+ int idx = 0;
+ for (int i = 0; i < p_decal_count; i++) {
+ RID di = p_decal_instances[i];
+ RID decal = decal_instance_get_base(di);
+
+ Transform xform = decal_instance_get_transform(di);
+
+ float fade = 1.0;
+
+ if (storage->decal_is_distance_fade_enabled(decal)) {
+ real_t distance = -p_camera_inverse_xform.xform(xform.origin).z;
+ float fade_begin = storage->decal_get_distance_fade_begin(decal);
+ float fade_length = storage->decal_get_distance_fade_length(decal);
+
+ if (distance > fade_begin) {
+ if (distance > fade_begin + fade_length) {
+ continue; // do not use this decal, its invisible
+ }
+
+ fade = 1.0 - (distance - fade_begin) / fade_length;
+ }
+ }
+
+ Cluster::DecalData &dd = cluster.decals[idx];
+
+ Vector3 decal_extents = storage->decal_get_extents(decal);
+
+ Transform scale_xform;
+ scale_xform.basis.scale(Vector3(decal_extents.x, decal_extents.y, decal_extents.z));
+ Transform to_decal_xform = (p_camera_inverse_xform * decal_instance_get_transform(di) * scale_xform * uv_xform).affine_inverse();
+ RasterizerStorageRD::store_transform(to_decal_xform, dd.xform);
+
+ Vector3 normal = xform.basis.get_axis(Vector3::AXIS_Y).normalized();
+ normal = p_camera_inverse_xform.basis.xform(normal); //camera is normalized, so fine
+
+ dd.normal[0] = normal.x;
+ dd.normal[1] = normal.y;
+ dd.normal[2] = normal.z;
+ dd.normal_fade = storage->decal_get_normal_fade(decal);
+
+ RID albedo_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_ALBEDO);
+ RID emission_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_EMISSION);
+ if (albedo_tex.is_valid()) {
+ Rect2 rect = storage->decal_atlas_get_texture_rect(albedo_tex);
+ dd.albedo_rect[0] = rect.position.x;
+ dd.albedo_rect[1] = rect.position.y;
+ dd.albedo_rect[2] = rect.size.x;
+ dd.albedo_rect[3] = rect.size.y;
+ } else {
+ if (!emission_tex.is_valid()) {
+ continue; //no albedo, no emission, no decal.
+ }
+ dd.albedo_rect[0] = 0;
+ dd.albedo_rect[1] = 0;
+ dd.albedo_rect[2] = 0;
+ dd.albedo_rect[3] = 0;
+ }
+
+ RID normal_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_NORMAL);
+
+ if (normal_tex.is_valid()) {
+ Rect2 rect = storage->decal_atlas_get_texture_rect(normal_tex);
+ dd.normal_rect[0] = rect.position.x;
+ dd.normal_rect[1] = rect.position.y;
+ dd.normal_rect[2] = rect.size.x;
+ dd.normal_rect[3] = rect.size.y;
+
+ Basis normal_xform = p_camera_inverse_xform.basis * xform.basis.orthonormalized();
+ RasterizerStorageRD::store_basis_3x4(normal_xform, dd.normal_xform);
+ } else {
+ dd.normal_rect[0] = 0;
+ dd.normal_rect[1] = 0;
+ dd.normal_rect[2] = 0;
+ dd.normal_rect[3] = 0;
+ }
+
+ RID orm_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_ORM);
+ if (orm_tex.is_valid()) {
+ Rect2 rect = storage->decal_atlas_get_texture_rect(orm_tex);
+ dd.orm_rect[0] = rect.position.x;
+ dd.orm_rect[1] = rect.position.y;
+ dd.orm_rect[2] = rect.size.x;
+ dd.orm_rect[3] = rect.size.y;
+ } else {
+ dd.orm_rect[0] = 0;
+ dd.orm_rect[1] = 0;
+ dd.orm_rect[2] = 0;
+ dd.orm_rect[3] = 0;
+ }
+
+ if (emission_tex.is_valid()) {
+ Rect2 rect = storage->decal_atlas_get_texture_rect(emission_tex);
+ dd.emission_rect[0] = rect.position.x;
+ dd.emission_rect[1] = rect.position.y;
+ dd.emission_rect[2] = rect.size.x;
+ dd.emission_rect[3] = rect.size.y;
+ } else {
+ dd.emission_rect[0] = 0;
+ dd.emission_rect[1] = 0;
+ dd.emission_rect[2] = 0;
+ dd.emission_rect[3] = 0;
+ }
+
+ Color modulate = storage->decal_get_modulate(decal);
+ dd.modulate[0] = modulate.r;
+ dd.modulate[1] = modulate.g;
+ dd.modulate[2] = modulate.b;
+ dd.modulate[3] = modulate.a * fade;
+ dd.emission_energy = storage->decal_get_emission_energy(decal) * fade;
+ dd.albedo_mix = storage->decal_get_albedo_mix(decal);
+ dd.mask = storage->decal_get_cull_mask(decal);
+ dd.upper_fade = storage->decal_get_upper_fade(decal);
+ dd.lower_fade = storage->decal_get_lower_fade(decal);
+
+ cluster.builder.add_decal(xform, decal_extents);
+
+ idx++;
+ }
+
+ if (idx > 0) {
+ RD::get_singleton()->buffer_update(cluster.decal_buffer, 0, sizeof(Cluster::DecalData) * idx, cluster.decals, true);
+ }
+}
+
void RasterizerSceneRD::render_scene(RID p_render_buffers, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID *p_decal_cull_result, int p_decal_cull_count, InstanceBase **p_lightmap_cull_result, int p_lightmap_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass) {
Color clear_color;
if (p_render_buffers.is_valid()) {
@@ -5637,7 +6170,31 @@ void RasterizerSceneRD::render_scene(RID p_render_buffers, const Transform &p_ca
}
}
- _render_scene(p_render_buffers, p_cam_transform, p_cam_projection, p_cam_ortogonal, p_cull_result, p_cull_count, p_light_cull_result, p_light_cull_count, p_reflection_probe_cull_result, p_reflection_probe_cull_count, p_gi_probe_cull_result, p_gi_probe_cull_count, p_decal_cull_result, p_decal_cull_count, p_lightmap_cull_result, p_lightmap_cull_count, p_environment, p_camera_effects, p_shadow_atlas, p_reflection_atlas, p_reflection_probe, p_reflection_probe_pass, clear_color);
+ if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_UNSHADED) {
+ p_light_cull_count = 0;
+ p_reflection_probe_cull_count = 0;
+ p_gi_probe_cull_count = 0;
+ }
+
+ cluster.builder.begin(p_cam_transform.affine_inverse(), p_cam_projection); //prepare cluster
+
+ bool using_shadows = true;
+
+ if (p_reflection_probe.is_valid()) {
+ if (!storage->reflection_probe_renders_shadows(reflection_probe_instance_get_probe(p_reflection_probe))) {
+ using_shadows = false;
+ }
+ } else {
+ //do not render reflections when rendering a reflection probe
+ _setup_reflections(p_reflection_probe_cull_result, p_reflection_probe_cull_count, p_cam_transform.affine_inverse(), p_environment);
+ }
+
+ uint32_t directional_light_count = 0;
+ _setup_lights(p_light_cull_result, p_light_cull_count, p_cam_transform.affine_inverse(), p_shadow_atlas, using_shadows, directional_light_count);
+ _setup_decals(p_decal_cull_result, p_decal_cull_count, p_cam_transform.affine_inverse());
+ cluster.builder.bake_cluster(); //bake to cluster
+
+ _render_scene(p_render_buffers, p_cam_transform, p_cam_projection, p_cam_ortogonal, p_cull_result, p_cull_count, directional_light_count, p_gi_probe_cull_result, p_gi_probe_cull_count, p_lightmap_cull_result, p_lightmap_cull_count, p_environment, p_camera_effects, p_shadow_atlas, p_reflection_atlas, p_reflection_probe, p_reflection_probe_pass, clear_color);
if (p_render_buffers.is_valid()) {
RENDER_TIMESTAMP("Tonemap");
@@ -5962,7 +6519,7 @@ void RasterizerSceneRD::render_sdfgi(RID p_render_buffers, int p_region, Instanc
uint32_t dispatch_indirct_data[4] = { 0, 0, 0, 0 };
RD::get_singleton()->buffer_update(rb->sdfgi->cascades[cascade].solid_cell_dispatch_buffer, 0, sizeof(uint32_t) * 4, dispatch_indirct_data, true);
- bool half_size = true; //much faster, very little differnce
+ bool half_size = true; //much faster, very little difference
static const int optimized_jf_group_size = 8;
if (half_size) {
@@ -6112,7 +6669,7 @@ void RasterizerSceneRD::render_sdfgi(RID p_render_buffers, int p_region, Instanc
push_constant.occlusion_index = i;
RD::get_singleton()->compute_list_set_push_constant(compute_list, &push_constant, sizeof(SDGIShader::PreprocessPushConstant));
- Vector3i groups = Vector3i(probe_size + 1, probe_size + 1, probe_size + 1) - offset; //if offseted, its one less probe per axis to compute
+ Vector3i groups = Vector3i(probe_size + 1, probe_size + 1, probe_size + 1) - offset; //if offset, it's one less probe per axis to compute
RD::get_singleton()->compute_list_dispatch(compute_list, groups.x, groups.y, groups.z);
}
RD::get_singleton()->compute_list_add_barrier(compute_list);
@@ -6496,6 +7053,30 @@ void RasterizerSceneRD::sdfgi_set_debug_probe_select(const Vector3 &p_position,
RasterizerSceneRD *RasterizerSceneRD::singleton = nullptr;
+RID RasterizerSceneRD::get_cluster_builder_texture() {
+ return cluster.builder.get_cluster_texture();
+}
+
+RID RasterizerSceneRD::get_cluster_builder_indices_buffer() {
+ return cluster.builder.get_cluster_indices_buffer();
+}
+
+RID RasterizerSceneRD::get_reflection_probe_buffer() {
+ return cluster.reflection_buffer;
+}
+RID RasterizerSceneRD::get_positional_light_buffer() {
+ return cluster.light_buffer;
+}
+RID RasterizerSceneRD::get_directional_light_buffer() {
+ return cluster.directional_light_buffer;
+}
+RID RasterizerSceneRD::get_decal_buffer() {
+ return cluster.decal_buffer;
+}
+int RasterizerSceneRD::get_max_directional_lights() const {
+ return cluster.max_directional_lights;
+}
+
RasterizerSceneRD::RasterizerSceneRD(RasterizerStorageRD *p_storage) {
storage = p_storage;
singleton = this;
@@ -6802,6 +7383,45 @@ RasterizerSceneRD::RasterizerSceneRD(RasterizerStorageRD *p_storage) {
}
}
+ //cluster setup
+ uint32_t uniform_max_size = RD::get_singleton()->limit_get(RD::LIMIT_MAX_UNIFORM_BUFFER_SIZE);
+
+ { //reflections
+ uint32_t reflection_buffer_size;
+ if (uniform_max_size < 65536) {
+ //Yes, you guessed right, ARM again
+ reflection_buffer_size = uniform_max_size;
+ } else {
+ reflection_buffer_size = 65536;
+ }
+
+ cluster.max_reflections = reflection_buffer_size / sizeof(Cluster::ReflectionData);
+ cluster.reflections = memnew_arr(Cluster::ReflectionData, cluster.max_reflections);
+ cluster.reflection_buffer = RD::get_singleton()->storage_buffer_create(reflection_buffer_size);
+ }
+
+ { //lights
+ cluster.max_lights = MIN(1024 * 1024, uniform_max_size) / sizeof(Cluster::LightData); //1mb of lights
+ uint32_t light_buffer_size = cluster.max_lights * sizeof(Cluster::LightData);
+ cluster.lights = memnew_arr(Cluster::LightData, cluster.max_lights);
+ cluster.light_buffer = RD::get_singleton()->storage_buffer_create(light_buffer_size);
+ //defines += "\n#define MAX_LIGHT_DATA_STRUCTS " + itos(cluster.max_lights) + "\n";
+
+ cluster.max_directional_lights = 8;
+ uint32_t directional_light_buffer_size = cluster.max_directional_lights * sizeof(Cluster::DirectionalLightData);
+ cluster.directional_lights = memnew_arr(Cluster::DirectionalLightData, cluster.max_directional_lights);
+ cluster.directional_light_buffer = RD::get_singleton()->uniform_buffer_create(directional_light_buffer_size);
+ }
+
+ { //decals
+ cluster.max_decals = MIN(1024 * 1024, uniform_max_size) / sizeof(Cluster::DecalData); //1mb of decals
+ uint32_t decal_buffer_size = cluster.max_decals * sizeof(Cluster::DecalData);
+ cluster.decals = memnew_arr(Cluster::DecalData, cluster.max_decals);
+ cluster.decal_buffer = RD::get_singleton()->storage_buffer_create(decal_buffer_size);
+ }
+
+ cluster.builder.setup(16, 8, 24);
+
default_giprobe_buffer = RD::get_singleton()->uniform_buffer_create(sizeof(GI::GIProbeData) * RenderBuffers::MAX_GIPROBES);
camera_effects_set_dof_blur_bokeh_shape(RS::DOFBokehShape(int(GLOBAL_GET("rendering/quality/depth_of_field/depth_of_field_bokeh_shape"))));
@@ -6863,4 +7483,15 @@ RasterizerSceneRD::~RasterizerSceneRD() {
memdelete_arr(directional_soft_shadow_kernel);
memdelete_arr(penumbra_shadow_kernel);
memdelete_arr(soft_shadow_kernel);
+
+ {
+ RD::get_singleton()->free(cluster.directional_light_buffer);
+ RD::get_singleton()->free(cluster.light_buffer);
+ RD::get_singleton()->free(cluster.reflection_buffer);
+ RD::get_singleton()->free(cluster.decal_buffer);
+ memdelete_arr(cluster.directional_lights);
+ memdelete_arr(cluster.lights);
+ memdelete_arr(cluster.reflections);
+ memdelete_arr(cluster.decals);
+ }
}
diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h
index f09f9dde65..27eec44ec3 100644
--- a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h
+++ b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h
@@ -34,6 +34,7 @@
#include "core/local_vector.h"
#include "core/rid_owner.h"
#include "servers/rendering/rasterizer.h"
+#include "servers/rendering/rasterizer_rd/light_cluster_builder.h"
#include "servers/rendering/rasterizer_rd/rasterizer_storage_rd.h"
#include "servers/rendering/rasterizer_rd/shaders/gi.glsl.gen.h"
#include "servers/rendering/rasterizer_rd/shaders/giprobe.glsl.gen.h"
@@ -77,7 +78,11 @@ protected:
};
virtual RenderBufferData *_create_render_buffer_data() = 0;
- virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID *p_decal_cull_result, int p_decal_cull_count, InstanceBase **p_lightmap_cull_result, int p_lightmap_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_color) = 0;
+ void _setup_lights(RID *p_light_cull_result, int p_light_cull_count, const Transform &p_camera_inverse_transform, RID p_shadow_atlas, bool p_using_shadows, uint32_t &r_directional_light_count);
+ void _setup_decals(const RID *p_decal_instances, int p_decal_count, const Transform &p_camera_inverse_xform);
+ void _setup_reflections(RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, const Transform &p_camera_inverse_transform, RID p_environment);
+
+ virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, int p_directional_light_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, InstanceBase **p_lightmap_cull_result, int p_lightmap_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_color) = 0;
virtual void _render_shadow(RID p_framebuffer, InstanceBase **p_cull_result, int p_cull_count, const CameraMatrix &p_projection, const Transform &p_transform, float p_zfar, float p_bias, float p_normal_bias, bool p_use_dp, bool use_dp_flip, bool p_use_pancake) = 0;
virtual void _render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region) = 0;
virtual void _render_uv2(InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region) = 0;
@@ -623,7 +628,7 @@ private:
/* ENVIRONMENT */
- struct Environent {
+ struct Environment {
// BG
RS::EnvironmentBG background = RS::ENV_BG_CLEAR_COLOR;
RID sky;
@@ -703,7 +708,7 @@ private:
static uint64_t auto_exposure_counter;
- mutable RID_Owner<Environent> environment_owner;
+ mutable RID_Owner<Environment> environment_owner;
/* CAMERA EFFECTS */
@@ -1181,6 +1186,112 @@ private:
void _render_buffers_post_process_and_tonemap(RID p_render_buffers, RID p_environment, RID p_camera_effects, const CameraMatrix &p_projection);
void _sdfgi_debug_draw(RID p_render_buffers, const CameraMatrix &p_projection, const Transform &p_transform);
+ /* Cluster */
+
+ struct Cluster {
+ /* Scene State UBO */
+
+ struct ReflectionData { //should always be 128 bytes
+ float box_extents[3];
+ float index;
+ float box_offset[3];
+ uint32_t mask;
+ float params[4]; // intensity, 0, interior , boxproject
+ float ambient[3]; // ambient color,
+ uint32_t ambient_mode;
+ float local_matrix[16]; // up to here for spot and omni, rest is for directional
+ };
+
+ struct LightData {
+ float position[3];
+ float inv_radius;
+ float direction[3];
+ float size;
+ uint16_t attenuation_energy[2]; //16 bits attenuation, then energy
+ uint8_t color_specular[4]; //rgb color, a specular (8 bit unorm)
+ uint16_t cone_attenuation_angle[2]; // attenuation and angle, (16bit float)
+ uint8_t shadow_color_enabled[4]; //shadow rgb color, a>0.5 enabled (8bit unorm)
+ float atlas_rect[4]; // in omni, used for atlas uv, in spot, used for projector uv
+ float shadow_matrix[16];
+ float shadow_bias;
+ float shadow_normal_bias;
+ float transmittance_bias;
+ float soft_shadow_size;
+ float soft_shadow_scale;
+ uint32_t mask;
+ uint32_t pad[2];
+ float projector_rect[4];
+ };
+
+ struct DirectionalLightData {
+ float direction[3];
+ float energy;
+ float color[3];
+ float size;
+ float specular;
+ uint32_t mask;
+ float softshadow_angle;
+ float soft_shadow_scale;
+ uint32_t blend_splits;
+ uint32_t shadow_enabled;
+ float fade_from;
+ float fade_to;
+ float shadow_bias[4];
+ float shadow_normal_bias[4];
+ float shadow_transmittance_bias[4];
+ float shadow_transmittance_z_scale[4];
+ float shadow_range_begin[4];
+ float shadow_split_offsets[4];
+ float shadow_matrices[4][16];
+ float shadow_color1[4];
+ float shadow_color2[4];
+ float shadow_color3[4];
+ float shadow_color4[4];
+ float uv_scale1[2];
+ float uv_scale2[2];
+ float uv_scale3[2];
+ float uv_scale4[2];
+ };
+
+ struct DecalData {
+ float xform[16];
+ float inv_extents[3];
+ float albedo_mix;
+ float albedo_rect[4];
+ float normal_rect[4];
+ float orm_rect[4];
+ float emission_rect[4];
+ float modulate[4];
+ float emission_energy;
+ uint32_t mask;
+ float upper_fade;
+ float lower_fade;
+ float normal_xform[12];
+ float normal[3];
+ float normal_fade;
+ };
+
+ ReflectionData *reflections;
+ uint32_t max_reflections;
+ RID reflection_buffer;
+ uint32_t max_reflection_probes_per_instance;
+
+ DecalData *decals;
+ uint32_t max_decals;
+ RID decal_buffer;
+
+ LightData *lights;
+ uint32_t max_lights;
+ RID light_buffer;
+
+ DirectionalLightData *directional_lights;
+ uint32_t max_directional_lights;
+ RID directional_light_buffer;
+
+ LightClusterBuilder builder;
+
+ } cluster;
+
uint64_t scene_pass = 0;
uint64_t shadow_atlas_realloc_tolerance_msec = 500;
@@ -1655,6 +1766,14 @@ public:
virtual void set_time(double p_time, double p_step);
+ RID get_cluster_builder_texture();
+ RID get_cluster_builder_indices_buffer();
+ RID get_reflection_probe_buffer();
+ RID get_positional_light_buffer();
+ RID get_directional_light_buffer();
+ RID get_decal_buffer();
+ int get_max_directional_lights() const;
+
void sdfgi_set_debug_probe_select(const Vector3 &p_position, const Vector3 &p_dir);
RasterizerSceneRD(RasterizerStorageRD *p_storage);
diff --git a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp
index 8f3e2c25f9..102e0e2eed 100644
--- a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp
+++ b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp
@@ -899,10 +899,10 @@ Ref<Image> RasterizerStorageRD::texture_3d_slice_get(RID p_texture, int p_depth,
void RasterizerStorageRD::texture_replace(RID p_texture, RID p_by_texture) {
Texture *tex = texture_owner.getornull(p_texture);
ERR_FAIL_COND(!tex);
- ERR_FAIL_COND(tex->proxy_to.is_valid()); //cant replace proxy
+ ERR_FAIL_COND(tex->proxy_to.is_valid()); //can't replace proxy
Texture *by_tex = texture_owner.getornull(p_by_texture);
ERR_FAIL_COND(!by_tex);
- ERR_FAIL_COND(by_tex->proxy_to.is_valid()); //cant replace proxy
+ ERR_FAIL_COND(by_tex->proxy_to.is_valid()); //can't replace proxy
if (tex == by_tex) {
return;
@@ -5276,7 +5276,7 @@ void RasterizerStorageRD::global_variable_add(const StringName &p_name, RS::Glob
if (p_type >= RS::GLOBAL_VAR_TYPE_SAMPLER2D) {
//is texture
- global_variables.must_update_texture_materials = true; //normally ther are no
+ global_variables.must_update_texture_materials = true; //normally there are none
} else {
gv.buffer_elements = 1;
if (p_type == RS::GLOBAL_VAR_TYPE_COLOR || p_type == RS::GLOBAL_VAR_TYPE_MAT2) {
@@ -5299,7 +5299,7 @@ void RasterizerStorageRD::global_variable_add(const StringName &p_name, RS::Glob
_global_variable_store_in_buffer(gv.buffer_index, gv.type, gv.value);
_global_variable_mark_buffer_dirty(gv.buffer_index, gv.buffer_elements);
- global_variables.must_update_buffer_materials = true; //normally ther are no
+ global_variables.must_update_buffer_materials = true; //normally there are none
}
global_variables.variables[p_name] = gv;
diff --git a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h
index b1146f1386..b7aedf8717 100644
--- a/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h
+++ b/servers/rendering/rasterizer_rd/rasterizer_storage_rd.h
@@ -40,6 +40,69 @@
class RasterizerStorageRD : public RasterizerStorage {
public:
+ static _FORCE_INLINE_ void store_transform(const Transform &p_mtx, float *p_array) {
+ p_array[0] = p_mtx.basis.elements[0][0];
+ p_array[1] = p_mtx.basis.elements[1][0];
+ p_array[2] = p_mtx.basis.elements[2][0];
+ p_array[3] = 0;
+ p_array[4] = p_mtx.basis.elements[0][1];
+ p_array[5] = p_mtx.basis.elements[1][1];
+ p_array[6] = p_mtx.basis.elements[2][1];
+ p_array[7] = 0;
+ p_array[8] = p_mtx.basis.elements[0][2];
+ p_array[9] = p_mtx.basis.elements[1][2];
+ p_array[10] = p_mtx.basis.elements[2][2];
+ p_array[11] = 0;
+ p_array[12] = p_mtx.origin.x;
+ p_array[13] = p_mtx.origin.y;
+ p_array[14] = p_mtx.origin.z;
+ p_array[15] = 1;
+ }
+
+ static _FORCE_INLINE_ void store_basis_3x4(const Basis &p_mtx, float *p_array) {
+ p_array[0] = p_mtx.elements[0][0];
+ p_array[1] = p_mtx.elements[1][0];
+ p_array[2] = p_mtx.elements[2][0];
+ p_array[3] = 0;
+ p_array[4] = p_mtx.elements[0][1];
+ p_array[5] = p_mtx.elements[1][1];
+ p_array[6] = p_mtx.elements[2][1];
+ p_array[7] = 0;
+ p_array[8] = p_mtx.elements[0][2];
+ p_array[9] = p_mtx.elements[1][2];
+ p_array[10] = p_mtx.elements[2][2];
+ p_array[11] = 0;
+ }
+
+ static _FORCE_INLINE_ void store_transform_3x3(const Basis &p_mtx, float *p_array) {
+ p_array[0] = p_mtx.elements[0][0];
+ p_array[1] = p_mtx.elements[1][0];
+ p_array[2] = p_mtx.elements[2][0];
+ p_array[3] = 0;
+ p_array[4] = p_mtx.elements[0][1];
+ p_array[5] = p_mtx.elements[1][1];
+ p_array[6] = p_mtx.elements[2][1];
+ p_array[7] = 0;
+ p_array[8] = p_mtx.elements[0][2];
+ p_array[9] = p_mtx.elements[1][2];
+ p_array[10] = p_mtx.elements[2][2];
+ p_array[11] = 0;
+ }
+
+ static _FORCE_INLINE_ void store_camera(const CameraMatrix &p_mtx, float *p_array) {
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ p_array[i * 4 + j] = p_mtx.matrix[i][j];
+ }
+ }
+ }
+
+ static _FORCE_INLINE_ void store_soft_shadow_kernel(const float *p_kernel, float *p_array) {
+ for (int i = 0; i < 128; i++) {
+ p_array[i] = p_kernel[i];
+ }
+ }
+
enum ShaderType {
SHADER_TYPE_2D,
SHADER_TYPE_3D,
diff --git a/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl b/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl
index d6a56b2543..792a1aa05f 100644
--- a/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl
+++ b/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl
@@ -2685,7 +2685,7 @@ FRAGMENT_SHADER_CODE
frag_color = vec4(albedo, alpha);
#else
frag_color = vec4(emission + ambient_light + diffuse_light + specular_light, alpha);
- //frag_color = vec4(1.0);
+ //frag_color = vec4(1.0);;;
#endif //USE_NO_SHADING
diff --git a/servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl b/servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl
index 1244599097..c4dc7bd675 100644
--- a/servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl
+++ b/servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl
@@ -205,8 +205,8 @@ struct ReflectionData {
// notes: for ambientblend, use distance to edge to blend between already existing global environment
};
-layout(set = 0, binding = 6, std140) uniform ReflectionProbeData {
- ReflectionData data[MAX_REFLECTION_DATA_STRUCTS];
+layout(set = 0, binding = 6) buffer restrict readonly ReflectionProbeData {
+ ReflectionData data[];
}
reflections;
diff --git a/servers/rendering/rendering_device_binds.cpp b/servers/rendering/rendering_device_binds.cpp
index 0400cebfdc..af9ecef0dd 100644
--- a/servers/rendering/rendering_device_binds.cpp
+++ b/servers/rendering/rendering_device_binds.cpp
@@ -135,7 +135,7 @@ Error RDShaderFile::parse_versions_from_text(const String &p_text, const String
//process include
String include = line.replace("#include", "").strip_edges();
if (!include.begins_with("\"") || !include.ends_with("\"")) {
- base_error = "Malformed #include syntax, expected #include \"<path>\", found instad: " + include;
+ base_error = "Malformed #include syntax, expected #include \"<path>\", found instead: " + include;
break;
}
include = include.substr(1, include.length() - 2).strip_edges();
diff --git a/servers/rendering/rendering_device_binds.h b/servers/rendering/rendering_device_binds.h
index 319c6d9fde..66c6a1c3a9 100644
--- a/servers/rendering/rendering_device_binds.h
+++ b/servers/rendering/rendering_device_binds.h
@@ -620,7 +620,7 @@ protected:
RD_BIND(Variant::INT, RDPipelineColorBlendState, logic_op);
RD_BIND(Variant::COLOR, RDPipelineColorBlendState, blend_constant);
- ClassDB::bind_method(D_METHOD("set_attachments", "atachments"), &RDPipelineColorBlendState::set_attachments);
+ ClassDB::bind_method(D_METHOD("set_attachments", "attachments"), &RDPipelineColorBlendState::set_attachments);
ClassDB::bind_method(D_METHOD("get_attachments"), &RDPipelineColorBlendState::get_attachments);
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "attachments", PROPERTY_HINT_ARRAY_TYPE, "RDPipelineColorBlendStateAttachment"), "set_attachments", "get_attachments");
}
diff --git a/servers/rendering/rendering_server_scene.cpp b/servers/rendering/rendering_server_scene.cpp
index 75a5834791..2024f5b983 100644
--- a/servers/rendering/rendering_server_scene.cpp
+++ b/servers/rendering/rendering_server_scene.cpp
@@ -370,8 +370,8 @@ void RenderingServerScene::instance_set_base(RID p_instance, RID p_base) {
case RS::INSTANCE_LIGHT: {
InstanceLightData *light = static_cast<InstanceLightData *>(instance->base_data);
- if (RSG::storage->light_get_type(instance->base) != RS::LIGHT_DIRECTIONAL && light->bake_mode == RS::LIGHT_BAKE_DYNAMIC) {
- instance->scenario->dynamic_lights.erase(light->instance);
+ if (scenario && RSG::storage->light_get_type(instance->base) != RS::LIGHT_DIRECTIONAL && light->bake_mode == RS::LIGHT_BAKE_DYNAMIC) {
+ scenario->dynamic_lights.erase(light->instance);
}
#ifdef DEBUG_ENABLED
@@ -379,8 +379,8 @@ void RenderingServerScene::instance_set_base(RID p_instance, RID p_base) {
ERR_PRINT("BUG, indexing did not unpair geometries from light.");
}
#endif
- if (instance->scenario && light->D) {
- instance->scenario->directional_lights.erase(light->D);
+ if (scenario && light->D) {
+ scenario->directional_lights.erase(light->D);
light->D = nullptr;
}
RSG::scene_render->free(light->instance);
@@ -986,13 +986,13 @@ void RenderingServerScene::_update_instance(Instance *p_instance) {
RS::LightBakeMode bake_mode = RSG::storage->light_get_bake_mode(p_instance->base);
if (RSG::storage->light_get_type(p_instance->base) != RS::LIGHT_DIRECTIONAL && bake_mode != light->bake_mode) {
- if (light->bake_mode == RS::LIGHT_BAKE_DYNAMIC) {
+ if (p_instance->scenario && light->bake_mode == RS::LIGHT_BAKE_DYNAMIC) {
p_instance->scenario->dynamic_lights.erase(light->instance);
}
light->bake_mode = bake_mode;
- if (light->bake_mode == RS::LIGHT_BAKE_DYNAMIC) {
+ if (p_instance->scenario && light->bake_mode == RS::LIGHT_BAKE_DYNAMIC) {
p_instance->scenario->dynamic_lights.push_back(light->instance);
}
}
diff --git a/main/tests/SCsub b/tests/SCsub
index cb1d35b12f..cb1d35b12f 100644
--- a/main/tests/SCsub
+++ b/tests/SCsub
diff --git a/main/tests/test_astar.cpp b/tests/test_astar.cpp
index cb5fcfe37b..cb5fcfe37b 100644
--- a/main/tests/test_astar.cpp
+++ b/tests/test_astar.cpp
diff --git a/main/tests/test_astar.h b/tests/test_astar.h
index 0992812c18..0992812c18 100644
--- a/main/tests/test_astar.h
+++ b/tests/test_astar.h
diff --git a/main/tests/test_basis.cpp b/tests/test_basis.cpp
index 5904fc386a..5904fc386a 100644
--- a/main/tests/test_basis.cpp
+++ b/tests/test_basis.cpp
diff --git a/main/tests/test_basis.h b/tests/test_basis.h
index 63297bd3b8..63297bd3b8 100644
--- a/main/tests/test_basis.h
+++ b/tests/test_basis.h
diff --git a/main/tests/test_class_db.cpp b/tests/test_class_db.cpp
index 3171091402..3171091402 100644
--- a/main/tests/test_class_db.cpp
+++ b/tests/test_class_db.cpp
diff --git a/main/tests/test_class_db.h b/tests/test_class_db.h
index 1a31cfb01b..1a31cfb01b 100644
--- a/main/tests/test_class_db.h
+++ b/tests/test_class_db.h
diff --git a/tests/test_gdscript.cpp b/tests/test_gdscript.cpp
new file mode 100644
index 0000000000..a50311972f
--- /dev/null
+++ b/tests/test_gdscript.cpp
@@ -0,0 +1,187 @@
+/*************************************************************************/
+/* test_gdscript.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "test_gdscript.h"
+
+#include "core/os/file_access.h"
+#include "core/os/main_loop.h"
+#include "core/os/os.h"
+#include "core/string_builder.h"
+
+#include "modules/modules_enabled.gen.h"
+#ifdef MODULE_GDSCRIPT_ENABLED
+
+#include "modules/gdscript/gdscript_parser.h"
+#include "modules/gdscript/gdscript_tokenizer.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif
+
+namespace TestGDScript {
+
+static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) {
+ GDScriptTokenizer tokenizer;
+ tokenizer.set_source_code(p_code);
+
+ int tab_size = 4;
+#ifdef TOOLS_ENABLED
+ if (EditorSettings::get_singleton()) {
+ tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size");
+ }
+#endif // TOOLS_ENABLED
+ String tab = String(" ").repeat(tab_size);
+
+ GDScriptTokenizer::Token current = tokenizer.scan();
+ while (current.type != GDScriptTokenizer::Token::TK_EOF) {
+ StringBuilder token;
+ token += " --> "; // Padding for line number.
+
+ for (int l = current.start_line; l <= current.end_line; l++) {
+ print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));
+ }
+
+ {
+ // Print carets to point at the token.
+ StringBuilder pointer;
+ pointer += " "; // Padding for line number.
+ int rightmost_column = current.rightmost_column;
+ if (current.end_line > current.start_line) {
+ rightmost_column--; // Don't point to the newline as a column.
+ }
+ for (int col = 1; col < rightmost_column; col++) {
+ if (col < current.leftmost_column) {
+ pointer += " ";
+ } else {
+ pointer += "^";
+ }
+ }
+ print_line(pointer.as_string());
+ }
+
+ token += current.get_name();
+
+ if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) {
+ token += "(";
+ token += Variant::get_type_name(current.literal.get_type());
+ token += ") ";
+ token += current.literal;
+ }
+
+ print_line(token.as_string());
+
+ print_line("-------------------------------------------------------");
+
+ current = tokenizer.scan();
+ }
+
+ print_line(current.get_name()); // Should be EOF
+}
+
+static void test_parser(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {
+ GDScriptParser parser;
+ Error err = parser.parse(p_code, p_script_path, false);
+
+ if (err != OK) {
+ const List<GDScriptParser::ParserError> &errors = parser.get_errors();
+ for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) {
+ const GDScriptParser::ParserError &error = E->get();
+ print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
+ }
+ }
+
+ GDScriptParser::TreePrinter printer;
+
+ printer.print_tree(parser);
+}
+
+MainLoop *test(TestType p_type) {
+ List<String> cmdlargs = OS::get_singleton()->get_cmdline_args();
+
+ if (cmdlargs.empty()) {
+ return nullptr;
+ }
+
+ String test = cmdlargs.back()->get();
+ if (!test.ends_with(".gd")) {
+ print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test);
+ return nullptr;
+ }
+
+ FileAccessRef fa = FileAccess::open(test, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!fa, nullptr, "Could not open file: " + test);
+
+ Vector<uint8_t> buf;
+ int flen = fa->get_len();
+ buf.resize(fa->get_len() + 1);
+ fa->get_buffer(buf.ptrw(), flen);
+ buf.write[flen] = 0;
+
+ String code;
+ code.parse_utf8((const char *)&buf[0]);
+
+ Vector<String> lines;
+ int last = 0;
+ for (int i = 0; i <= code.length(); i++) {
+ if (code[i] == '\n' || code[i] == 0) {
+ lines.push_back(code.substr(last, i - last));
+ last = i + 1;
+ }
+ }
+
+ switch (p_type) {
+ case TEST_TOKENIZER:
+ test_tokenizer(code, lines);
+ break;
+ case TEST_PARSER:
+ test_parser(code, test, lines);
+ break;
+ case TEST_COMPILER:
+ case TEST_BYTECODE:
+ print_line("Not implemented.");
+ }
+
+ return nullptr;
+}
+
+} // namespace TestGDScript
+
+#else
+
+namespace TestGDScript {
+
+MainLoop *test(TestType p_type) {
+ ERR_PRINT("The GDScript module is disabled, therefore GDScript tests cannot be used.");
+ return nullptr;
+}
+
+} // namespace TestGDScript
+
+#endif
diff --git a/main/tests/test_gdscript.h b/tests/test_gdscript.h
index 6595da1430..6595da1430 100644
--- a/main/tests/test_gdscript.h
+++ b/tests/test_gdscript.h
diff --git a/main/tests/test_gui.cpp b/tests/test_gui.cpp
index d46a13d2c0..d46a13d2c0 100644
--- a/main/tests/test_gui.cpp
+++ b/tests/test_gui.cpp
diff --git a/main/tests/test_gui.h b/tests/test_gui.h
index 5a23179eee..5a23179eee 100644
--- a/main/tests/test_gui.h
+++ b/tests/test_gui.h
diff --git a/main/tests/test_main.cpp b/tests/test_main.cpp
index 5ebdaf1741..91eff28f86 100644
--- a/main/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -47,10 +47,14 @@
#include "test_render.h"
#include "test_shader_lang.h"
#include "test_string.h"
+#include "test_validate_testing.h"
+
+#include "thirdparty/doctest/doctest.h"
const char **tests_get_names() {
static const char *test_names[] = {
- "string",
+ "*",
+ "all",
"math",
"basis",
"physics_2d",
@@ -72,75 +76,38 @@ const char **tests_get_names() {
return test_names;
}
-MainLoop *test_main(String p_test, const List<String> &p_args) {
- if (p_test == "string") {
- return TestString::test();
- }
-
- if (p_test == "math") {
- return TestMath::test();
- }
-
- if (p_test == "basis") {
- return TestBasis::test();
- }
-
- if (p_test == "physics_2d") {
- return TestPhysics2D::test();
- }
-
- if (p_test == "physics_3d") {
- return TestPhysics3D::test();
- }
-
- if (p_test == "render") {
- return TestRender::test();
- }
-
- if (p_test == "oa_hash_map") {
- return TestOAHashMap::test();
- }
-
- if (p_test == "class_db") {
- return TestClassDB::test();
- }
-
-#ifndef _3D_DISABLED
- if (p_test == "gui") {
- return TestGUI::test();
- }
-#endif
-
- if (p_test == "shaderlang") {
- return TestShaderLang::test();
- }
-
- if (p_test == "gd_tokenizer") {
- return TestGDScript::test(TestGDScript::TEST_TOKENIZER);
- }
-
- if (p_test == "gd_parser") {
- return TestGDScript::test(TestGDScript::TEST_PARSER);
- }
-
- if (p_test == "gd_compiler") {
- return TestGDScript::test(TestGDScript::TEST_COMPILER);
- }
-
- if (p_test == "gd_bytecode") {
- return TestGDScript::test(TestGDScript::TEST_BYTECODE);
- }
-
- if (p_test == "ordered_hash_map") {
- return TestOrderedHashMap::test();
- }
-
- if (p_test == "astar") {
- return TestAStar::test();
- }
-
- print_line("Unknown test: " + p_test);
- return nullptr;
+int test_main(int argc, char *argv[]) {
+ // doctest runner for when legacy unit tests are no found
+ doctest::Context test_context;
+ List<String> valid_arguments;
+
+ // clean arguments of --test from the args
+ int argument_count = 0;
+ for (int x = 0; x < argc; x++) {
+ if (strncmp(argv[x], "--test", 6) != 0) {
+ valid_arguments.push_back(String(argv[x]));
+ argument_count++;
+ }
+ }
+
+ // convert godot command line arguments back to standard arguments.
+ char **args = new char *[valid_arguments.size()];
+ for (int x = 0; x < valid_arguments.size(); x++) {
+ // operation to convert godot string to non wchar string
+ const char *str = valid_arguments[x].utf8().ptr();
+ // allocate the string copy
+ args[x] = new char[strlen(str) + 1];
+ // copy this into memory
+ std::memcpy(args[x], str, strlen(str) + 1);
+ }
+
+ test_context.applyCommandLine(valid_arguments.size(), args);
+
+ test_context.setOption("order-by", "name");
+ test_context.setOption("abort-after", 5);
+ test_context.setOption("no-breaks", true);
+ delete[] args;
+ return test_context.run();
}
#else
@@ -153,8 +120,8 @@ const char **tests_get_names() {
return test_names;
}
-MainLoop *test_main(String p_test, const List<String> &p_args) {
- return nullptr;
+int test_main(int argc, char *argv[]) {
+ return 0;
}
#endif
diff --git a/main/tests/test_main.h b/tests/test_main.h
index bdb1668d21..8273b74eac 100644
--- a/main/tests/test_main.h
+++ b/tests/test_main.h
@@ -36,6 +36,6 @@
#include "core/ustring.h"
const char **tests_get_names();
-MainLoop *test_main(String p_test, const List<String> &p_args);
+int test_main(int argc, char *argv[]);
#endif // TEST_MAIN_H
diff --git a/main/tests/test_math.cpp b/tests/test_math.cpp
index 5f84bad4e9..5f84bad4e9 100644
--- a/main/tests/test_math.cpp
+++ b/tests/test_math.cpp
diff --git a/main/tests/test_math.h b/tests/test_math.h
index 77bce8dd66..77bce8dd66 100644
--- a/main/tests/test_math.h
+++ b/tests/test_math.h
diff --git a/main/tests/test_oa_hash_map.cpp b/tests/test_oa_hash_map.cpp
index 9182f66b61..9182f66b61 100644
--- a/main/tests/test_oa_hash_map.cpp
+++ b/tests/test_oa_hash_map.cpp
diff --git a/main/tests/test_oa_hash_map.h b/tests/test_oa_hash_map.h
index eb2b3d1e99..eb2b3d1e99 100644
--- a/main/tests/test_oa_hash_map.h
+++ b/tests/test_oa_hash_map.h
diff --git a/main/tests/test_ordered_hash_map.cpp b/tests/test_ordered_hash_map.cpp
index d18a3784be..d18a3784be 100644
--- a/main/tests/test_ordered_hash_map.cpp
+++ b/tests/test_ordered_hash_map.cpp
diff --git a/main/tests/test_ordered_hash_map.h b/tests/test_ordered_hash_map.h
index f251da0ba2..f251da0ba2 100644
--- a/main/tests/test_ordered_hash_map.h
+++ b/tests/test_ordered_hash_map.h
diff --git a/main/tests/test_physics_2d.cpp b/tests/test_physics_2d.cpp
index c82ae920bc..c82ae920bc 100644
--- a/main/tests/test_physics_2d.cpp
+++ b/tests/test_physics_2d.cpp
diff --git a/main/tests/test_physics_2d.h b/tests/test_physics_2d.h
index 517d324f3b..517d324f3b 100644
--- a/main/tests/test_physics_2d.h
+++ b/tests/test_physics_2d.h
diff --git a/main/tests/test_physics_3d.cpp b/tests/test_physics_3d.cpp
index 72de2041e4..72de2041e4 100644
--- a/main/tests/test_physics_3d.cpp
+++ b/tests/test_physics_3d.cpp
diff --git a/main/tests/test_physics_3d.h b/tests/test_physics_3d.h
index d03f2c6573..d03f2c6573 100644
--- a/main/tests/test_physics_3d.h
+++ b/tests/test_physics_3d.h
diff --git a/main/tests/test_render.cpp b/tests/test_render.cpp
index d936dd72e7..d936dd72e7 100644
--- a/main/tests/test_render.cpp
+++ b/tests/test_render.cpp
diff --git a/main/tests/test_render.h b/tests/test_render.h
index 4a6340c443..4a6340c443 100644
--- a/main/tests/test_render.h
+++ b/tests/test_render.h
diff --git a/main/tests/test_shader_lang.cpp b/tests/test_shader_lang.cpp
index 34ee3e3210..34ee3e3210 100644
--- a/main/tests/test_shader_lang.cpp
+++ b/tests/test_shader_lang.cpp
diff --git a/main/tests/test_shader_lang.h b/tests/test_shader_lang.h
index 2811c5f46e..2811c5f46e 100644
--- a/main/tests/test_shader_lang.h
+++ b/tests/test_shader_lang.h
diff --git a/tests/test_string.h b/tests/test_string.h
new file mode 100644
index 0000000000..25fd513a1a
--- /dev/null
+++ b/tests/test_string.h
@@ -0,0 +1,798 @@
+/*************************************************************************/
+/* test_string.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_STRING_H
+#define TEST_STRING_H
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <wchar.h>
+
+#include "core/io/ip_address.h"
+#include "core/os/main_loop.h"
+#include "core/os/os.h"
+#include "core/ustring.h"
+
+#ifdef MODULE_REGEX_ENABLED
+#include "modules/regex/regex.h"
+#endif
+
+#include "thirdparty/doctest/doctest.h"
+
+namespace TestString {
+
+TEST_CASE("[String] Assign from cstr") {
+ String s = "Hello";
+ CHECK(wcscmp(s.c_str(), L"Hello") == 0);
+}
+
+TEST_CASE("[String] Assign from string (operator=)") {
+ String s = "Dolly";
+ const String &t = s;
+ CHECK(wcscmp(t.c_str(), L"Dolly") == 0);
+}
+
+TEST_CASE("[String] Assign from c-string (copycon)") {
+ String s("Sheep");
+ const String &t(s);
+ CHECK(wcscmp(t.c_str(), L"Sheep") == 0);
+}
+
+TEST_CASE("[String] Assign from c-widechar (operator=)") {
+ String s(L"Give me");
+ CHECK(wcscmp(s.c_str(), L"Give me") == 0);
+}
+
+TEST_CASE("[String] Assign from c-widechar (copycon)") {
+ String s(L"Wool");
+ CHECK(wcscmp(s.c_str(), L"Wool") == 0);
+}
+
+TEST_CASE("[String] Comparisons (equal)") {
+ String s = "Test Compare";
+ CHECK(s == "Test Compare");
+ CHECK(s == L"Test Compare");
+ CHECK(s == String("Test Compare"));
+}
+
+TEST_CASE("[String] Comparisons (not equal)") {
+ String s = "Test Compare";
+ CHECK(s != "Peanut");
+ CHECK(s != L"Coconut");
+ CHECK(s != String("Butter"));
+}
+
+TEST_CASE("[String] Comparisons (operator <)") {
+ String s = "Bees";
+ CHECK(s < "Elephant");
+ CHECK(!(s < L"Amber"));
+ CHECK(!(s < String("Beatrix")));
+}
+
+TEST_CASE("[String] Concatenation") {
+ String s;
+
+ s += "Have";
+ s += ' ';
+ s += 'a';
+ s += String(" ");
+ s = s + L"Nice";
+ s = s + " ";
+ s = s + String("Day");
+
+ CHECK(s == "Have a Nice Day");
+}
+
+TEST_CASE("[String] Testing size and length of string") {
+ // todo: expand this test to do more tests on size() as it is complicated under the hood.
+ CHECK(String("Mellon").size() == 7);
+ CHECK(String("Mellon1").size() == 8);
+
+ // length works fine and is easier to test
+ CHECK(String("Mellon").length() == 6);
+ CHECK(String("Mellon1").length() == 7);
+ CHECK(String("Mellon2").length() == 7);
+ CHECK(String("Mellon3").length() == 7);
+}
+
+TEST_CASE("[String] Testing for empty string") {
+ CHECK(!String("Mellon").empty());
+ // do this more than once, to check for string corruption
+ CHECK(String("").empty());
+ CHECK(String("").empty());
+ CHECK(String("").empty());
+}
+
+TEST_CASE("[String] Operator []") {
+ String a = "Kugar Sane";
+ a[0] = 'S';
+ a[6] = 'C';
+ CHECK(a == "Sugar Cane");
+ CHECK(a[1] == 'u');
+}
+
+TEST_CASE("[String] Case function test") {
+ String a = "MoMoNgA";
+
+ CHECK(a.to_upper() == "MOMONGA");
+ CHECK(a.nocasecmp_to("momonga") == 0);
+}
+
+TEST_CASE("[String] UTF8") {
+ /* how can i embed UTF in here? */
+ static const CharType ustr[] = { 0x304A, 0x360F, 0x3088, 0x3046, 0 };
+ //static const wchar_t ustr[] = { 'P', 0xCE, 'p',0xD3, 0 };
+ String s = ustr;
+ s.parse_utf8(s.utf8().get_data());
+ CHECK(s == ustr);
+}
+
+TEST_CASE("[String] ASCII") {
+ String s = L"Primero Leche";
+ String t = s.ascii().get_data();
+ CHECK(s == t);
+}
+
+TEST_CASE("[String] Substr") {
+ String s = "Killer Baby";
+ CHECK(s.substr(3, 4) == "ler ");
+}
+
+TEST_CASE("[string] Find") {
+ String s = "Pretty Woman";
+ s.find("Revenge of the Monster Truck");
+
+ CHECK(s.find("tty") == 3);
+ CHECK(s.find("Revenge of the Monster Truck") == -1);
+}
+
+TEST_CASE("[String] find no case") {
+ String s = "Pretty Whale";
+ CHECK(s.findn("WHA") == 7);
+ CHECK(s.findn("Revenge of the Monster SawFish") == -1);
+}
+
+TEST_CASE("[String] Find and replace") {
+ String s = "Happy Birthday, Anna!";
+ s = s.replace("Birthday", "Halloween");
+ CHECK(s == "Happy Halloween, Anna!");
+}
+
+TEST_CASE("[String] Insertion") {
+ String s = "Who is Frederic?";
+ s = s.insert(s.find("?"), " Chopin");
+ CHECK(s == "Who is Frederic Chopin?");
+}
+
+TEST_CASE("[String] Number to string") {
+ CHECK(String::num(3.141593) == "3.141593");
+}
+
+TEST_CASE("[String] String to integer") {
+ static const char *nums[4] = { "1237461283", "- 22", "0", " - 1123412" };
+ static const int num[4] = { 1237461283, -22, 0, -1123412 };
+
+ for (int i = 0; i < 4; i++) {
+ CHECK(String(nums[i]).to_int() == num[i]);
+ }
+}
+
+TEST_CASE("[String] String to float") {
+ static const char *nums[4] = { "-12348298412.2", "0.05", "2.0002", " -0.0001" };
+ static const double num[4] = { -12348298412.2, 0.05, 2.0002, -0.0001 };
+
+ for (int i = 0; i < 4; i++) {
+ CHECK(!(ABS(String(nums[i]).to_double() - num[i]) > 0.00001));
+ }
+}
+
+TEST_CASE("[String] Slicing") {
+ String s = "Mars,Jupiter,Saturn,Uranus";
+
+ const char *slices[4] = { "Mars", "Jupiter", "Saturn", "Uranus" };
+ for (int i = 0; i < s.get_slice_count(","); i++) {
+ CHECK(s.get_slice(",", i) == slices[i]);
+ }
+}
+
+TEST_CASE("[String] Erasing") {
+ String s = "Josephine is such a cute girl!";
+ s.erase(s.find("cute "), String("cute ").length());
+ CHECK(s == "Josephine is such a girl!");
+}
+
+#ifdef MODULE_REGEX_ENABLED
+TEST_CASE("[String] Regex substitution") {
+ String s = "Double all the vowels.";
+ RegEx re("(?<vowel>[aeiou])");
+ s = re.sub(s, "$0$vowel", true);
+ CHECK(s == "Doouublee aall thee vooweels.");
+}
+#endif
+
+struct test_27_data {
+ char const *data;
+ char const *begin;
+ bool expected;
+};
+
+TEST_CASE("[String] Begins with") {
+ test_27_data tc[] = {
+ { "res://foobar", "res://", true },
+ { "res", "res://", false },
+ { "abc", "abc", true }
+ };
+ size_t count = sizeof(tc) / sizeof(tc[0]);
+ bool state = true;
+ for (size_t i = 0; state && i < count; ++i) {
+ String s = tc[i].data;
+ state = s.begins_with(tc[i].begin) == tc[i].expected;
+ if (state) {
+ String sb = tc[i].begin;
+ state = s.begins_with(sb) == tc[i].expected;
+ }
+ CHECK(state);
+ if (!state) {
+ break;
+ }
+ };
+ CHECK(state);
+}
+
+TEST_CASE("[String] sprintf") {
+ String format, output;
+ Array args;
+ bool error;
+
+ // %%
+ format = "fish %% frog";
+ args.clear();
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish % frog"));
+ //////// INTS
+
+ // Int
+ format = "fish %d frog";
+ args.clear();
+ args.push_back(5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 5 frog"));
+
+ // Int left padded with zeroes.
+ format = "fish %05d frog";
+ args.clear();
+ args.push_back(5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 00005 frog"));
+
+ // Int left padded with spaces.
+ format = "fish %5d frog";
+ args.clear();
+ args.push_back(5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 5 frog"));
+
+ // Int right padded with spaces.
+ format = "fish %-5d frog";
+ args.clear();
+ args.push_back(5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 5 frog"));
+
+ // Int with sign (positive).
+ format = "fish %+d frog";
+ args.clear();
+ args.push_back(5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish +5 frog"));
+
+ // Negative int.
+ format = "fish %d frog";
+ args.clear();
+ args.push_back(-5);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish -5 frog"));
+
+ // Hex (lower)
+ format = "fish %x frog";
+ args.clear();
+ args.push_back(45);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 2d frog"));
+
+ // Hex (upper)
+ format = "fish %X frog";
+ args.clear();
+ args.push_back(45);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 2D frog"));
+
+ // Octal
+ format = "fish %o frog";
+ args.clear();
+ args.push_back(99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 143 frog"));
+
+ ////// REALS
+
+ // Real
+ format = "fish %f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.990000 frog"));
+
+ // Real left-padded
+ format = "fish %11f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.990000 frog"));
+
+ // Real right-padded
+ format = "fish %-11f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.990000 frog"));
+
+ // Real given int.
+ format = "fish %f frog";
+ args.clear();
+ args.push_back(99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.000000 frog"));
+
+ // Real with sign (positive).
+ format = "fish %+f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish +99.990000 frog"));
+
+ // Real with 1 decimals.
+ format = "fish %.1f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 100.0 frog"));
+
+ // Real with 12 decimals.
+ format = "fish %.12f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.990000000000 frog"));
+
+ // Real with no decimals.
+ format = "fish %.f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 100 frog"));
+
+ /////// Strings.
+
+ // String
+ format = "fish %s frog";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish cheese frog"));
+
+ // String left-padded
+ format = "fish %10s frog";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish cheese frog"));
+
+ // String right-padded
+ format = "fish %-10s frog";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish cheese frog"));
+
+ ///// Characters
+
+ // Character as string.
+ format = "fish %c frog";
+ args.clear();
+ args.push_back("A");
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish A frog"));
+
+ // Character as int.
+ format = "fish %c frog";
+ args.clear();
+ args.push_back(65);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish A frog"));
+
+ ///// Dynamic width
+
+ // String dynamic width
+ format = "fish %*s frog";
+ args.clear();
+ args.push_back(10);
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ REQUIRE(output == String("fish cheese frog"));
+
+ // Int dynamic width
+ format = "fish %*d frog";
+ args.clear();
+ args.push_back(10);
+ args.push_back(99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ REQUIRE(output == String("fish 99 frog"));
+
+ // Float dynamic width
+ format = "fish %*.*f frog";
+ args.clear();
+ args.push_back(10);
+ args.push_back(3);
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error == false);
+ CHECK(output == String("fish 99.990 frog"));
+
+ ///// Errors
+
+ // More formats than arguments.
+ format = "fish %s %s frog";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "not enough arguments for format string");
+
+ // More arguments than formats.
+ format = "fish %s frog";
+ args.clear();
+ args.push_back("hello");
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "not all arguments converted during string formatting");
+
+ // Incomplete format.
+ format = "fish %10";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "incomplete format");
+
+ // Bad character in format string
+ format = "fish %&f frog";
+ args.clear();
+ args.push_back("cheese");
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "unsupported format character");
+
+ // Too many decimals.
+ format = "fish %2.2.2f frog";
+ args.clear();
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "too many decimal points in format");
+
+ // * not a number
+ format = "fish %*f frog";
+ args.clear();
+ args.push_back("cheese");
+ args.push_back(99.99);
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "* wants number");
+
+ // Character too long.
+ format = "fish %c frog";
+ args.clear();
+ args.push_back("sc");
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "%c requires number or single-character string");
+
+ // Character bad type.
+ format = "fish %c frog";
+ args.clear();
+ args.push_back(Array());
+ output = format.sprintf(args, &error);
+ REQUIRE(error);
+ CHECK(output == "%c requires number or single-character string");
+}
+
+TEST_CASE("[String] IPVX address to string") {
+ IP_Address ip0("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
+ IP_Address ip(0x0123, 0x4567, 0x89ab, 0xcdef, true);
+ IP_Address ip2("fe80::52e5:49ff:fe93:1baf");
+ IP_Address ip3("::ffff:192.168.0.1");
+ String ip4 = "192.168.0.1";
+ CHECK(ip4.is_valid_ip_address());
+
+ ip4 = "192.368.0.1";
+ CHECK(!ip4.is_valid_ip_address());
+
+ String ip6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
+ CHECK(ip6.is_valid_ip_address());
+
+ ip6 = "2001:0db8:85j3:0000:0000:8a2e:0370:7334";
+ CHECK(!ip6.is_valid_ip_address());
+
+ ip6 = "2001:0db8:85f345:0000:0000:8a2e:0370:7334";
+ CHECK(!ip6.is_valid_ip_address());
+
+ ip6 = "2001:0db8::0:8a2e:370:7334";
+ CHECK(ip6.is_valid_ip_address());
+
+ ip6 = "::ffff:192.168.0.1";
+ CHECK(ip6.is_valid_ip_address());
+}
+
+TEST_CASE("[String] Capitalize against many strings") {
+ String input = "bytes2var";
+ String output = "Bytes 2 Var";
+ CHECK(input.capitalize() == output);
+
+ input = "linear2db";
+ output = "Linear 2 Db";
+ CHECK(input.capitalize() == output);
+
+ input = "vector3";
+ output = "Vector 3";
+ CHECK(input.capitalize() == output);
+
+ input = "sha256";
+ output = "Sha 256";
+ CHECK(input.capitalize() == output);
+
+ input = "2db";
+ output = "2 Db";
+ CHECK(input.capitalize() == output);
+
+ input = "PascalCase";
+ output = "Pascal Case";
+ CHECK(input.capitalize() == output);
+
+ input = "PascalPascalCase";
+ output = "Pascal Pascal Case";
+ CHECK(input.capitalize() == output);
+
+ input = "snake_case";
+ output = "Snake Case";
+ CHECK(input.capitalize() == output);
+
+ input = "snake_snake_case";
+ output = "Snake Snake Case";
+ CHECK(input.capitalize() == output);
+
+ input = "sha256sum";
+ output = "Sha 256 Sum";
+ CHECK(input.capitalize() == output);
+
+ input = "cat2dog";
+ output = "Cat 2 Dog";
+ CHECK(input.capitalize() == output);
+
+ input = "function(name)";
+ output = "Function(name)";
+ CHECK(input.capitalize() == output);
+
+ input = "snake_case_function(snake_case_arg)";
+ output = "Snake Case Function(snake Case Arg)";
+ CHECK(input.capitalize() == output);
+
+ input = "snake_case_function( snake_case_arg )";
+ output = "Snake Case Function( Snake Case Arg )";
+ CHECK(input.capitalize() == output);
+}
+
+TEST_CASE("[String] Checking string is empty when it should be") {
+ bool state = true;
+ bool success;
+
+ String a = "";
+ success = a[0] == 0;
+ if (!success) {
+ state = false;
+ }
+ String b = "Godot";
+ success = b[b.size()] == 0;
+ if (!success) {
+ state = false;
+ }
+ const String c = "";
+ success = c[0] == 0;
+ if (!success) {
+ state = false;
+ }
+
+ const String d = "Godot";
+ success = d[d.size()] == 0;
+ if (!success) {
+ state = false;
+ }
+
+ CHECK(state);
+}
+
+TEST_CASE("[String] lstrip and rstrip") {
+#define STRIP_TEST(x) \
+ { \
+ bool success = x; \
+ state = state && success; \
+ }
+
+ bool state = true;
+
+ // strip none
+ STRIP_TEST(String("abc").lstrip("") == "abc");
+ STRIP_TEST(String("abc").rstrip("") == "abc");
+ // strip one
+ STRIP_TEST(String("abc").lstrip("a") == "bc");
+ STRIP_TEST(String("abc").rstrip("c") == "ab");
+ // strip lots
+ STRIP_TEST(String("bababbababccc").lstrip("ab") == "ccc");
+ STRIP_TEST(String("aaabcbcbcbbcbbc").rstrip("cb") == "aaa");
+ // strip empty string
+ STRIP_TEST(String("").lstrip("") == "");
+ STRIP_TEST(String("").rstrip("") == "");
+ // strip to empty string
+ STRIP_TEST(String("abcabcabc").lstrip("bca") == "");
+ STRIP_TEST(String("abcabcabc").rstrip("bca") == "");
+ // don't strip wrong end
+ STRIP_TEST(String("abc").lstrip("c") == "abc");
+ STRIP_TEST(String("abca").lstrip("a") == "bca");
+ STRIP_TEST(String("abc").rstrip("a") == "abc");
+ STRIP_TEST(String("abca").rstrip("a") == "abc");
+ // in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5)
+ // and the same second as "ÿ" (\u00ff)
+ STRIP_TEST(String::utf8("¿").lstrip(String::utf8("µÿ")) == String::utf8("¿"));
+ STRIP_TEST(String::utf8("¿").rstrip(String::utf8("µÿ")) == String::utf8("¿"));
+ STRIP_TEST(String::utf8("µ¿ÿ").lstrip(String::utf8("µÿ")) == String::utf8("¿ÿ"));
+ STRIP_TEST(String::utf8("µ¿ÿ").rstrip(String::utf8("µÿ")) == String::utf8("µ¿"));
+
+ // the above tests repeated with additional superfluous strip chars
+
+ // strip none
+ STRIP_TEST(String("abc").lstrip("qwjkl") == "abc");
+ STRIP_TEST(String("abc").rstrip("qwjkl") == "abc");
+ // strip one
+ STRIP_TEST(String("abc").lstrip("qwajkl") == "bc");
+ STRIP_TEST(String("abc").rstrip("qwcjkl") == "ab");
+ // strip lots
+ STRIP_TEST(String("bababbababccc").lstrip("qwabjkl") == "ccc");
+ STRIP_TEST(String("aaabcbcbcbbcbbc").rstrip("qwcbjkl") == "aaa");
+ // strip empty string
+ STRIP_TEST(String("").lstrip("qwjkl") == "");
+ STRIP_TEST(String("").rstrip("qwjkl") == "");
+ // strip to empty string
+ STRIP_TEST(String("abcabcabc").lstrip("qwbcajkl") == "");
+ STRIP_TEST(String("abcabcabc").rstrip("qwbcajkl") == "");
+ // don't strip wrong end
+ STRIP_TEST(String("abc").lstrip("qwcjkl") == "abc");
+ STRIP_TEST(String("abca").lstrip("qwajkl") == "bca");
+ STRIP_TEST(String("abc").rstrip("qwajkl") == "abc");
+ STRIP_TEST(String("abca").rstrip("qwajkl") == "abc");
+ // in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5)
+ // and the same second as "ÿ" (\u00ff)
+ STRIP_TEST(String::utf8("¿").lstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿"));
+ STRIP_TEST(String::utf8("¿").rstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿"));
+ STRIP_TEST(String::utf8("µ¿ÿ").lstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿ÿ"));
+ STRIP_TEST(String::utf8("µ¿ÿ").rstrip(String::utf8("qwaµÿjkl")) == String::utf8("µ¿"));
+
+ CHECK(state);
+
+#undef STRIP_TEST
+}
+
+TEST_CASE("[String] ensuring empty string into parse_utf8 passes empty string") {
+ String empty;
+ CHECK(empty.parse_utf8(NULL, -1));
+}
+
+TEST_CASE("[String] Cyrillic to_lower()") {
+ String upper = String::utf8("АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ");
+ String lower = String::utf8("абвгдеёжзийклмнопрстуфхцчшщъыьэюя");
+
+ String test = upper.to_lower();
+
+ bool state = test == lower;
+
+ CHECK(state);
+}
+
+TEST_CASE("[String] Count and countn functionality") {
+#define COUNT_TEST(x) \
+ { \
+ bool success = x; \
+ state = state && success; \
+ }
+
+ bool state = true;
+
+ COUNT_TEST(String("").count("Test") == 0);
+ COUNT_TEST(String("Test").count("") == 0);
+ COUNT_TEST(String("Test").count("test") == 0);
+ COUNT_TEST(String("Test").count("TEST") == 0);
+ COUNT_TEST(String("TEST").count("TEST") == 1);
+ COUNT_TEST(String("Test").count("Test") == 1);
+ COUNT_TEST(String("aTest").count("Test") == 1);
+ COUNT_TEST(String("Testa").count("Test") == 1);
+ COUNT_TEST(String("TestTestTest").count("Test") == 3);
+ COUNT_TEST(String("TestTestTest").count("TestTest") == 1);
+ COUNT_TEST(String("TestGodotTestGodotTestGodot").count("Test") == 3);
+
+ COUNT_TEST(String("TestTestTestTest").count("Test", 4, 8) == 1);
+ COUNT_TEST(String("TestTestTestTest").count("Test", 4, 12) == 2);
+ COUNT_TEST(String("TestTestTestTest").count("Test", 4, 16) == 3);
+ COUNT_TEST(String("TestTestTestTest").count("Test", 4) == 3);
+
+ COUNT_TEST(String("Test").countn("test") == 1);
+ COUNT_TEST(String("Test").countn("TEST") == 1);
+ COUNT_TEST(String("testTest-Testatest").countn("tEst") == 4);
+ COUNT_TEST(String("testTest-TeStatest").countn("tEsT", 4, 16) == 2);
+
+ CHECK(state);
+}
+} // namespace TestString
+
+#endif // TEST_STRING_H
diff --git a/tests/test_validate_testing.h b/tests/test_validate_testing.h
new file mode 100644
index 0000000000..5be7d45185
--- /dev/null
+++ b/tests/test_validate_testing.h
@@ -0,0 +1,42 @@
+/*************************************************************************/
+/* test_validate_testing.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_VALIDATE_TESTING_H
+#define TEST_VALIDATE_TESTING_H
+
+#include "core/os/os.h"
+
+#include "thirdparty/doctest/doctest.h"
+
+TEST_CASE("Validate Test will always pass") {
+ CHECK(true);
+}
+
+#endif // TEST_VALIDATE_TESTING_H
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 43cd448a00..c1b230cfb7 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -74,6 +74,17 @@ Files extracted from upstream source:
- all .cpp, .h, and .txt files in ConvectionKernels/
+## doctest
+- Upstream: https://github.com/onqtam/doctest
+- Version: 1c8da00 (2.4.0)
+- License: MIT
+
+Extracted from .zip provided. Extracted license and header only.
+
+Important: Some files have Godot-made changes.
+They are marked with `// -- GODOT start --` and `// -- GODOT end --`
+comments.
+
## enet
- Upstream: http://enet.bespin.org
@@ -426,9 +437,8 @@ Collection of single-file libraries used in Godot components.
* License: zlib
- `stb_vorbis.c`
* Upstream: https://github.com/nothings/stb
- * Version: 1.19
+ * Version: 1.20
* License: Public Domain (Unlicense) or MIT
- * Modifications: `f->temp_offset += (sz+3)&~3;` changed to `f->temp_offset += (sz+7)&~7;` (needed until fixed upstream)
## nanosvg
diff --git a/thirdparty/doctest/LICENSE.txt b/thirdparty/doctest/LICENSE.txt
new file mode 100644
index 0000000000..a204721468
--- /dev/null
+++ b/thirdparty/doctest/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016-2019 Viktor Kirilov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/thirdparty/doctest/doctest.h b/thirdparty/doctest/doctest.h
new file mode 100644
index 0000000000..e4fed12767
--- /dev/null
+++ b/thirdparty/doctest/doctest.h
@@ -0,0 +1,6211 @@
+// ====================================================================== lgtm [cpp/missing-header-guard]
+// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! ==
+// ======================================================================
+//
+// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD
+//
+// Copyright (c) 2016-2019 Viktor Kirilov
+//
+// Distributed under the MIT Software License
+// See accompanying file LICENSE.txt or copy at
+// https://opensource.org/licenses/MIT
+//
+// The documentation can be found at the library's page:
+// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md
+//
+// =================================================================================================
+// =================================================================================================
+// =================================================================================================
+//
+// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2
+// which uses the Boost Software License - Version 1.0
+// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt
+//
+// The concept of subcases (sections in Catch) and expression decomposition are from there.
+// Some parts of the code are taken directly:
+// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<>
+// - the Approx() helper class for floating point comparison
+// - colors in the console
+// - breaking into a debugger
+// - signal / SEH handling
+// - timer
+// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste)
+//
+// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest
+// which uses the Boost Software License - Version 1.0
+// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt
+//
+// =================================================================================================
+// =================================================================================================
+// =================================================================================================
+
+#ifndef DOCTEST_LIBRARY_INCLUDED
+#define DOCTEST_LIBRARY_INCLUDED
+
+// =================================================================================================
+// == VERSION ======================================================================================
+// =================================================================================================
+
+#define DOCTEST_VERSION_MAJOR 2
+#define DOCTEST_VERSION_MINOR 4
+#define DOCTEST_VERSION_PATCH 0
+#define DOCTEST_VERSION_STR "2.4.0"
+
+#define DOCTEST_VERSION \
+ (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH)
+
+// =================================================================================================
+// == COMPILER VERSION =============================================================================
+// =================================================================================================
+
+// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect
+
+#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH))
+
+// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl...
+#if defined(_MSC_VER) && defined(_MSC_FULL_VER)
+#if _MSC_VER == _MSC_FULL_VER / 10000
+#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000)
+#else // MSVC
+#define DOCTEST_MSVC \
+ DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000)
+#endif // MSVC
+#endif // MSVC
+#if defined(__clang__) && defined(__clang_minor__)
+#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__)
+#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \
+ !defined(__INTEL_COMPILER)
+#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
+#endif // GCC
+
+#ifndef DOCTEST_MSVC
+#define DOCTEST_MSVC 0
+#endif // DOCTEST_MSVC
+#ifndef DOCTEST_CLANG
+#define DOCTEST_CLANG 0
+#endif // DOCTEST_CLANG
+#ifndef DOCTEST_GCC
+#define DOCTEST_GCC 0
+#endif // DOCTEST_GCC
+
+// =================================================================================================
+// == COMPILER WARNINGS HELPERS ====================================================================
+// =================================================================================================
+
+#if DOCTEST_CLANG
+#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x)
+#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push")
+#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w)
+#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop")
+#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w)
+#else // DOCTEST_CLANG
+#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+#define DOCTEST_CLANG_SUPPRESS_WARNING(w)
+#define DOCTEST_CLANG_SUPPRESS_WARNING_POP
+#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w)
+#endif // DOCTEST_CLANG
+
+#if DOCTEST_GCC
+#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x)
+#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push")
+#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w)
+#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop")
+#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \
+ DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w)
+#else // DOCTEST_GCC
+#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+#define DOCTEST_GCC_SUPPRESS_WARNING(w)
+#define DOCTEST_GCC_SUPPRESS_WARNING_POP
+#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w)
+#endif // DOCTEST_GCC
+
+#if DOCTEST_MSVC
+#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push))
+#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w))
+#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop))
+#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \
+ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w)
+#else // DOCTEST_MSVC
+#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+#define DOCTEST_MSVC_SUPPRESS_WARNING(w)
+#define DOCTEST_MSVC_SUPPRESS_WARNING_POP
+#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w)
+#endif // DOCTEST_MSVC
+
+// =================================================================================================
+// == COMPILER WARNINGS ============================================================================
+// =================================================================================================
+
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
+
+DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas")
+DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo")
+
+DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning
+DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning
+DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration
+DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression
+DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated
+DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant
+DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding
+DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe
+// static analysis
+DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept'
+DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable
+DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ...
+DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr...
+DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum'
+
+// 4548 - expression before comma has no effect; expected expression with side - effect
+// 4265 - class has virtual functions, but destructor is not virtual
+// 4986 - exception specification does not match previous declaration
+// 4350 - behavior change: 'member1' called instead of 'member2'
+// 4668 - 'x' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif'
+// 4365 - conversion from 'int' to 'unsigned long', signed/unsigned mismatch
+// 4774 - format string expected in argument 'x' is not a string literal
+// 4820 - padding in structs
+
+// only 4 should be disabled globally:
+// - 4514 # unreferenced inline function has been removed
+// - 4571 # SEH related
+// - 4710 # function not inlined
+// - 4711 # function 'x' selected for automatic inline expansion
+
+#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \
+ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4548) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4265) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4986) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4350) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4668) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4365) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4774) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4820) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4625) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4626) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5027) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5026) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4623) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5039) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5045) \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5105)
+
+#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+// =================================================================================================
+// == FEATURE DETECTION ============================================================================
+// =================================================================================================
+
+// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support
+// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx
+// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html
+// MSVC version table:
+// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering
+// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019)
+// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017)
+// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
+// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
+// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)
+// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)
+// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008)
+// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005)
+
+#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
+#define DOCTEST_CONFIG_WINDOWS_SEH
+#endif // MSVC
+#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH)
+#undef DOCTEST_CONFIG_WINDOWS_SEH
+#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH
+
+#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \
+ !defined(__EMSCRIPTEN__)
+#define DOCTEST_CONFIG_POSIX_SIGNALS
+#endif // _WIN32
+#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS)
+#undef DOCTEST_CONFIG_POSIX_SIGNALS
+#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND)
+#define DOCTEST_CONFIG_NO_EXCEPTIONS
+#endif // no exceptions
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+#define DOCTEST_CONFIG_NO_EXCEPTIONS
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+
+#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS)
+#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+
+#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT)
+#define DOCTEST_CONFIG_IMPLEMENT
+#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+#if DOCTEST_MSVC
+#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport)
+#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport)
+#else // MSVC
+#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport))
+#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport))
+#endif // MSVC
+#else // _WIN32
+#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default")))
+#define DOCTEST_SYMBOL_IMPORT
+#endif // _WIN32
+
+#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
+#ifdef DOCTEST_CONFIG_IMPLEMENT
+#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT
+#else // DOCTEST_CONFIG_IMPLEMENT
+#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT
+#endif // DOCTEST_CONFIG_IMPLEMENT
+#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
+#define DOCTEST_INTERFACE
+#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
+
+#define DOCTEST_EMPTY
+
+#if DOCTEST_MSVC
+#define DOCTEST_NOINLINE __declspec(noinline)
+#define DOCTEST_UNUSED
+#define DOCTEST_ALIGNMENT(x)
+#else // MSVC
+#define DOCTEST_NOINLINE __attribute__((noinline))
+#define DOCTEST_UNUSED __attribute__((unused))
+#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x)))
+#endif // MSVC
+
+#ifndef DOCTEST_NORETURN
+#define DOCTEST_NORETURN [[noreturn]]
+#endif // DOCTEST_NORETURN
+
+#ifndef DOCTEST_NOEXCEPT
+#define DOCTEST_NOEXCEPT noexcept
+#endif // DOCTEST_NOEXCEPT
+
+// =================================================================================================
+// == FEATURE DETECTION END ========================================================================
+// =================================================================================================
+
+// internal macros for string concatenation and anonymous variable name generation
+#define DOCTEST_CAT_IMPL(s1, s2) s1##s2
+#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2)
+#ifdef __COUNTER__ // not standard and may be missing for some compilers
+#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__)
+#else // __COUNTER__
+#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__)
+#endif // __COUNTER__
+
+#define DOCTEST_TOSTR(x) #x
+
+#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
+#define DOCTEST_REF_WRAP(x) x&
+#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
+#define DOCTEST_REF_WRAP(x) x
+#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
+
+// not using __APPLE__ because... this is how Catch does it
+#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
+#define DOCTEST_PLATFORM_MAC
+#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
+#define DOCTEST_PLATFORM_IPHONE
+#elif defined(_WIN32)
+#define DOCTEST_PLATFORM_WINDOWS
+#else // DOCTEST_PLATFORM
+#define DOCTEST_PLATFORM_LINUX
+#endif // DOCTEST_PLATFORM
+
+#define DOCTEST_GLOBAL_NO_WARNINGS(var) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable") \
+ static int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp)
+#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+#ifndef DOCTEST_BREAK_INTO_DEBUGGER
+// should probably take a look at https://github.com/scottt/debugbreak
+#ifdef DOCTEST_PLATFORM_MAC
+// -- GODOT start --
+#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__)
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :)
+#else
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0");
+#endif
+// -- GODOT end --
+#elif DOCTEST_MSVC
+#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak()
+#elif defined(__MINGW32__)
+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls")
+extern "C" __declspec(dllimport) void __stdcall DebugBreak();
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak()
+#else // linux
+#define DOCTEST_BREAK_INTO_DEBUGGER() ((void)0)
+#endif // linux
+#endif // DOCTEST_BREAK_INTO_DEBUGGER
+
+// this is kept here for backwards compatibility since the config option was changed
+#ifdef DOCTEST_CONFIG_USE_IOSFWD
+#define DOCTEST_CONFIG_USE_STD_HEADERS
+#endif // DOCTEST_CONFIG_USE_IOSFWD
+
+#ifdef DOCTEST_CONFIG_USE_STD_HEADERS
+#include <iosfwd>
+#include <cstddef>
+#include <ostream>
+#else // DOCTEST_CONFIG_USE_STD_HEADERS
+
+#if DOCTEST_CLANG
+// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier)
+#include <ciso646>
+#endif // clang
+
+#ifdef _LIBCPP_VERSION
+#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD
+#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD
+#else // _LIBCPP_VERSION
+#define DOCTEST_STD_NAMESPACE_BEGIN namespace std {
+#define DOCTEST_STD_NAMESPACE_END }
+#endif // _LIBCPP_VERSION
+
+// Forward declaring 'X' in namespace std is not permitted by the C++ Standard.
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643)
+
+DOCTEST_STD_NAMESPACE_BEGIN // NOLINT (cert-dcl58-cpp)
+typedef decltype(nullptr) nullptr_t;
+template <class charT>
+struct char_traits;
+template <>
+struct char_traits<char>;
+template <class charT, class traits>
+class basic_ostream;
+typedef basic_ostream<char, char_traits<char>> ostream;
+template <class... Types>
+class tuple;
+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
+// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183
+template <class _Ty>
+class allocator;
+template <class _Elem, class _Traits, class _Alloc>
+class basic_string;
+using string = basic_string<char, char_traits<char>, allocator<char>>;
+#endif // VS 2019
+DOCTEST_STD_NAMESPACE_END
+
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+#endif // DOCTEST_CONFIG_USE_STD_HEADERS
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#include <type_traits>
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+namespace doctest {
+
+DOCTEST_INTERFACE extern bool is_running_in_test;
+
+// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length
+// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for:
+// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128)
+// - if small - capacity left before going on the heap - using the lowest 5 bits
+// - if small - 2 bits are left unused - the second and third highest ones
+// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator)
+// and the "is small" bit remains "0" ("as well as the capacity left") so its OK
+// Idea taken from this lecture about the string implementation of facebook/folly - fbstring
+// https://www.youtube.com/watch?v=kPR8h4-qZdk
+// TODO:
+// - optimizations - like not deleting memory unnecessarily in operator= and etc.
+// - resize/reserve/clear
+// - substr
+// - replace
+// - back/front
+// - iterator stuff
+// - find & friends
+// - push_back/pop_back
+// - assign/insert/erase
+// - relational operators as free functions - taking const char* as one of the params
+class DOCTEST_INTERFACE String
+{
+ static const unsigned len = 24; //!OCLINT avoid private static members
+ static const unsigned last = len - 1; //!OCLINT avoid private static members
+
+ struct view // len should be more than sizeof(view) - because of the final byte for flags
+ {
+ char* ptr;
+ unsigned size;
+ unsigned capacity;
+ };
+
+ union
+ {
+ char buf[len];
+ view data;
+ };
+
+ bool isOnStack() const { return (buf[last] & 128) == 0; }
+ void setOnHeap();
+ void setLast(unsigned in = last);
+
+ void copy(const String& other);
+
+public:
+ String();
+ ~String();
+
+ // cppcheck-suppress noExplicitConstructor
+ String(const char* in);
+ String(const char* in, unsigned in_size);
+
+ String(const String& other);
+ String& operator=(const String& other);
+
+ String& operator+=(const String& other);
+ String operator+(const String& other) const;
+
+ String(String&& other);
+ String& operator=(String&& other);
+
+ char operator[](unsigned i) const;
+ char& operator[](unsigned i);
+
+ // the only functions I'm willing to leave in the interface - available for inlining
+ const char* c_str() const { return const_cast<String*>(this)->c_str(); } // NOLINT
+ char* c_str() {
+ if(isOnStack())
+ return reinterpret_cast<char*>(buf);
+ return data.ptr;
+ }
+
+ unsigned size() const;
+ unsigned capacity() const;
+
+ int compare(const char* other, bool no_case = false) const;
+ int compare(const String& other, bool no_case = false) const;
+};
+
+DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs);
+
+DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in);
+
+namespace Color {
+ enum Enum
+ {
+ None = 0,
+ White,
+ Red,
+ Green,
+ Blue,
+ Cyan,
+ Yellow,
+ Grey,
+
+ Bright = 0x10,
+
+ BrightRed = Bright | Red,
+ BrightGreen = Bright | Green,
+ LightGrey = Bright | Grey,
+ BrightWhite = Bright | White
+ };
+
+ DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code);
+} // namespace Color
+
+namespace assertType {
+ enum Enum
+ {
+ // macro traits
+
+ is_warn = 1,
+ is_check = 2 * is_warn,
+ is_require = 2 * is_check,
+
+ is_normal = 2 * is_require,
+ is_throws = 2 * is_normal,
+ is_throws_as = 2 * is_throws,
+ is_throws_with = 2 * is_throws_as,
+ is_nothrow = 2 * is_throws_with,
+
+ is_false = 2 * is_nothrow,
+ is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types
+
+ is_eq = 2 * is_unary,
+ is_ne = 2 * is_eq,
+
+ is_lt = 2 * is_ne,
+ is_gt = 2 * is_lt,
+
+ is_ge = 2 * is_gt,
+ is_le = 2 * is_ge,
+
+ // macro types
+
+ DT_WARN = is_normal | is_warn,
+ DT_CHECK = is_normal | is_check,
+ DT_REQUIRE = is_normal | is_require,
+
+ DT_WARN_FALSE = is_normal | is_false | is_warn,
+ DT_CHECK_FALSE = is_normal | is_false | is_check,
+ DT_REQUIRE_FALSE = is_normal | is_false | is_require,
+
+ DT_WARN_THROWS = is_throws | is_warn,
+ DT_CHECK_THROWS = is_throws | is_check,
+ DT_REQUIRE_THROWS = is_throws | is_require,
+
+ DT_WARN_THROWS_AS = is_throws_as | is_warn,
+ DT_CHECK_THROWS_AS = is_throws_as | is_check,
+ DT_REQUIRE_THROWS_AS = is_throws_as | is_require,
+
+ DT_WARN_THROWS_WITH = is_throws_with | is_warn,
+ DT_CHECK_THROWS_WITH = is_throws_with | is_check,
+ DT_REQUIRE_THROWS_WITH = is_throws_with | is_require,
+
+ DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn,
+ DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check,
+ DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require,
+
+ DT_WARN_NOTHROW = is_nothrow | is_warn,
+ DT_CHECK_NOTHROW = is_nothrow | is_check,
+ DT_REQUIRE_NOTHROW = is_nothrow | is_require,
+
+ DT_WARN_EQ = is_normal | is_eq | is_warn,
+ DT_CHECK_EQ = is_normal | is_eq | is_check,
+ DT_REQUIRE_EQ = is_normal | is_eq | is_require,
+
+ DT_WARN_NE = is_normal | is_ne | is_warn,
+ DT_CHECK_NE = is_normal | is_ne | is_check,
+ DT_REQUIRE_NE = is_normal | is_ne | is_require,
+
+ DT_WARN_GT = is_normal | is_gt | is_warn,
+ DT_CHECK_GT = is_normal | is_gt | is_check,
+ DT_REQUIRE_GT = is_normal | is_gt | is_require,
+
+ DT_WARN_LT = is_normal | is_lt | is_warn,
+ DT_CHECK_LT = is_normal | is_lt | is_check,
+ DT_REQUIRE_LT = is_normal | is_lt | is_require,
+
+ DT_WARN_GE = is_normal | is_ge | is_warn,
+ DT_CHECK_GE = is_normal | is_ge | is_check,
+ DT_REQUIRE_GE = is_normal | is_ge | is_require,
+
+ DT_WARN_LE = is_normal | is_le | is_warn,
+ DT_CHECK_LE = is_normal | is_le | is_check,
+ DT_REQUIRE_LE = is_normal | is_le | is_require,
+
+ DT_WARN_UNARY = is_normal | is_unary | is_warn,
+ DT_CHECK_UNARY = is_normal | is_unary | is_check,
+ DT_REQUIRE_UNARY = is_normal | is_unary | is_require,
+
+ DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn,
+ DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check,
+ DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require,
+ };
+} // namespace assertType
+
+DOCTEST_INTERFACE const char* assertString(assertType::Enum at);
+DOCTEST_INTERFACE const char* failureString(assertType::Enum at);
+DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file);
+
+struct DOCTEST_INTERFACE TestCaseData
+{
+ String m_file; // the file in which the test was registered
+ unsigned m_line; // the line where the test was registered
+ const char* m_name; // name of the test case
+ const char* m_test_suite; // the test suite in which the test was added
+ const char* m_description;
+ bool m_skip;
+ bool m_may_fail;
+ bool m_should_fail;
+ int m_expected_failures;
+ double m_timeout;
+};
+
+struct DOCTEST_INTERFACE AssertData
+{
+ // common - for all asserts
+ const TestCaseData* m_test_case;
+ assertType::Enum m_at;
+ const char* m_file;
+ int m_line;
+ const char* m_expr;
+ bool m_failed;
+
+ // exception-related - for all asserts
+ bool m_threw;
+ String m_exception;
+
+ // for normal asserts
+ String m_decomp;
+
+ // for specific exception-related asserts
+ bool m_threw_as;
+ const char* m_exception_type;
+ const char* m_exception_string;
+};
+
+struct DOCTEST_INTERFACE MessageData
+{
+ String m_string;
+ const char* m_file;
+ int m_line;
+ assertType::Enum m_severity;
+};
+
+struct DOCTEST_INTERFACE SubcaseSignature
+{
+ String m_name;
+ const char* m_file;
+ int m_line;
+
+ bool operator<(const SubcaseSignature& other) const;
+};
+
+struct DOCTEST_INTERFACE IContextScope
+{
+ IContextScope();
+ virtual ~IContextScope();
+ virtual void stringify(std::ostream*) const = 0;
+};
+
+struct ContextOptions //!OCLINT too many fields
+{
+ std::ostream* cout; // stdout stream - std::cout by default
+ std::ostream* cerr; // stderr stream - std::cerr by default
+ String binary_name; // the test binary name
+
+ // == parameters from the command line
+ String out; // output filename
+ String order_by; // how tests should be ordered
+ unsigned rand_seed; // the seed for rand ordering
+
+ unsigned first; // the first (matching) test to be executed
+ unsigned last; // the last (matching) test to be executed
+
+ int abort_after; // stop tests after this many failed assertions
+ int subcase_filter_levels; // apply the subcase filters for the first N levels
+
+ bool success; // include successful assertions in output
+ bool case_sensitive; // if filtering should be case sensitive
+ bool exit; // if the program should be exited after the tests are ran/whatever
+ bool duration; // print the time duration of each test case
+ bool no_throw; // to skip exceptions-related assertion macros
+ bool no_exitcode; // if the framework should return 0 as the exitcode
+ bool no_run; // to not run the tests at all (can be done with an "*" exclude)
+ bool no_version; // to not print the version of the framework
+ bool no_colors; // if output to the console should be colorized
+ bool force_colors; // forces the use of colors even when a tty cannot be detected
+ bool no_breaks; // to not break into the debugger
+ bool no_skip; // don't skip test cases which are marked to be skipped
+ bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x):
+ bool no_path_in_filenames; // if the path to files should be removed from the output
+ bool no_line_numbers; // if source code line numbers should be omitted from the output
+ bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!!
+ bool no_time_in_output; // omit any time/timestamps from output !!! UNDOCUMENTED !!!
+
+ bool help; // to print the help
+ bool version; // to print the version
+ bool count; // if only the count of matching tests is to be retrieved
+ bool list_test_cases; // to list all tests matching the filters
+ bool list_test_suites; // to list all suites matching the filters
+ bool list_reporters; // lists all registered reporters
+};
+
+namespace detail {
+#if defined(DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || defined(DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS)
+ template <bool CONDITION, typename TYPE = void>
+ struct enable_if
+ {};
+
+ template <typename TYPE>
+ struct enable_if<true, TYPE>
+ { typedef TYPE type; };
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+ // clang-format off
+ template<class T> struct remove_reference { typedef T type; };
+ template<class T> struct remove_reference<T&> { typedef T type; };
+ template<class T> struct remove_reference<T&&> { typedef T type; };
+
+ template<class T> struct remove_const { typedef T type; };
+ template<class T> struct remove_const<const T> { typedef T type; };
+ // clang-format on
+
+ template <typename T>
+ struct deferred_false
+ // cppcheck-suppress unusedStructMember
+ { static const bool value = false; };
+
+ namespace has_insertion_operator_impl {
+ std::ostream &os();
+ template<class T>
+ DOCTEST_REF_WRAP(T) val();
+
+ template<class, class = void>
+ struct check {
+ static constexpr auto value = false;
+ };
+
+ template<class T>
+ struct check<T, decltype(os() << val<T>(), void())> {
+ static constexpr auto value = true;
+ };
+ } // namespace has_insertion_operator_impl
+
+ template<class T>
+ using has_insertion_operator = has_insertion_operator_impl::check<T>;
+
+ DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num);
+
+ DOCTEST_INTERFACE std::ostream* getTlsOss(); // returns a thread-local ostringstream
+ DOCTEST_INTERFACE String getTlsOssResult();
+
+ template <bool C>
+ struct StringMakerBase
+ {
+ template <typename T>
+ static String convert(const DOCTEST_REF_WRAP(T)) {
+ return "{?}";
+ }
+ };
+
+ template <>
+ struct StringMakerBase<true>
+ {
+ template <typename T>
+ static String convert(const DOCTEST_REF_WRAP(T) in) {
+ *getTlsOss() << in;
+ return getTlsOssResult();
+ }
+ };
+
+ DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size);
+
+ template <typename T>
+ String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) {
+ return rawMemoryToString(&object, sizeof(object));
+ }
+
+ template <typename T>
+ const char* type_to_string() {
+ return "<>";
+ }
+} // namespace detail
+
+template <typename T>
+struct StringMaker : public detail::StringMakerBase<detail::has_insertion_operator<T>::value>
+{};
+
+template <typename T>
+struct StringMaker<T*>
+{
+ template <typename U>
+ static String convert(U* p) {
+ if(p)
+ return detail::rawMemoryToString(p);
+ return "NULL";
+ }
+};
+
+template <typename R, typename C>
+struct StringMaker<R C::*>
+{
+ static String convert(R C::*p) {
+ if(p)
+ return detail::rawMemoryToString(p);
+ return "NULL";
+ }
+};
+
+template <typename T>
+String toString(const DOCTEST_REF_WRAP(T) value) {
+ return StringMaker<T>::convert(value);
+}
+
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+DOCTEST_INTERFACE String toString(char* in);
+DOCTEST_INTERFACE String toString(const char* in);
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+DOCTEST_INTERFACE String toString(bool in);
+DOCTEST_INTERFACE String toString(float in);
+DOCTEST_INTERFACE String toString(double in);
+DOCTEST_INTERFACE String toString(double long in);
+
+DOCTEST_INTERFACE String toString(char in);
+DOCTEST_INTERFACE String toString(char signed in);
+DOCTEST_INTERFACE String toString(char unsigned in);
+DOCTEST_INTERFACE String toString(int short in);
+DOCTEST_INTERFACE String toString(int short unsigned in);
+DOCTEST_INTERFACE String toString(int in);
+DOCTEST_INTERFACE String toString(int unsigned in);
+DOCTEST_INTERFACE String toString(int long in);
+DOCTEST_INTERFACE String toString(int long unsigned in);
+DOCTEST_INTERFACE String toString(int long long in);
+DOCTEST_INTERFACE String toString(int long long unsigned in);
+DOCTEST_INTERFACE String toString(std::nullptr_t in);
+
+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
+// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183
+DOCTEST_INTERFACE String toString(const std::string& in);
+#endif // VS 2019
+
+class DOCTEST_INTERFACE Approx
+{
+public:
+ explicit Approx(double value);
+
+ Approx operator()(double value) const;
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+ template <typename T>
+ explicit Approx(const T& value,
+ typename detail::enable_if<std::is_constructible<double, T>::value>::type* =
+ static_cast<T*>(nullptr)) {
+ *this = Approx(static_cast<double>(value));
+ }
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+ Approx& epsilon(double newEpsilon);
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+ template <typename T>
+ typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon(
+ const T& newEpsilon) {
+ m_epsilon = static_cast<double>(newEpsilon);
+ return *this;
+ }
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+ Approx& scale(double newScale);
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+ template <typename T>
+ typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale(
+ const T& newScale) {
+ m_scale = static_cast<double>(newScale);
+ return *this;
+ }
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+ // clang-format off
+ DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs);
+ DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs);
+ DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs);
+ DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs);
+ DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs);
+ DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs);
+ DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs);
+
+ DOCTEST_INTERFACE friend String toString(const Approx& in);
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#define DOCTEST_APPROX_PREFIX \
+ template <typename T> friend typename detail::enable_if<std::is_constructible<double, T>::value, bool>::type
+
+ DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); }
+ DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); }
+ DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); }
+ DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); }
+ DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; }
+ DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; }
+ DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; }
+ DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; }
+#undef DOCTEST_APPROX_PREFIX
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+ // clang-format on
+
+private:
+ double m_epsilon;
+ double m_scale;
+ double m_value;
+};
+
+DOCTEST_INTERFACE String toString(const Approx& in);
+
+DOCTEST_INTERFACE const ContextOptions* getContextOptions();
+
+#if !defined(DOCTEST_CONFIG_DISABLE)
+
+namespace detail {
+ // clang-format off
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+ template<class T> struct decay_array { typedef T type; };
+ template<class T, unsigned N> struct decay_array<T[N]> { typedef T* type; };
+ template<class T> struct decay_array<T[]> { typedef T* type; };
+
+ template<class T> struct not_char_pointer { enum { value = 1 }; };
+ template<> struct not_char_pointer<char*> { enum { value = 0 }; };
+ template<> struct not_char_pointer<const char*> { enum { value = 0 }; };
+
+ template<class T> struct can_use_op : public not_char_pointer<typename decay_array<T>::type> {};
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+ // clang-format on
+
+ struct DOCTEST_INTERFACE TestFailureException
+ {
+ };
+
+ DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at);
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ DOCTEST_NORETURN
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+ DOCTEST_INTERFACE void throwException();
+
+ struct DOCTEST_INTERFACE Subcase
+ {
+ SubcaseSignature m_signature;
+ bool m_entered = false;
+
+ Subcase(const String& name, const char* file, int line);
+ ~Subcase();
+
+ operator bool() const;
+ };
+
+ template <typename L, typename R>
+ String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op,
+ const DOCTEST_REF_WRAP(R) rhs) {
+ return toString(lhs) + op + toString(rhs);
+ }
+
+#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \
+ template <typename R> \
+ DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) { \
+ bool res = op_macro(lhs, rhs); \
+ if(m_at & assertType::is_false) \
+ res = !res; \
+ if(!res || doctest::getContextOptions()->success) \
+ return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \
+ return Result(res); \
+ }
+
+ // more checks could be added - like in Catch:
+ // https://github.com/catchorg/Catch2/pull/1480/files
+ // https://github.com/catchorg/Catch2/pull/1481/files
+#define DOCTEST_FORBIT_EXPRESSION(rt, op) \
+ template <typename R> \
+ rt& operator op(const R&) { \
+ static_assert(deferred_false<R>::value, \
+ "Expression Too Complex Please Rewrite As Binary Comparison!"); \
+ return *this; \
+ }
+
+ struct DOCTEST_INTERFACE Result
+ {
+ bool m_passed;
+ String m_decomp;
+
+ Result(bool passed, const String& decomposition = String());
+
+ // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence
+ DOCTEST_FORBIT_EXPRESSION(Result, &)
+ DOCTEST_FORBIT_EXPRESSION(Result, ^)
+ DOCTEST_FORBIT_EXPRESSION(Result, |)
+ DOCTEST_FORBIT_EXPRESSION(Result, &&)
+ DOCTEST_FORBIT_EXPRESSION(Result, ||)
+ DOCTEST_FORBIT_EXPRESSION(Result, ==)
+ DOCTEST_FORBIT_EXPRESSION(Result, !=)
+ DOCTEST_FORBIT_EXPRESSION(Result, <)
+ DOCTEST_FORBIT_EXPRESSION(Result, >)
+ DOCTEST_FORBIT_EXPRESSION(Result, <=)
+ DOCTEST_FORBIT_EXPRESSION(Result, >=)
+ DOCTEST_FORBIT_EXPRESSION(Result, =)
+ DOCTEST_FORBIT_EXPRESSION(Result, +=)
+ DOCTEST_FORBIT_EXPRESSION(Result, -=)
+ DOCTEST_FORBIT_EXPRESSION(Result, *=)
+ DOCTEST_FORBIT_EXPRESSION(Result, /=)
+ DOCTEST_FORBIT_EXPRESSION(Result, %=)
+ DOCTEST_FORBIT_EXPRESSION(Result, <<=)
+ DOCTEST_FORBIT_EXPRESSION(Result, >>=)
+ DOCTEST_FORBIT_EXPRESSION(Result, &=)
+ DOCTEST_FORBIT_EXPRESSION(Result, ^=)
+ DOCTEST_FORBIT_EXPRESSION(Result, |=)
+ };
+
+#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+ DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion")
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare")
+ //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion")
+ //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion")
+ //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal")
+
+ DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion")
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare")
+ //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion")
+ //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion")
+ //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal")
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+ // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389
+ DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch
+ DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch
+ DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch
+ //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation
+
+#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+ // clang-format off
+#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_COMPARISON_RETURN_TYPE bool
+#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type
+ inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); }
+ inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); }
+ inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); }
+ inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); }
+ inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); }
+ inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); }
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+ // clang-format on
+
+#define DOCTEST_RELATIONAL_OP(name, op) \
+ template <typename L, typename R> \
+ DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \
+ const DOCTEST_REF_WRAP(R) rhs) { \
+ return lhs op rhs; \
+ }
+
+ DOCTEST_RELATIONAL_OP(eq, ==)
+ DOCTEST_RELATIONAL_OP(ne, !=)
+ DOCTEST_RELATIONAL_OP(lt, <)
+ DOCTEST_RELATIONAL_OP(gt, >)
+ DOCTEST_RELATIONAL_OP(le, <=)
+ DOCTEST_RELATIONAL_OP(ge, >=)
+
+#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_CMP_EQ(l, r) l == r
+#define DOCTEST_CMP_NE(l, r) l != r
+#define DOCTEST_CMP_GT(l, r) l > r
+#define DOCTEST_CMP_LT(l, r) l < r
+#define DOCTEST_CMP_GE(l, r) l >= r
+#define DOCTEST_CMP_LE(l, r) l <= r
+#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_CMP_EQ(l, r) eq(l, r)
+#define DOCTEST_CMP_NE(l, r) ne(l, r)
+#define DOCTEST_CMP_GT(l, r) gt(l, r)
+#define DOCTEST_CMP_LT(l, r) lt(l, r)
+#define DOCTEST_CMP_GE(l, r) ge(l, r)
+#define DOCTEST_CMP_LE(l, r) le(l, r)
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+
+ template <typename L>
+ // cppcheck-suppress copyCtorAndEqOperator
+ struct Expression_lhs
+ {
+ L lhs;
+ assertType::Enum m_at;
+
+ explicit Expression_lhs(L in, assertType::Enum at)
+ : lhs(in)
+ , m_at(at) {}
+
+ DOCTEST_NOINLINE operator Result() {
+ bool res = !!lhs;
+ if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
+ res = !res;
+
+ if(!res || getContextOptions()->success)
+ return Result(res, toString(lhs));
+ return Result(res);
+ }
+
+ // clang-format off
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional
+ DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional
+ // clang-format on
+
+ // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=)
+ // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the
+ // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression...
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<)
+ DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>)
+ };
+
+#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+ struct DOCTEST_INTERFACE ExpressionDecomposer
+ {
+ assertType::Enum m_at;
+
+ ExpressionDecomposer(assertType::Enum at);
+
+ // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table)
+ // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now...
+ // https://github.com/catchorg/Catch2/issues/870
+ // https://github.com/catchorg/Catch2/issues/565
+ template <typename L>
+ Expression_lhs<const DOCTEST_REF_WRAP(L)> operator<<(const DOCTEST_REF_WRAP(L) operand) {
+ return Expression_lhs<const DOCTEST_REF_WRAP(L)>(operand, m_at);
+ }
+ };
+
+ struct DOCTEST_INTERFACE TestSuite
+ {
+ const char* m_test_suite;
+ const char* m_description;
+ bool m_skip;
+ bool m_may_fail;
+ bool m_should_fail;
+ int m_expected_failures;
+ double m_timeout;
+
+ TestSuite& operator*(const char* in);
+
+ template <typename T>
+ TestSuite& operator*(const T& in) {
+ in.fill(*this);
+ return *this;
+ }
+ };
+
+ typedef void (*funcType)();
+
+ struct DOCTEST_INTERFACE TestCase : public TestCaseData
+ {
+ funcType m_test; // a function pointer to the test case
+
+ const char* m_type; // for templated test cases - gets appended to the real name
+ int m_template_id; // an ID used to distinguish between the different versions of a templated test case
+ String m_full_name; // contains the name (only for templated test cases!) + the template type
+
+ TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
+ const char* type = "", int template_id = -1);
+
+ TestCase(const TestCase& other);
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function
+ TestCase& operator=(const TestCase& other);
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+ TestCase& operator*(const char* in);
+
+ template <typename T>
+ TestCase& operator*(const T& in) {
+ in.fill(*this);
+ return *this;
+ }
+
+ bool operator<(const TestCase& other) const;
+ };
+
+ // forward declarations of functions used by the macros
+ DOCTEST_INTERFACE int regTest(const TestCase& tc);
+ DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts);
+ DOCTEST_INTERFACE bool isDebuggerActive();
+
+ template<typename T>
+ int instantiationHelper(const T&) { return 0; }
+
+ namespace binaryAssertComparison {
+ enum Enum
+ {
+ eq = 0,
+ ne,
+ gt,
+ lt,
+ ge,
+ le
+ };
+ } // namespace binaryAssertComparison
+
+ // clang-format off
+ template <int, class L, class R> struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } };
+
+#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \
+ template <class L, class R> struct RelationalComparator<n, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } };
+ // clang-format on
+
+ DOCTEST_BINARY_RELATIONAL_OP(0, eq)
+ DOCTEST_BINARY_RELATIONAL_OP(1, ne)
+ DOCTEST_BINARY_RELATIONAL_OP(2, gt)
+ DOCTEST_BINARY_RELATIONAL_OP(3, lt)
+ DOCTEST_BINARY_RELATIONAL_OP(4, ge)
+ DOCTEST_BINARY_RELATIONAL_OP(5, le)
+
+ struct DOCTEST_INTERFACE ResultBuilder : public AssertData
+ {
+ ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type = "", const char* exception_string = "");
+
+ void setResult(const Result& res);
+
+ template <int comparison, typename L, typename R>
+ DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs,
+ const DOCTEST_REF_WRAP(R) rhs) {
+ m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
+ if(m_failed || getContextOptions()->success)
+ m_decomp = stringifyBinaryExpr(lhs, ", ", rhs);
+ }
+
+ template <typename L>
+ DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) {
+ m_failed = !val;
+
+ if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
+ m_failed = !m_failed;
+
+ if(m_failed || getContextOptions()->success)
+ m_decomp = toString(val);
+ }
+
+ void translateException();
+
+ bool log();
+ void react() const;
+ };
+
+ namespace assertAction {
+ enum Enum
+ {
+ nothing = 0,
+ dbgbreak = 1,
+ shouldthrow = 2
+ };
+ } // namespace assertAction
+
+ DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad);
+
+ DOCTEST_INTERFACE void decomp_assert(assertType::Enum at, const char* file, int line,
+ const char* expr, Result result);
+
+#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \
+ do { \
+ if(!is_running_in_test) { \
+ if(failed) { \
+ ResultBuilder rb(at, file, line, expr); \
+ rb.m_failed = failed; \
+ rb.m_decomp = decomp; \
+ failed_out_of_a_testing_context(rb); \
+ if(isDebuggerActive() && !getContextOptions()->no_breaks) \
+ DOCTEST_BREAK_INTO_DEBUGGER(); \
+ if(checkIfShouldThrow(at)) \
+ throwException(); \
+ } \
+ return; \
+ } \
+ } while(false)
+
+#define DOCTEST_ASSERT_IN_TESTS(decomp) \
+ ResultBuilder rb(at, file, line, expr); \
+ rb.m_failed = failed; \
+ if(rb.m_failed || getContextOptions()->success) \
+ rb.m_decomp = decomp; \
+ if(rb.log()) \
+ DOCTEST_BREAK_INTO_DEBUGGER(); \
+ if(rb.m_failed && checkIfShouldThrow(at)) \
+ throwException()
+
+ template <int comparison, typename L, typename R>
+ DOCTEST_NOINLINE void binary_assert(assertType::Enum at, const char* file, int line,
+ const char* expr, const DOCTEST_REF_WRAP(L) lhs,
+ const DOCTEST_REF_WRAP(R) rhs) {
+ bool failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
+
+ // ###################################################################################
+ // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
+ // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
+ // ###################################################################################
+ DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs));
+ DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs));
+ }
+
+ template <typename L>
+ DOCTEST_NOINLINE void unary_assert(assertType::Enum at, const char* file, int line,
+ const char* expr, const DOCTEST_REF_WRAP(L) val) {
+ bool failed = !val;
+
+ if(at & assertType::is_false) //!OCLINT bitwise operator in conditional
+ failed = !failed;
+
+ // ###################################################################################
+ // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
+ // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
+ // ###################################################################################
+ DOCTEST_ASSERT_OUT_OF_TESTS(toString(val));
+ DOCTEST_ASSERT_IN_TESTS(toString(val));
+ }
+
+ struct DOCTEST_INTERFACE IExceptionTranslator
+ {
+ IExceptionTranslator();
+ virtual ~IExceptionTranslator();
+ virtual bool translate(String&) const = 0;
+ };
+
+ template <typename T>
+ class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class
+ {
+ public:
+ explicit ExceptionTranslator(String (*translateFunction)(T))
+ : m_translateFunction(translateFunction) {}
+
+ bool translate(String& res) const override {
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ try {
+ throw; // lgtm [cpp/rethrow-no-exception]
+ // cppcheck-suppress catchExceptionByValue
+ } catch(T ex) { // NOLINT
+ res = m_translateFunction(ex); //!OCLINT parameter reassignment
+ return true;
+ } catch(...) {} //!OCLINT - empty catch statement
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+ ((void)res); // to silence -Wunused-parameter
+ return false;
+ }
+
+ private:
+ String (*m_translateFunction)(T);
+ };
+
+ DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et);
+
+ template <bool C>
+ struct StringStreamBase
+ {
+ template <typename T>
+ static void convert(std::ostream* s, const T& in) {
+ *s << toString(in);
+ }
+
+ // always treat char* as a string in this context - no matter
+ // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined
+ static void convert(std::ostream* s, const char* in) { *s << String(in); }
+ };
+
+ template <>
+ struct StringStreamBase<true>
+ {
+ template <typename T>
+ static void convert(std::ostream* s, const T& in) {
+ *s << in;
+ }
+ };
+
+ template <typename T>
+ struct StringStream : public StringStreamBase<has_insertion_operator<T>::value>
+ {};
+
+ template <typename T>
+ void toStream(std::ostream* s, const T& value) {
+ StringStream<T>::convert(s, value);
+ }
+
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+ DOCTEST_INTERFACE void toStream(std::ostream* s, char* in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in);
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+ DOCTEST_INTERFACE void toStream(std::ostream* s, bool in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, float in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, double in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, double long in);
+
+ DOCTEST_INTERFACE void toStream(std::ostream* s, char in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, int short in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, int in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, int long in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in);
+ DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in);
+
+ // ContextScope base class used to allow implementing methods of ContextScope
+ // that don't depend on the template parameter in doctest.cpp.
+ class DOCTEST_INTERFACE ContextScopeBase : public IContextScope {
+ protected:
+ ContextScopeBase();
+
+ void destroy();
+ };
+
+ template <typename L> class ContextScope : public ContextScopeBase
+ {
+ const L &lambda_;
+
+ public:
+ explicit ContextScope(const L &lambda) : lambda_(lambda) {}
+
+ ContextScope(ContextScope &&other) : lambda_(other.lambda_) {}
+
+ void stringify(std::ostream* s) const override { lambda_(s); }
+
+ ~ContextScope() override { destroy(); }
+ };
+
+ struct DOCTEST_INTERFACE MessageBuilder : public MessageData
+ {
+ std::ostream* m_stream;
+
+ MessageBuilder(const char* file, int line, assertType::Enum severity);
+ MessageBuilder() = delete;
+ ~MessageBuilder();
+
+ template <typename T>
+ MessageBuilder& operator<<(const T& in) {
+ toStream(m_stream, in);
+ return *this;
+ }
+
+ bool log();
+ void react();
+ };
+
+ template <typename L>
+ ContextScope<L> MakeContextScope(const L &lambda) {
+ return ContextScope<L>(lambda);
+ }
+} // namespace detail
+
+#define DOCTEST_DEFINE_DECORATOR(name, type, def) \
+ struct name \
+ { \
+ type data; \
+ name(type in = def) \
+ : data(in) {} \
+ void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \
+ void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \
+ }
+
+DOCTEST_DEFINE_DECORATOR(test_suite, const char*, "");
+DOCTEST_DEFINE_DECORATOR(description, const char*, "");
+DOCTEST_DEFINE_DECORATOR(skip, bool, true);
+DOCTEST_DEFINE_DECORATOR(timeout, double, 0);
+DOCTEST_DEFINE_DECORATOR(may_fail, bool, true);
+DOCTEST_DEFINE_DECORATOR(should_fail, bool, true);
+DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0);
+
+template <typename T>
+int registerExceptionTranslator(String (*translateFunction)(T)) {
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors")
+ static detail::ExceptionTranslator<T> exceptionTranslator(translateFunction);
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ detail::registerExceptionTranslatorImpl(&exceptionTranslator);
+ return 0;
+}
+
+} // namespace doctest
+
+// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro
+// introduces an anonymous namespace in which getCurrentTestSuite gets overridden
+namespace doctest_detail_test_suite_ns {
+DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite();
+} // namespace doctest_detail_test_suite_ns
+
+namespace doctest {
+#else // DOCTEST_CONFIG_DISABLE
+template <typename T>
+int registerExceptionTranslator(String (*)(T)) {
+ return 0;
+}
+#endif // DOCTEST_CONFIG_DISABLE
+
+namespace detail {
+ typedef void (*assert_handler)(const AssertData&);
+ struct ContextState;
+} // namespace detail
+
+class DOCTEST_INTERFACE Context
+{
+ detail::ContextState* p;
+
+ void parseArgs(int argc, const char* const* argv, bool withDefaults = false);
+
+public:
+ explicit Context(int argc = 0, const char* const* argv = nullptr);
+
+ ~Context();
+
+ void applyCommandLine(int argc, const char* const* argv);
+
+ void addFilter(const char* filter, const char* value);
+ void clearFilters();
+ void setOption(const char* option, int value);
+ void setOption(const char* option, const char* value);
+
+ bool shouldExit();
+
+ void setAsDefaultForAssertsOutOfTestCases();
+
+ void setAssertHandler(detail::assert_handler ah);
+
+ int run();
+};
+
+namespace TestCaseFailureReason {
+ enum Enum
+ {
+ None = 0,
+ AssertFailure = 1, // an assertion has failed in the test case
+ Exception = 2, // test case threw an exception
+ Crash = 4, // a crash...
+ TooManyFailedAsserts = 8, // the abort-after option
+ Timeout = 16, // see the timeout decorator
+ ShouldHaveFailedButDidnt = 32, // see the should_fail decorator
+ ShouldHaveFailedAndDid = 64, // see the should_fail decorator
+ DidntFailExactlyNumTimes = 128, // see the expected_failures decorator
+ FailedExactlyNumTimes = 256, // see the expected_failures decorator
+ CouldHaveFailedAndDid = 512 // see the may_fail decorator
+ };
+} // namespace TestCaseFailureReason
+
+struct DOCTEST_INTERFACE CurrentTestCaseStats
+{
+ int numAssertsCurrentTest;
+ int numAssertsFailedCurrentTest;
+ double seconds;
+ int failure_flags; // use TestCaseFailureReason::Enum
+};
+
+struct DOCTEST_INTERFACE TestCaseException
+{
+ String error_string;
+ bool is_crash;
+};
+
+struct DOCTEST_INTERFACE TestRunStats
+{
+ unsigned numTestCases;
+ unsigned numTestCasesPassingFilters;
+ unsigned numTestSuitesPassingFilters;
+ unsigned numTestCasesFailed;
+ int numAsserts;
+ int numAssertsFailed;
+};
+
+struct QueryData
+{
+ const TestRunStats* run_stats = nullptr;
+ const TestCaseData** data = nullptr;
+ unsigned num_data = 0;
+};
+
+struct DOCTEST_INTERFACE IReporter
+{
+ // The constructor has to accept "const ContextOptions&" as a single argument
+ // which has most of the options for the run + a pointer to the stdout stream
+ // Reporter(const ContextOptions& in)
+
+ // called when a query should be reported (listing test cases, printing the version, etc.)
+ virtual void report_query(const QueryData&) = 0;
+
+ // called when the whole test run starts
+ virtual void test_run_start() = 0;
+ // called when the whole test run ends (caching a pointer to the input doesn't make sense here)
+ virtual void test_run_end(const TestRunStats&) = 0;
+
+ // called when a test case is started (safe to cache a pointer to the input)
+ virtual void test_case_start(const TestCaseData&) = 0;
+ // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input)
+ virtual void test_case_reenter(const TestCaseData&) = 0;
+ // called when a test case has ended
+ virtual void test_case_end(const CurrentTestCaseStats&) = 0;
+
+ // called when an exception is thrown from the test case (or it crashes)
+ virtual void test_case_exception(const TestCaseException&) = 0;
+
+ // called whenever a subcase is entered (don't cache pointers to the input)
+ virtual void subcase_start(const SubcaseSignature&) = 0;
+ // called whenever a subcase is exited (don't cache pointers to the input)
+ virtual void subcase_end() = 0;
+
+ // called for each assert (don't cache pointers to the input)
+ virtual void log_assert(const AssertData&) = 0;
+ // called for each message (don't cache pointers to the input)
+ virtual void log_message(const MessageData&) = 0;
+
+ // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator
+ // or isn't in the execution range (between first and last) (safe to cache a pointer to the input)
+ virtual void test_case_skipped(const TestCaseData&) = 0;
+
+ // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have
+ virtual ~IReporter();
+
+ // can obtain all currently active contexts and stringify them if one wishes to do so
+ static int get_num_active_contexts();
+ static const IContextScope* const* get_active_contexts();
+
+ // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown
+ static int get_num_stringified_contexts();
+ static const String* get_stringified_contexts();
+};
+
+namespace detail {
+ typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&);
+
+ DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter);
+
+ template <typename Reporter>
+ IReporter* reporterCreator(const ContextOptions& o) {
+ return new Reporter(o);
+ }
+} // namespace detail
+
+template <typename Reporter>
+int registerReporter(const char* name, int priority, bool isReporter) {
+ detail::registerReporterImpl(name, priority, detail::reporterCreator<Reporter>, isReporter);
+ return 0;
+}
+} // namespace doctest
+
+// if registering is not disabled
+#if !defined(DOCTEST_CONFIG_DISABLE)
+
+// common code in asserts - for convenience
+#define DOCTEST_ASSERT_LOG_AND_REACT(b) \
+ if(b.log()) \
+ DOCTEST_BREAK_INTO_DEBUGGER(); \
+ b.react()
+
+#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+#define DOCTEST_WRAP_IN_TRY(x) x;
+#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+#define DOCTEST_WRAP_IN_TRY(x) \
+ try { \
+ x; \
+ } catch(...) { _DOCTEST_RB.translateException(); }
+#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+
+#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS
+#define DOCTEST_CAST_TO_VOID(...) \
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \
+ static_cast<void>(__VA_ARGS__); \
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
+#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS
+#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__;
+#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS
+
+// registers the test by initializing a dummy var with a function
+#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \
+ global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \
+ doctest::detail::regTest( \
+ doctest::detail::TestCase( \
+ f, __FILE__, __LINE__, \
+ doctest_detail_test_suite_ns::getCurrentTestSuite()) * \
+ decorators); \
+ DOCTEST_GLOBAL_NO_WARNINGS_END()
+
+#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \
+ namespace { \
+ struct der : public base \
+ { \
+ void f(); \
+ }; \
+ static void func() { \
+ der v; \
+ v.f(); \
+ } \
+ DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \
+ } \
+ inline DOCTEST_NOINLINE void der::f()
+
+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \
+ static void f(); \
+ DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \
+ static void f()
+
+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \
+ static doctest::detail::funcType proxy() { return f; } \
+ DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators) \
+ static void f()
+
+// for registering tests
+#define DOCTEST_TEST_CASE(decorators) \
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators)
+
+// for registering tests in classes - requires C++17 for inline variables!
+#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L)
+#define DOCTEST_TEST_CASE_CLASS(decorators) \
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), \
+ DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_), \
+ decorators)
+#else // DOCTEST_TEST_CASE_CLASS
+#define DOCTEST_TEST_CASE_CLASS(...) \
+ TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER
+#endif // DOCTEST_TEST_CASE_CLASS
+
+// for registering tests with a fixture
+#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \
+ DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c, \
+ DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators)
+
+// for converting types to strings without the <typeinfo> header and demangling
+#define DOCTEST_TYPE_TO_STRING_IMPL(...) \
+ template <> \
+ inline const char* type_to_string<__VA_ARGS__>() { \
+ return "<" #__VA_ARGS__ ">"; \
+ }
+#define DOCTEST_TYPE_TO_STRING(...) \
+ namespace doctest { namespace detail { \
+ DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \
+ } \
+ } \
+ typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \
+ template <typename T> \
+ static void func(); \
+ namespace { \
+ template <typename Tuple> \
+ struct iter; \
+ template <typename Type, typename... Rest> \
+ struct iter<std::tuple<Type, Rest...>> \
+ { \
+ iter(const char* file, unsigned line, int index) { \
+ doctest::detail::regTest(doctest::detail::TestCase(func<Type>, file, line, \
+ doctest_detail_test_suite_ns::getCurrentTestSuite(), \
+ doctest::detail::type_to_string<Type>(), \
+ int(line) * 1000 + index) \
+ * dec); \
+ iter<std::tuple<Rest...>>(file, line, index + 1); \
+ } \
+ }; \
+ template <> \
+ struct iter<std::tuple<>> \
+ { \
+ iter(const char*, unsigned, int) {} \
+ }; \
+ } \
+ template <typename T> \
+ static void func()
+
+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \
+ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \
+ DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_))
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = \
+ doctest::detail::instantiationHelper(DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0));\
+ DOCTEST_GLOBAL_NO_WARNINGS_END()
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \
+ DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \
+ typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \
+ DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \
+ typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \
+ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \
+ DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>) \
+ template <typename T> \
+ static void anon()
+
+#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \
+ DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__)
+
+// for subcases
+#define DOCTEST_SUBCASE(name) \
+ if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \
+ doctest::detail::Subcase(name, __FILE__, __LINE__))
+
+// for grouping tests in test suites by using code blocks
+#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \
+ namespace ns_name { namespace doctest_detail_test_suite_ns { \
+ static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \
+ static doctest::detail::TestSuite data; \
+ static bool inited = false; \
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP \
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP \
+ if(!inited) { \
+ data* decorators; \
+ inited = true; \
+ } \
+ return data; \
+ } \
+ } \
+ } \
+ namespace ns_name
+
+#define DOCTEST_TEST_SUITE(decorators) \
+ DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_))
+
+// for starting a testsuite block
+#define DOCTEST_TEST_SUITE_BEGIN(decorators) \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \
+ doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators); \
+ DOCTEST_GLOBAL_NO_WARNINGS_END() \
+ typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+// for ending a testsuite block
+#define DOCTEST_TEST_SUITE_END \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \
+ doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""); \
+ DOCTEST_GLOBAL_NO_WARNINGS_END() \
+ typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+// for registering exception translators
+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \
+ inline doctest::String translatorName(signature); \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) = \
+ doctest::registerExceptionTranslator(translatorName); \
+ DOCTEST_GLOBAL_NO_WARNINGS_END() \
+ doctest::String translatorName(signature)
+
+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \
+ DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \
+ signature)
+
+// for registering reporters
+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \
+ doctest::registerReporter<reporter>(name, priority, true); \
+ DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+// for registering listeners
+#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \
+ doctest::registerReporter<reporter>(name, priority, false); \
+ DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+// for logging
+#define DOCTEST_INFO(expression) \
+ DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), \
+ DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), expression)
+
+#define DOCTEST_INFO_IMPL(lambda_name, mb_name, s_name, expression) \
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4626) \
+ auto lambda_name = [&](std::ostream* s_name) { \
+ doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \
+ mb_name.m_stream = s_name; \
+ mb_name << expression; \
+ }; \
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP \
+ auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(lambda_name)
+
+#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x)
+
+#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \
+ do { \
+ doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \
+ mb << x; \
+ DOCTEST_ASSERT_LOG_AND_REACT(mb); \
+ } while(false)
+
+// clang-format off
+#define DOCTEST_ADD_MESSAGE_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x)
+#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x)
+#define DOCTEST_ADD_FAIL_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x)
+// clang-format on
+
+#define DOCTEST_MESSAGE(x) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, x)
+#define DOCTEST_FAIL_CHECK(x) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, x)
+#define DOCTEST_FAIL(x) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, x)
+
+#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility.
+
+#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \
+ doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \
+ << __VA_ARGS__)) \
+ DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \
+ do { \
+ DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \
+ } while(false)
+
+#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+// necessary for <ASSERT>_MESSAGE
+#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1
+
+#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \
+ doctest::detail::decomp_assert( \
+ doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \
+ doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \
+ << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__)
+#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__)
+#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__)
+#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__)
+#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__)
+#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__)
+
+// clang-format off
+#define DOCTEST_WARN_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while(false)
+#define DOCTEST_CHECK_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while(false)
+#define DOCTEST_REQUIRE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while(false)
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while(false)
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while(false)
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while(false)
+// clang-format on
+
+#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \
+ do { \
+ if(!doctest::getContextOptions()->no_throw) { \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #expr, #__VA_ARGS__, message); \
+ try { \
+ DOCTEST_CAST_TO_VOID(expr) \
+ } catch(const doctest::detail::remove_const< \
+ doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \
+ _DOCTEST_RB.translateException(); \
+ _DOCTEST_RB.m_threw_as = true; \
+ } catch(...) { _DOCTEST_RB.translateException(); } \
+ DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
+ } \
+ } while(false)
+
+#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \
+ do { \
+ if(!doctest::getContextOptions()->no_throw) { \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, expr_str, "", __VA_ARGS__); \
+ try { \
+ DOCTEST_CAST_TO_VOID(expr) \
+ } catch(...) { _DOCTEST_RB.translateException(); } \
+ DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
+ } \
+ } while(false)
+
+#define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \
+ do { \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ try { \
+ DOCTEST_CAST_TO_VOID(__VA_ARGS__) \
+ } catch(...) { _DOCTEST_RB.translateException(); } \
+ DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
+ } while(false)
+
+// clang-format off
+#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "")
+#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "")
+#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "")
+
+#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__)
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__)
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__)
+
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__)
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__)
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__)
+
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__)
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__)
+
+#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__)
+#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__)
+#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__)
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS(expr); } while(false)
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS(expr); } while(false)
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS(expr); } while(false)
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_AS(expr, ex); } while(false)
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_AS(expr, ex); } while(false)
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while(false)
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH(expr, with); } while(false)
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH(expr, with); } while(false)
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while(false)
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while(false)
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while(false)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while(false)
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_NOTHROW(expr); } while(false)
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_NOTHROW(expr); } while(false)
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_NOTHROW(expr); } while(false)
+// clang-format on
+
+#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \
+ do { \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ DOCTEST_WRAP_IN_TRY( \
+ _DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>( \
+ __VA_ARGS__)) \
+ DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
+ } while(false)
+
+#define DOCTEST_UNARY_ASSERT(assert_type, ...) \
+ do { \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__)) \
+ DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
+ } while(false)
+
+#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \
+ doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>( \
+ doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__)
+
+#define DOCTEST_UNARY_ASSERT(assert_type, ...) \
+ doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \
+ #__VA_ARGS__, __VA_ARGS__)
+
+#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__)
+#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__)
+#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__)
+#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__)
+#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__)
+#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__)
+#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__)
+#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__)
+#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__)
+#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__)
+#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__)
+#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__)
+#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__)
+#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__)
+#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__)
+#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__)
+#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__)
+#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__)
+
+#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__)
+#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__)
+#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__)
+#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__)
+#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__)
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__)
+
+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#undef DOCTEST_WARN_THROWS
+#undef DOCTEST_CHECK_THROWS
+#undef DOCTEST_REQUIRE_THROWS
+#undef DOCTEST_WARN_THROWS_AS
+#undef DOCTEST_CHECK_THROWS_AS
+#undef DOCTEST_REQUIRE_THROWS_AS
+#undef DOCTEST_WARN_THROWS_WITH
+#undef DOCTEST_CHECK_THROWS_WITH
+#undef DOCTEST_REQUIRE_THROWS_WITH
+#undef DOCTEST_WARN_THROWS_WITH_AS
+#undef DOCTEST_CHECK_THROWS_WITH_AS
+#undef DOCTEST_REQUIRE_THROWS_WITH_AS
+#undef DOCTEST_WARN_NOTHROW
+#undef DOCTEST_CHECK_NOTHROW
+#undef DOCTEST_REQUIRE_NOTHROW
+
+#undef DOCTEST_WARN_THROWS_MESSAGE
+#undef DOCTEST_CHECK_THROWS_MESSAGE
+#undef DOCTEST_REQUIRE_THROWS_MESSAGE
+#undef DOCTEST_WARN_THROWS_AS_MESSAGE
+#undef DOCTEST_CHECK_THROWS_AS_MESSAGE
+#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE
+#undef DOCTEST_WARN_THROWS_WITH_MESSAGE
+#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE
+#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE
+#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE
+#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE
+#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE
+#undef DOCTEST_WARN_NOTHROW_MESSAGE
+#undef DOCTEST_CHECK_NOTHROW_MESSAGE
+#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE
+
+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+
+#define DOCTEST_WARN_THROWS(...) ((void)0)
+#define DOCTEST_CHECK_THROWS(...) ((void)0)
+#define DOCTEST_REQUIRE_THROWS(...) ((void)0)
+#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0)
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0)
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0)
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0)
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0)
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0)
+#define DOCTEST_WARN_NOTHROW(...) ((void)0)
+#define DOCTEST_CHECK_NOTHROW(...) ((void)0)
+#define DOCTEST_REQUIRE_NOTHROW(...) ((void)0)
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0)
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0)
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0)
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0)
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0)
+
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+
+#undef DOCTEST_REQUIRE
+#undef DOCTEST_REQUIRE_FALSE
+#undef DOCTEST_REQUIRE_MESSAGE
+#undef DOCTEST_REQUIRE_FALSE_MESSAGE
+#undef DOCTEST_REQUIRE_EQ
+#undef DOCTEST_REQUIRE_NE
+#undef DOCTEST_REQUIRE_GT
+#undef DOCTEST_REQUIRE_LT
+#undef DOCTEST_REQUIRE_GE
+#undef DOCTEST_REQUIRE_LE
+#undef DOCTEST_REQUIRE_UNARY
+#undef DOCTEST_REQUIRE_UNARY_FALSE
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+// =================================================================================================
+// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! ==
+// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! ==
+// =================================================================================================
+#else // DOCTEST_CONFIG_DISABLE
+
+#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \
+ namespace { \
+ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
+ struct der : public base \
+ { void f(); }; \
+ } \
+ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
+ inline void der<DOCTEST_UNUSED_TEMPLATE_TYPE>::f()
+
+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \
+ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
+ static inline void f()
+
+// for registering tests
+#define DOCTEST_TEST_CASE(name) \
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name)
+
+// for registering tests in classes
+#define DOCTEST_TEST_CASE_CLASS(name) \
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name)
+
+// for registering tests with a fixture
+#define DOCTEST_TEST_CASE_FIXTURE(x, name) \
+ DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x, \
+ DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name)
+
+// for converting types to strings without the <typeinfo> header and demangling
+#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+#define DOCTEST_TYPE_TO_STRING_IMPL(...)
+
+// for typed tests
+#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \
+ template <typename type> \
+ inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)()
+
+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \
+ template <typename type> \
+ inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)()
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \
+ typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \
+ typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+// for subcases
+#define DOCTEST_SUBCASE(name)
+
+// for a testsuite block
+#define DOCTEST_TEST_SUITE(name) namespace
+
+// for starting a testsuite block
+#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+// for ending a testsuite block
+#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \
+ template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
+ static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature)
+
+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)
+#define DOCTEST_REGISTER_LISTENER(name, priority, reporter)
+
+#define DOCTEST_INFO(x) ((void)0)
+#define DOCTEST_CAPTURE(x) ((void)0)
+#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0)
+#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) ((void)0)
+#define DOCTEST_ADD_FAIL_AT(file, line, x) ((void)0)
+#define DOCTEST_MESSAGE(x) ((void)0)
+#define DOCTEST_FAIL_CHECK(x) ((void)0)
+#define DOCTEST_FAIL(x) ((void)0)
+
+#define DOCTEST_WARN(...) ((void)0)
+#define DOCTEST_CHECK(...) ((void)0)
+#define DOCTEST_REQUIRE(...) ((void)0)
+#define DOCTEST_WARN_FALSE(...) ((void)0)
+#define DOCTEST_CHECK_FALSE(...) ((void)0)
+#define DOCTEST_REQUIRE_FALSE(...) ((void)0)
+
+#define DOCTEST_WARN_MESSAGE(cond, msg) ((void)0)
+#define DOCTEST_CHECK_MESSAGE(cond, msg) ((void)0)
+#define DOCTEST_REQUIRE_MESSAGE(cond, msg) ((void)0)
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) ((void)0)
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) ((void)0)
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) ((void)0)
+
+#define DOCTEST_WARN_THROWS(...) ((void)0)
+#define DOCTEST_CHECK_THROWS(...) ((void)0)
+#define DOCTEST_REQUIRE_THROWS(...) ((void)0)
+#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0)
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0)
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0)
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0)
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0)
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0)
+#define DOCTEST_WARN_NOTHROW(...) ((void)0)
+#define DOCTEST_CHECK_NOTHROW(...) ((void)0)
+#define DOCTEST_REQUIRE_NOTHROW(...) ((void)0)
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0)
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0)
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0)
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0)
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0)
+
+#define DOCTEST_WARN_EQ(...) ((void)0)
+#define DOCTEST_CHECK_EQ(...) ((void)0)
+#define DOCTEST_REQUIRE_EQ(...) ((void)0)
+#define DOCTEST_WARN_NE(...) ((void)0)
+#define DOCTEST_CHECK_NE(...) ((void)0)
+#define DOCTEST_REQUIRE_NE(...) ((void)0)
+#define DOCTEST_WARN_GT(...) ((void)0)
+#define DOCTEST_CHECK_GT(...) ((void)0)
+#define DOCTEST_REQUIRE_GT(...) ((void)0)
+#define DOCTEST_WARN_LT(...) ((void)0)
+#define DOCTEST_CHECK_LT(...) ((void)0)
+#define DOCTEST_REQUIRE_LT(...) ((void)0)
+#define DOCTEST_WARN_GE(...) ((void)0)
+#define DOCTEST_CHECK_GE(...) ((void)0)
+#define DOCTEST_REQUIRE_GE(...) ((void)0)
+#define DOCTEST_WARN_LE(...) ((void)0)
+#define DOCTEST_CHECK_LE(...) ((void)0)
+#define DOCTEST_REQUIRE_LE(...) ((void)0)
+
+#define DOCTEST_WARN_UNARY(...) ((void)0)
+#define DOCTEST_CHECK_UNARY(...) ((void)0)
+#define DOCTEST_REQUIRE_UNARY(...) ((void)0)
+#define DOCTEST_WARN_UNARY_FALSE(...) ((void)0)
+#define DOCTEST_CHECK_UNARY_FALSE(...) ((void)0)
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) ((void)0)
+
+#endif // DOCTEST_CONFIG_DISABLE
+
+// clang-format off
+// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS
+#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ
+#define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ
+#define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ
+#define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE
+#define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE
+#define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE
+#define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT
+#define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT
+#define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT
+#define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT
+#define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT
+#define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT
+#define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE
+#define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE
+#define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE
+#define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE
+#define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE
+#define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE
+
+#define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY
+#define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY
+#define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY
+#define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE
+#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE
+#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INVOKE
+// clang-format on
+
+// BDD style macros
+// clang-format off
+#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name)
+#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name)
+#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__)
+#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id)
+
+#define DOCTEST_GIVEN(name) DOCTEST_SUBCASE(" Given: " name)
+#define DOCTEST_WHEN(name) DOCTEST_SUBCASE(" When: " name)
+#define DOCTEST_AND_WHEN(name) DOCTEST_SUBCASE("And when: " name)
+#define DOCTEST_THEN(name) DOCTEST_SUBCASE(" Then: " name)
+#define DOCTEST_AND_THEN(name) DOCTEST_SUBCASE(" And: " name)
+// clang-format on
+
+// == SHORT VERSIONS OF THE MACROS
+#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES)
+
+#define TEST_CASE DOCTEST_TEST_CASE
+#define TEST_CASE_CLASS DOCTEST_TEST_CASE_CLASS
+#define TEST_CASE_FIXTURE DOCTEST_TEST_CASE_FIXTURE
+#define TYPE_TO_STRING DOCTEST_TYPE_TO_STRING
+#define TEST_CASE_TEMPLATE DOCTEST_TEST_CASE_TEMPLATE
+#define TEST_CASE_TEMPLATE_DEFINE DOCTEST_TEST_CASE_TEMPLATE_DEFINE
+#define TEST_CASE_TEMPLATE_INVOKE DOCTEST_TEST_CASE_TEMPLATE_INVOKE
+#define TEST_CASE_TEMPLATE_APPLY DOCTEST_TEST_CASE_TEMPLATE_APPLY
+#define SUBCASE DOCTEST_SUBCASE
+#define TEST_SUITE DOCTEST_TEST_SUITE
+#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN
+#define TEST_SUITE_END DOCTEST_TEST_SUITE_END
+#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR
+#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER
+#define REGISTER_LISTENER DOCTEST_REGISTER_LISTENER
+#define INFO DOCTEST_INFO
+#define CAPTURE DOCTEST_CAPTURE
+#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT
+#define ADD_FAIL_CHECK_AT DOCTEST_ADD_FAIL_CHECK_AT
+#define ADD_FAIL_AT DOCTEST_ADD_FAIL_AT
+#define MESSAGE DOCTEST_MESSAGE
+#define FAIL_CHECK DOCTEST_FAIL_CHECK
+#define FAIL DOCTEST_FAIL
+#define TO_LVALUE DOCTEST_TO_LVALUE
+
+#define WARN DOCTEST_WARN
+#define WARN_FALSE DOCTEST_WARN_FALSE
+#define WARN_THROWS DOCTEST_WARN_THROWS
+#define WARN_THROWS_AS DOCTEST_WARN_THROWS_AS
+#define WARN_THROWS_WITH DOCTEST_WARN_THROWS_WITH
+#define WARN_THROWS_WITH_AS DOCTEST_WARN_THROWS_WITH_AS
+#define WARN_NOTHROW DOCTEST_WARN_NOTHROW
+#define CHECK DOCTEST_CHECK
+#define CHECK_FALSE DOCTEST_CHECK_FALSE
+#define CHECK_THROWS DOCTEST_CHECK_THROWS
+#define CHECK_THROWS_AS DOCTEST_CHECK_THROWS_AS
+#define CHECK_THROWS_WITH DOCTEST_CHECK_THROWS_WITH
+#define CHECK_THROWS_WITH_AS DOCTEST_CHECK_THROWS_WITH_AS
+#define CHECK_NOTHROW DOCTEST_CHECK_NOTHROW
+#define REQUIRE DOCTEST_REQUIRE
+#define REQUIRE_FALSE DOCTEST_REQUIRE_FALSE
+#define REQUIRE_THROWS DOCTEST_REQUIRE_THROWS
+#define REQUIRE_THROWS_AS DOCTEST_REQUIRE_THROWS_AS
+#define REQUIRE_THROWS_WITH DOCTEST_REQUIRE_THROWS_WITH
+#define REQUIRE_THROWS_WITH_AS DOCTEST_REQUIRE_THROWS_WITH_AS
+#define REQUIRE_NOTHROW DOCTEST_REQUIRE_NOTHROW
+
+#define WARN_MESSAGE DOCTEST_WARN_MESSAGE
+#define WARN_FALSE_MESSAGE DOCTEST_WARN_FALSE_MESSAGE
+#define WARN_THROWS_MESSAGE DOCTEST_WARN_THROWS_MESSAGE
+#define WARN_THROWS_AS_MESSAGE DOCTEST_WARN_THROWS_AS_MESSAGE
+#define WARN_THROWS_WITH_MESSAGE DOCTEST_WARN_THROWS_WITH_MESSAGE
+#define WARN_THROWS_WITH_AS_MESSAGE DOCTEST_WARN_THROWS_WITH_AS_MESSAGE
+#define WARN_NOTHROW_MESSAGE DOCTEST_WARN_NOTHROW_MESSAGE
+#define CHECK_MESSAGE DOCTEST_CHECK_MESSAGE
+#define CHECK_FALSE_MESSAGE DOCTEST_CHECK_FALSE_MESSAGE
+#define CHECK_THROWS_MESSAGE DOCTEST_CHECK_THROWS_MESSAGE
+#define CHECK_THROWS_AS_MESSAGE DOCTEST_CHECK_THROWS_AS_MESSAGE
+#define CHECK_THROWS_WITH_MESSAGE DOCTEST_CHECK_THROWS_WITH_MESSAGE
+#define CHECK_THROWS_WITH_AS_MESSAGE DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE
+#define CHECK_NOTHROW_MESSAGE DOCTEST_CHECK_NOTHROW_MESSAGE
+#define REQUIRE_MESSAGE DOCTEST_REQUIRE_MESSAGE
+#define REQUIRE_FALSE_MESSAGE DOCTEST_REQUIRE_FALSE_MESSAGE
+#define REQUIRE_THROWS_MESSAGE DOCTEST_REQUIRE_THROWS_MESSAGE
+#define REQUIRE_THROWS_AS_MESSAGE DOCTEST_REQUIRE_THROWS_AS_MESSAGE
+#define REQUIRE_THROWS_WITH_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_MESSAGE
+#define REQUIRE_THROWS_WITH_AS_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE
+#define REQUIRE_NOTHROW_MESSAGE DOCTEST_REQUIRE_NOTHROW_MESSAGE
+
+#define SCENARIO DOCTEST_SCENARIO
+#define SCENARIO_CLASS DOCTEST_SCENARIO_CLASS
+#define SCENARIO_TEMPLATE DOCTEST_SCENARIO_TEMPLATE
+#define SCENARIO_TEMPLATE_DEFINE DOCTEST_SCENARIO_TEMPLATE_DEFINE
+#define GIVEN DOCTEST_GIVEN
+#define WHEN DOCTEST_WHEN
+#define AND_WHEN DOCTEST_AND_WHEN
+#define THEN DOCTEST_THEN
+#define AND_THEN DOCTEST_AND_THEN
+
+#define WARN_EQ DOCTEST_WARN_EQ
+#define CHECK_EQ DOCTEST_CHECK_EQ
+#define REQUIRE_EQ DOCTEST_REQUIRE_EQ
+#define WARN_NE DOCTEST_WARN_NE
+#define CHECK_NE DOCTEST_CHECK_NE
+#define REQUIRE_NE DOCTEST_REQUIRE_NE
+#define WARN_GT DOCTEST_WARN_GT
+#define CHECK_GT DOCTEST_CHECK_GT
+#define REQUIRE_GT DOCTEST_REQUIRE_GT
+#define WARN_LT DOCTEST_WARN_LT
+#define CHECK_LT DOCTEST_CHECK_LT
+#define REQUIRE_LT DOCTEST_REQUIRE_LT
+#define WARN_GE DOCTEST_WARN_GE
+#define CHECK_GE DOCTEST_CHECK_GE
+#define REQUIRE_GE DOCTEST_REQUIRE_GE
+#define WARN_LE DOCTEST_WARN_LE
+#define CHECK_LE DOCTEST_CHECK_LE
+#define REQUIRE_LE DOCTEST_REQUIRE_LE
+#define WARN_UNARY DOCTEST_WARN_UNARY
+#define CHECK_UNARY DOCTEST_CHECK_UNARY
+#define REQUIRE_UNARY DOCTEST_REQUIRE_UNARY
+#define WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE
+#define CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE
+#define REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE
+
+// KEPT FOR BACKWARDS COMPATIBILITY
+#define FAST_WARN_EQ DOCTEST_FAST_WARN_EQ
+#define FAST_CHECK_EQ DOCTEST_FAST_CHECK_EQ
+#define FAST_REQUIRE_EQ DOCTEST_FAST_REQUIRE_EQ
+#define FAST_WARN_NE DOCTEST_FAST_WARN_NE
+#define FAST_CHECK_NE DOCTEST_FAST_CHECK_NE
+#define FAST_REQUIRE_NE DOCTEST_FAST_REQUIRE_NE
+#define FAST_WARN_GT DOCTEST_FAST_WARN_GT
+#define FAST_CHECK_GT DOCTEST_FAST_CHECK_GT
+#define FAST_REQUIRE_GT DOCTEST_FAST_REQUIRE_GT
+#define FAST_WARN_LT DOCTEST_FAST_WARN_LT
+#define FAST_CHECK_LT DOCTEST_FAST_CHECK_LT
+#define FAST_REQUIRE_LT DOCTEST_FAST_REQUIRE_LT
+#define FAST_WARN_GE DOCTEST_FAST_WARN_GE
+#define FAST_CHECK_GE DOCTEST_FAST_CHECK_GE
+#define FAST_REQUIRE_GE DOCTEST_FAST_REQUIRE_GE
+#define FAST_WARN_LE DOCTEST_FAST_WARN_LE
+#define FAST_CHECK_LE DOCTEST_FAST_CHECK_LE
+#define FAST_REQUIRE_LE DOCTEST_FAST_REQUIRE_LE
+
+#define FAST_WARN_UNARY DOCTEST_FAST_WARN_UNARY
+#define FAST_CHECK_UNARY DOCTEST_FAST_CHECK_UNARY
+#define FAST_REQUIRE_UNARY DOCTEST_FAST_REQUIRE_UNARY
+#define FAST_WARN_UNARY_FALSE DOCTEST_FAST_WARN_UNARY_FALSE
+#define FAST_CHECK_UNARY_FALSE DOCTEST_FAST_CHECK_UNARY_FALSE
+#define FAST_REQUIRE_UNARY_FALSE DOCTEST_FAST_REQUIRE_UNARY_FALSE
+
+#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE
+
+#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
+
+#if !defined(DOCTEST_CONFIG_DISABLE)
+
+// this is here to clear the 'current test suite' for the current translation unit - at the top
+DOCTEST_TEST_SUITE_END();
+
+// add stringification for primitive/fundamental types
+namespace doctest { namespace detail {
+ DOCTEST_TYPE_TO_STRING_IMPL(bool)
+ DOCTEST_TYPE_TO_STRING_IMPL(float)
+ DOCTEST_TYPE_TO_STRING_IMPL(double)
+ DOCTEST_TYPE_TO_STRING_IMPL(long double)
+ DOCTEST_TYPE_TO_STRING_IMPL(char)
+ DOCTEST_TYPE_TO_STRING_IMPL(signed char)
+ DOCTEST_TYPE_TO_STRING_IMPL(unsigned char)
+#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED)
+ DOCTEST_TYPE_TO_STRING_IMPL(wchar_t)
+#endif // not MSVC or wchar_t support enabled
+ DOCTEST_TYPE_TO_STRING_IMPL(short int)
+ DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int)
+ DOCTEST_TYPE_TO_STRING_IMPL(int)
+ DOCTEST_TYPE_TO_STRING_IMPL(unsigned int)
+ DOCTEST_TYPE_TO_STRING_IMPL(long int)
+ DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int)
+ DOCTEST_TYPE_TO_STRING_IMPL(long long int)
+ DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int)
+}} // namespace doctest::detail
+
+#endif // DOCTEST_CONFIG_DISABLE
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+#endif // DOCTEST_LIBRARY_INCLUDED
+
+#ifndef DOCTEST_SINGLE_HEADER
+#define DOCTEST_SINGLE_HEADER
+#endif // DOCTEST_SINGLE_HEADER
+
+#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER)
+
+#ifndef DOCTEST_SINGLE_HEADER
+#include "doctest_fwd.h"
+#endif // DOCTEST_SINGLE_HEADER
+
+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros")
+
+#ifndef DOCTEST_LIBRARY_IMPLEMENTATION
+#define DOCTEST_LIBRARY_IMPLEMENTATION
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function")
+
+DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion")
+DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute")
+
+DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning
+DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning
+DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration
+DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data
+DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression
+DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated
+DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant
+DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled
+DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified
+DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal
+DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch
+DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding in structs
+DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe
+DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C
+DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff
+DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning)
+// static analysis
+DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept'
+DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable
+DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ...
+DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtor...
+DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum'
+
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
+
+// required includes - will go only in one translation unit!
+#include <ctime>
+#include <cmath>
+#include <climits>
+// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37
+#ifdef __BORLANDC__
+#include <math.h>
+#endif // __BORLANDC__
+#include <new>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+#include <utility>
+#include <fstream>
+#include <sstream>
+#include <iostream>
+#include <algorithm>
+#include <iomanip>
+#include <vector>
+#include <atomic>
+#include <mutex>
+#include <set>
+#include <map>
+#include <exception>
+#include <stdexcept>
+#ifdef DOCTEST_CONFIG_POSIX_SIGNALS
+#include <csignal>
+#endif // DOCTEST_CONFIG_POSIX_SIGNALS
+#include <cfloat>
+#include <cctype>
+#include <cstdint>
+
+#ifdef DOCTEST_PLATFORM_MAC
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/sysctl.h>
+#endif // DOCTEST_PLATFORM_MAC
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+
+// defines for a leaner windows.h
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif // WIN32_LEAN_AND_MEAN
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif // NOMINMAX
+
+// not sure what AfxWin.h is for - here I do what Catch does
+#ifdef __AFXDLL
+#include <AfxWin.h>
+#else
+#if defined(__MINGW32__) || defined(__MINGW64__)
+#include <windows.h>
+#else // MINGW
+#include <Windows.h>
+#endif // MINGW
+#endif
+#include <io.h>
+
+#else // DOCTEST_PLATFORM_WINDOWS
+
+#include <sys/time.h>
+#include <unistd.h>
+
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+// this is a fix for https://github.com/onqtam/doctest/issues/348
+// https://mail.gnome.org/archives/xml/2012-January/msg00000.html
+#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO)
+#define STDOUT_FILENO fileno(stdout)
+#endif // HAVE_UNISTD_H
+
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
+
+// counts the number of elements in a C array
+#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0]))
+
+#ifdef DOCTEST_CONFIG_DISABLE
+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled
+#else // DOCTEST_CONFIG_DISABLE
+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled
+#endif // DOCTEST_CONFIG_DISABLE
+
+#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX
+#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-"
+#endif
+
+#ifndef DOCTEST_THREAD_LOCAL
+#define DOCTEST_THREAD_LOCAL thread_local
+#endif
+
+#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX
+#else
+#define DOCTEST_OPTIONS_PREFIX_DISPLAY ""
+#endif
+
+namespace doctest {
+
+bool is_running_in_test = false;
+
+namespace {
+ using namespace detail;
+ // case insensitive strcmp
+ int stricmp(const char* a, const char* b) {
+ for(;; a++, b++) {
+ const int d = tolower(*a) - tolower(*b);
+ if(d != 0 || !*a)
+ return d;
+ }
+ }
+
+ template <typename T>
+ String fpToString(T value, int precision) {
+ std::ostringstream oss;
+ oss << std::setprecision(precision) << std::fixed << value;
+ std::string d = oss.str();
+ size_t i = d.find_last_not_of('0');
+ if(i != std::string::npos && i != d.size() - 1) {
+ if(d[i] == '.')
+ i++;
+ d = d.substr(0, i + 1);
+ }
+ return d.c_str();
+ }
+
+ struct Endianness
+ {
+ enum Arch
+ {
+ Big,
+ Little
+ };
+
+ static Arch which() {
+ int x = 1;
+ // casting any data pointer to char* is allowed
+ auto ptr = reinterpret_cast<char*>(&x);
+ if(*ptr)
+ return Little;
+ return Big;
+ }
+ };
+} // namespace
+
+namespace detail {
+ void my_memcpy(void* dest, const void* src, unsigned num) { memcpy(dest, src, num); }
+
+ String rawMemoryToString(const void* object, unsigned size) {
+ // Reverse order for little endian architectures
+ int i = 0, end = static_cast<int>(size), inc = 1;
+ if(Endianness::which() == Endianness::Little) {
+ i = end - 1;
+ end = inc = -1;
+ }
+
+ unsigned const char* bytes = static_cast<unsigned const char*>(object);
+ std::ostringstream oss;
+ oss << "0x" << std::setfill('0') << std::hex;
+ for(; i != end; i += inc)
+ oss << std::setw(2) << static_cast<unsigned>(bytes[i]);
+ return oss.str().c_str();
+ }
+
+ DOCTEST_THREAD_LOCAL std::ostringstream g_oss; // NOLINT(cert-err58-cpp)
+
+ std::ostream* getTlsOss() {
+ g_oss.clear(); // there shouldn't be anything worth clearing in the flags
+ g_oss.str(""); // the slow way of resetting a string stream
+ //g_oss.seekp(0); // optimal reset - as seen here: https://stackoverflow.com/a/624291/3162383
+ return &g_oss;
+ }
+
+ String getTlsOssResult() {
+ //g_oss << std::ends; // needed - as shown here: https://stackoverflow.com/a/624291/3162383
+ return g_oss.str().c_str();
+ }
+
+#ifndef DOCTEST_CONFIG_DISABLE
+
+namespace timer_large_integer
+{
+
+#if defined(DOCTEST_PLATFORM_WINDOWS)
+ typedef ULONGLONG type;
+#else // DOCTEST_PLATFORM_WINDOWS
+ using namespace std;
+ typedef uint64_t type;
+#endif // DOCTEST_PLATFORM_WINDOWS
+}
+
+typedef timer_large_integer::type ticks_t;
+
+#ifdef DOCTEST_CONFIG_GETCURRENTTICKS
+ ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); }
+#elif defined(DOCTEST_PLATFORM_WINDOWS)
+ ticks_t getCurrentTicks() {
+ static LARGE_INTEGER hz = {0}, hzo = {0};
+ if(!hz.QuadPart) {
+ QueryPerformanceFrequency(&hz);
+ QueryPerformanceCounter(&hzo);
+ }
+ LARGE_INTEGER t;
+ QueryPerformanceCounter(&t);
+ return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart;
+ }
+#else // DOCTEST_PLATFORM_WINDOWS
+ ticks_t getCurrentTicks() {
+ timeval t;
+ gettimeofday(&t, nullptr);
+ return static_cast<ticks_t>(t.tv_sec) * 1000000 + static_cast<ticks_t>(t.tv_usec);
+ }
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+ struct Timer
+ {
+ void start() { m_ticks = getCurrentTicks(); }
+ unsigned int getElapsedMicroseconds() const {
+ return static_cast<unsigned int>(getCurrentTicks() - m_ticks);
+ }
+ //unsigned int getElapsedMilliseconds() const {
+ // return static_cast<unsigned int>(getElapsedMicroseconds() / 1000);
+ //}
+ double getElapsedSeconds() const { return static_cast<double>(getCurrentTicks() - m_ticks) / 1000000.0; }
+
+ private:
+ ticks_t m_ticks = 0;
+ };
+
+ // this holds both parameters from the command line and runtime data for tests
+ struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats
+ {
+ std::atomic<int> numAssertsCurrentTest_atomic;
+ std::atomic<int> numAssertsFailedCurrentTest_atomic;
+
+ std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters
+
+ std::vector<IReporter*> reporters_currently_used;
+
+ const TestCase* currentTest = nullptr;
+
+ assert_handler ah = nullptr;
+
+ Timer timer;
+
+ std::vector<String> stringifiedContexts; // logging from INFO() due to an exception
+
+ // stuff for subcases
+ std::vector<SubcaseSignature> subcasesStack;
+ std::set<decltype(subcasesStack)> subcasesPassed;
+ int subcasesCurrentMaxLevel;
+ bool should_reenter;
+ std::atomic<bool> shouldLogCurrentException;
+
+ void resetRunData() {
+ numTestCases = 0;
+ numTestCasesPassingFilters = 0;
+ numTestSuitesPassingFilters = 0;
+ numTestCasesFailed = 0;
+ numAsserts = 0;
+ numAssertsFailed = 0;
+ numAssertsCurrentTest = 0;
+ numAssertsFailedCurrentTest = 0;
+ }
+
+ void finalizeTestCaseData() {
+ seconds = timer.getElapsedSeconds();
+
+ // update the non-atomic counters
+ numAsserts += numAssertsCurrentTest_atomic;
+ numAssertsFailed += numAssertsFailedCurrentTest_atomic;
+ numAssertsCurrentTest = numAssertsCurrentTest_atomic;
+ numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic;
+
+ if(numAssertsFailedCurrentTest)
+ failure_flags |= TestCaseFailureReason::AssertFailure;
+
+ if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 &&
+ Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout)
+ failure_flags |= TestCaseFailureReason::Timeout;
+
+ if(currentTest->m_should_fail) {
+ if(failure_flags) {
+ failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid;
+ } else {
+ failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt;
+ }
+ } else if(failure_flags && currentTest->m_may_fail) {
+ failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid;
+ } else if(currentTest->m_expected_failures > 0) {
+ if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) {
+ failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes;
+ } else {
+ failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes;
+ }
+ }
+
+ bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) ||
+ (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) ||
+ (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags);
+
+ // if any subcase has failed - the whole test case has failed
+ if(failure_flags && !ok_to_fail)
+ numTestCasesFailed++;
+ }
+ };
+
+ ContextState* g_cs = nullptr;
+
+ // used to avoid locks for the debug output
+ // TODO: figure out if this is indeed necessary/correct - seems like either there still
+ // could be a race or that there wouldn't be a race even if using the context directly
+ DOCTEST_THREAD_LOCAL bool g_no_colors;
+
+#endif // DOCTEST_CONFIG_DISABLE
+} // namespace detail
+
+void String::setOnHeap() { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; }
+void String::setLast(unsigned in) { buf[last] = char(in); }
+
+void String::copy(const String& other) {
+ using namespace std;
+ if(other.isOnStack()) {
+ memcpy(buf, other.buf, len);
+ } else {
+ setOnHeap();
+ data.size = other.data.size;
+ data.capacity = data.size + 1;
+ data.ptr = new char[data.capacity];
+ memcpy(data.ptr, other.data.ptr, data.size + 1);
+ }
+}
+
+String::String() {
+ buf[0] = '\0';
+ setLast();
+}
+
+String::~String() {
+ if(!isOnStack())
+ delete[] data.ptr;
+}
+
+String::String(const char* in)
+ : String(in, strlen(in)) {}
+
+String::String(const char* in, unsigned in_size) {
+ using namespace std;
+ if(in_size <= last) {
+ memcpy(buf, in, in_size + 1);
+ setLast(last - in_size);
+ } else {
+ setOnHeap();
+ data.size = in_size;
+ data.capacity = data.size + 1;
+ data.ptr = new char[data.capacity];
+ memcpy(data.ptr, in, in_size + 1);
+ }
+}
+
+String::String(const String& other) { copy(other); }
+
+String& String::operator=(const String& other) {
+ if(this != &other) {
+ if(!isOnStack())
+ delete[] data.ptr;
+
+ copy(other);
+ }
+
+ return *this;
+}
+
+String& String::operator+=(const String& other) {
+ const unsigned my_old_size = size();
+ const unsigned other_size = other.size();
+ const unsigned total_size = my_old_size + other_size;
+ using namespace std;
+ if(isOnStack()) {
+ if(total_size < len) {
+ // append to the current stack space
+ memcpy(buf + my_old_size, other.c_str(), other_size + 1);
+ setLast(last - total_size);
+ } else {
+ // alloc new chunk
+ char* temp = new char[total_size + 1];
+ // copy current data to new location before writing in the union
+ memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed
+ // update data in union
+ setOnHeap();
+ data.size = total_size;
+ data.capacity = data.size + 1;
+ data.ptr = temp;
+ // transfer the rest of the data
+ memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);
+ }
+ } else {
+ if(data.capacity > total_size) {
+ // append to the current heap block
+ data.size = total_size;
+ memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);
+ } else {
+ // resize
+ data.capacity *= 2;
+ if(data.capacity <= total_size)
+ data.capacity = total_size + 1;
+ // alloc new chunk
+ char* temp = new char[data.capacity];
+ // copy current data to new location before releasing it
+ memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed
+ // release old chunk
+ delete[] data.ptr;
+ // update the rest of the union members
+ data.size = total_size;
+ data.ptr = temp;
+ // transfer the rest of the data
+ memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);
+ }
+ }
+
+ return *this;
+}
+
+String String::operator+(const String& other) const { return String(*this) += other; }
+
+String::String(String&& other) {
+ using namespace std;
+ memcpy(buf, other.buf, len);
+ other.buf[0] = '\0';
+ other.setLast();
+}
+
+String& String::operator=(String&& other) {
+ using namespace std;
+ if(this != &other) {
+ if(!isOnStack())
+ delete[] data.ptr;
+ memcpy(buf, other.buf, len);
+ other.buf[0] = '\0';
+ other.setLast();
+ }
+ return *this;
+}
+
+char String::operator[](unsigned i) const {
+ return const_cast<String*>(this)->operator[](i); // NOLINT
+}
+
+char& String::operator[](unsigned i) {
+ if(isOnStack())
+ return reinterpret_cast<char*>(buf)[i];
+ return data.ptr[i];
+}
+
+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized")
+unsigned String::size() const {
+ if(isOnStack())
+ return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32
+ return data.size;
+}
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+unsigned String::capacity() const {
+ if(isOnStack())
+ return len;
+ return data.capacity;
+}
+
+int String::compare(const char* other, bool no_case) const {
+ if(no_case)
+ return doctest::stricmp(c_str(), other);
+ return std::strcmp(c_str(), other);
+}
+
+int String::compare(const String& other, bool no_case) const {
+ return compare(other.c_str(), no_case);
+}
+
+// clang-format off
+bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; }
+bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; }
+bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; }
+bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; }
+bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; }
+bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; }
+// clang-format on
+
+std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); }
+
+namespace {
+ void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;)
+} // namespace
+
+namespace Color {
+ std::ostream& operator<<(std::ostream& s, Color::Enum code) {
+ color_to_stream(s, code);
+ return s;
+ }
+} // namespace Color
+
+// clang-format off
+const char* assertString(assertType::Enum at) {
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled
+ switch(at) { //!OCLINT missing default in switch statements
+ case assertType::DT_WARN : return "WARN";
+ case assertType::DT_CHECK : return "CHECK";
+ case assertType::DT_REQUIRE : return "REQUIRE";
+
+ case assertType::DT_WARN_FALSE : return "WARN_FALSE";
+ case assertType::DT_CHECK_FALSE : return "CHECK_FALSE";
+ case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE";
+
+ case assertType::DT_WARN_THROWS : return "WARN_THROWS";
+ case assertType::DT_CHECK_THROWS : return "CHECK_THROWS";
+ case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS";
+
+ case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS";
+ case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS";
+ case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS";
+
+ case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH";
+ case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH";
+ case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH";
+
+ case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS";
+ case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS";
+ case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS";
+
+ case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW";
+ case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW";
+ case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW";
+
+ case assertType::DT_WARN_EQ : return "WARN_EQ";
+ case assertType::DT_CHECK_EQ : return "CHECK_EQ";
+ case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ";
+ case assertType::DT_WARN_NE : return "WARN_NE";
+ case assertType::DT_CHECK_NE : return "CHECK_NE";
+ case assertType::DT_REQUIRE_NE : return "REQUIRE_NE";
+ case assertType::DT_WARN_GT : return "WARN_GT";
+ case assertType::DT_CHECK_GT : return "CHECK_GT";
+ case assertType::DT_REQUIRE_GT : return "REQUIRE_GT";
+ case assertType::DT_WARN_LT : return "WARN_LT";
+ case assertType::DT_CHECK_LT : return "CHECK_LT";
+ case assertType::DT_REQUIRE_LT : return "REQUIRE_LT";
+ case assertType::DT_WARN_GE : return "WARN_GE";
+ case assertType::DT_CHECK_GE : return "CHECK_GE";
+ case assertType::DT_REQUIRE_GE : return "REQUIRE_GE";
+ case assertType::DT_WARN_LE : return "WARN_LE";
+ case assertType::DT_CHECK_LE : return "CHECK_LE";
+ case assertType::DT_REQUIRE_LE : return "REQUIRE_LE";
+
+ case assertType::DT_WARN_UNARY : return "WARN_UNARY";
+ case assertType::DT_CHECK_UNARY : return "CHECK_UNARY";
+ case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY";
+ case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE";
+ case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE";
+ case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE";
+ }
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+ return "";
+}
+// clang-format on
+
+const char* failureString(assertType::Enum at) {
+ if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional
+ return "WARNING";
+ if(at & assertType::is_check) //!OCLINT bitwise operator in conditional
+ return "ERROR";
+ if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
+ return "FATAL ERROR";
+ return "";
+}
+
+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference")
+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference")
+// depending on the current options this will remove the path of filenames
+const char* skipPathFromFilename(const char* file) {
+ if(getContextOptions()->no_path_in_filenames) {
+ auto back = std::strrchr(file, '\\');
+ auto forward = std::strrchr(file, '/');
+ if(back || forward) {
+ if(back > forward)
+ forward = back;
+ return forward + 1;
+ }
+ }
+ return file;
+}
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+bool SubcaseSignature::operator<(const SubcaseSignature& other) const {
+ if(m_line != other.m_line)
+ return m_line < other.m_line;
+ if(std::strcmp(m_file, other.m_file) != 0)
+ return std::strcmp(m_file, other.m_file) < 0;
+ return m_name.compare(other.m_name) < 0;
+}
+
+IContextScope::IContextScope() = default;
+IContextScope::~IContextScope() = default;
+
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+String toString(char* in) { return toString(static_cast<const char*>(in)); }
+String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; }
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+String toString(bool in) { return in ? "true" : "false"; }
+String toString(float in) { return fpToString(in, 5) + "f"; }
+String toString(double in) { return fpToString(in, 10); }
+String toString(double long in) { return fpToString(in, 15); }
+
+#define DOCTEST_TO_STRING_OVERLOAD(type, fmt) \
+ String toString(type in) { \
+ char buf[64]; \
+ std::sprintf(buf, fmt, in); \
+ return buf; \
+ }
+
+DOCTEST_TO_STRING_OVERLOAD(char, "%d")
+DOCTEST_TO_STRING_OVERLOAD(char signed, "%d")
+DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u")
+DOCTEST_TO_STRING_OVERLOAD(int short, "%d")
+DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u")
+DOCTEST_TO_STRING_OVERLOAD(int, "%d")
+DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u")
+DOCTEST_TO_STRING_OVERLOAD(int long, "%ld")
+DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu")
+DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld")
+DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu")
+
+String toString(std::nullptr_t) { return "NULL"; }
+
+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
+// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183
+String toString(const std::string& in) { return in.c_str(); }
+#endif // VS 2019
+
+Approx::Approx(double value)
+ : m_epsilon(static_cast<double>(std::numeric_limits<float>::epsilon()) * 100)
+ , m_scale(1.0)
+ , m_value(value) {}
+
+Approx Approx::operator()(double value) const {
+ Approx approx(value);
+ approx.epsilon(m_epsilon);
+ approx.scale(m_scale);
+ return approx;
+}
+
+Approx& Approx::epsilon(double newEpsilon) {
+ m_epsilon = newEpsilon;
+ return *this;
+}
+Approx& Approx::scale(double newScale) {
+ m_scale = newScale;
+ return *this;
+}
+
+bool operator==(double lhs, const Approx& rhs) {
+ // Thanks to Richard Harris for his help refining this formula
+ return std::fabs(lhs - rhs.m_value) <
+ rhs.m_epsilon * (rhs.m_scale + std::max<double>(std::fabs(lhs), std::fabs(rhs.m_value)));
+}
+bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); }
+bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); }
+bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); }
+bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; }
+bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; }
+bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; }
+bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; }
+bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; }
+bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; }
+bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; }
+bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; }
+
+String toString(const Approx& in) {
+ return String("Approx( ") + doctest::toString(in.m_value) + " )";
+}
+const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); }
+
+} // namespace doctest
+
+#ifdef DOCTEST_CONFIG_DISABLE
+namespace doctest {
+Context::Context(int, const char* const*) {}
+Context::~Context() = default;
+void Context::applyCommandLine(int, const char* const*) {}
+void Context::addFilter(const char*, const char*) {}
+void Context::clearFilters() {}
+void Context::setOption(const char*, int) {}
+void Context::setOption(const char*, const char*) {}
+bool Context::shouldExit() { return false; }
+void Context::setAsDefaultForAssertsOutOfTestCases() {}
+void Context::setAssertHandler(detail::assert_handler) {}
+int Context::run() { return 0; }
+
+IReporter::~IReporter() = default;
+
+int IReporter::get_num_active_contexts() { return 0; }
+const IContextScope* const* IReporter::get_active_contexts() { return nullptr; }
+int IReporter::get_num_stringified_contexts() { return 0; }
+const String* IReporter::get_stringified_contexts() { return nullptr; }
+
+int registerReporter(const char*, int, IReporter*) { return 0; }
+
+} // namespace doctest
+#else // DOCTEST_CONFIG_DISABLE
+
+#if !defined(DOCTEST_CONFIG_COLORS_NONE)
+#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI)
+#ifdef DOCTEST_PLATFORM_WINDOWS
+#define DOCTEST_CONFIG_COLORS_WINDOWS
+#else // linux
+#define DOCTEST_CONFIG_COLORS_ANSI
+#endif // platform
+#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI
+#endif // DOCTEST_CONFIG_COLORS_NONE
+
+namespace doctest_detail_test_suite_ns {
+// holds the current test suite
+doctest::detail::TestSuite& getCurrentTestSuite() {
+ static doctest::detail::TestSuite data;
+ return data;
+}
+} // namespace doctest_detail_test_suite_ns
+
+namespace doctest {
+namespace {
+ // the int (priority) is part of the key for automatic sorting - sadly one can register a
+ // reporter with a duplicate name and a different priority but hopefully that won't happen often :|
+ typedef std::map<std::pair<int, String>, reporterCreatorFunc> reporterMap;
+
+ reporterMap& getReporters() {
+ static reporterMap data;
+ return data;
+ }
+ reporterMap& getListeners() {
+ static reporterMap data;
+ return data;
+ }
+} // namespace
+namespace detail {
+#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \
+ for(auto& curr_rep : g_cs->reporters_currently_used) \
+ curr_rep->function(__VA_ARGS__)
+
+ bool checkIfShouldThrow(assertType::Enum at) {
+ if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
+ return true;
+
+ if((at & assertType::is_check) //!OCLINT bitwise operator in conditional
+ && getContextOptions()->abort_after > 0 &&
+ (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >=
+ getContextOptions()->abort_after)
+ return true;
+
+ return false;
+ }
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ DOCTEST_NORETURN void throwException() {
+ g_cs->shouldLogCurrentException = false;
+ throw TestFailureException();
+ } // NOLINT(cert-err60-cpp)
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS
+ void throwException() {}
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+} // namespace detail
+
+namespace {
+ using namespace detail;
+ // matching of a string against a wildcard mask (case sensitivity configurable) taken from
+ // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing
+ int wildcmp(const char* str, const char* wild, bool caseSensitive) {
+ const char* cp = str;
+ const char* mp = wild;
+
+ while((*str) && (*wild != '*')) {
+ if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) &&
+ (*wild != '?')) {
+ return 0;
+ }
+ wild++;
+ str++;
+ }
+
+ while(*str) {
+ if(*wild == '*') {
+ if(!*++wild) {
+ return 1;
+ }
+ mp = wild;
+ cp = str + 1;
+ } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) ||
+ (*wild == '?')) {
+ wild++;
+ str++;
+ } else {
+ wild = mp; //!OCLINT parameter reassignment
+ str = cp++; //!OCLINT parameter reassignment
+ }
+ }
+
+ while(*wild == '*') {
+ wild++;
+ }
+ return !*wild;
+ }
+
+ //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html
+ //unsigned hashStr(unsigned const char* str) {
+ // unsigned long hash = 5381;
+ // char c;
+ // while((c = *str++))
+ // hash = ((hash << 5) + hash) + c; // hash * 33 + c
+ // return hash;
+ //}
+
+ // checks if the name matches any of the filters (and can be configured what to do when empty)
+ bool matchesAny(const char* name, const std::vector<String>& filters, bool matchEmpty,
+ bool caseSensitive) {
+ if(filters.empty() && matchEmpty)
+ return true;
+ for(auto& curr : filters)
+ if(wildcmp(name, curr.c_str(), caseSensitive))
+ return true;
+ return false;
+ }
+} // namespace
+namespace detail {
+
+ Subcase::Subcase(const String& name, const char* file, int line)
+ : m_signature({name, file, line}) {
+ ContextState* s = g_cs;
+
+ // check subcase filters
+ if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) {
+ if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive))
+ return;
+ if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive))
+ return;
+ }
+
+ // if a Subcase on the same level has already been entered
+ if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) {
+ s->should_reenter = true;
+ return;
+ }
+
+ // push the current signature to the stack so we can check if the
+ // current stack + the current new subcase have been traversed
+ s->subcasesStack.push_back(m_signature);
+ if(s->subcasesPassed.count(s->subcasesStack) != 0) {
+ // pop - revert to previous stack since we've already passed this
+ s->subcasesStack.pop_back();
+ return;
+ }
+
+ s->subcasesCurrentMaxLevel = s->subcasesStack.size();
+ m_entered = true;
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature);
+ }
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+
+ Subcase::~Subcase() {
+ if(m_entered) {
+ // only mark the subcase stack as passed if no subcases have been skipped
+ if(g_cs->should_reenter == false)
+ g_cs->subcasesPassed.insert(g_cs->subcasesStack);
+ g_cs->subcasesStack.pop_back();
+
+#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L
+ if(std::uncaught_exceptions() > 0
+#else
+ if(std::uncaught_exception()
+#endif
+ && g_cs->shouldLogCurrentException) {
+ DOCTEST_ITERATE_THROUGH_REPORTERS(
+ test_case_exception, {"exception thrown in subcase - will translate later "
+ "when the whole test case has been exited (cannot "
+ "translate while there is an active exception)",
+ false});
+ g_cs->shouldLogCurrentException = false;
+ }
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);
+ }
+ }
+
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+ Subcase::operator bool() const { return m_entered; }
+
+ Result::Result(bool passed, const String& decomposition)
+ : m_passed(passed)
+ , m_decomp(decomposition) {}
+
+ ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at)
+ : m_at(at) {}
+
+ TestSuite& TestSuite::operator*(const char* in) {
+ m_test_suite = in;
+ // clear state
+ m_description = nullptr;
+ m_skip = false;
+ m_may_fail = false;
+ m_should_fail = false;
+ m_expected_failures = 0;
+ m_timeout = 0;
+ return *this;
+ }
+
+ TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
+ const char* type, int template_id) {
+ m_file = file;
+ m_line = line;
+ m_name = nullptr; // will be later overridden in operator*
+ m_test_suite = test_suite.m_test_suite;
+ m_description = test_suite.m_description;
+ m_skip = test_suite.m_skip;
+ m_may_fail = test_suite.m_may_fail;
+ m_should_fail = test_suite.m_should_fail;
+ m_expected_failures = test_suite.m_expected_failures;
+ m_timeout = test_suite.m_timeout;
+
+ m_test = test;
+ m_type = type;
+ m_template_id = template_id;
+ }
+
+ TestCase::TestCase(const TestCase& other)
+ : TestCaseData() {
+ *this = other;
+ }
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function
+ DOCTEST_MSVC_SUPPRESS_WARNING(26437) // Do not slice
+ TestCase& TestCase::operator=(const TestCase& other) {
+ static_cast<TestCaseData&>(*this) = static_cast<const TestCaseData&>(other);
+
+ m_test = other.m_test;
+ m_type = other.m_type;
+ m_template_id = other.m_template_id;
+ m_full_name = other.m_full_name;
+
+ if(m_template_id != -1)
+ m_name = m_full_name.c_str();
+ return *this;
+ }
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+ TestCase& TestCase::operator*(const char* in) {
+ m_name = in;
+ // make a new name with an appended type for templated test case
+ if(m_template_id != -1) {
+ m_full_name = String(m_name) + m_type;
+ // redirect the name to point to the newly constructed full name
+ m_name = m_full_name.c_str();
+ }
+ return *this;
+ }
+
+ bool TestCase::operator<(const TestCase& other) const {
+ if(m_line != other.m_line)
+ return m_line < other.m_line;
+ const int file_cmp = m_file.compare(other.m_file);
+ if(file_cmp != 0)
+ return file_cmp < 0;
+ return m_template_id < other.m_template_id;
+ }
+} // namespace detail
+namespace {
+ using namespace detail;
+ // for sorting tests by file/line
+ bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) {
+ // this is needed because MSVC gives different case for drive letters
+ // for __FILE__ when evaluated in a header and a source file
+ const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC));
+ if(res != 0)
+ return res < 0;
+ if(lhs->m_line != rhs->m_line)
+ return lhs->m_line < rhs->m_line;
+ return lhs->m_template_id < rhs->m_template_id;
+ }
+
+ // for sorting tests by suite/file/line
+ bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) {
+ const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite);
+ if(res != 0)
+ return res < 0;
+ return fileOrderComparator(lhs, rhs);
+ }
+
+ // for sorting tests by name/suite/file/line
+ bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) {
+ const int res = std::strcmp(lhs->m_name, rhs->m_name);
+ if(res != 0)
+ return res < 0;
+ return suiteOrderComparator(lhs, rhs);
+ }
+
+ // all the registered tests
+ std::set<TestCase>& getRegisteredTests() {
+ static std::set<TestCase> data;
+ return data;
+ }
+
+#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
+ HANDLE g_stdoutHandle;
+ WORD g_origFgAttrs;
+ WORD g_origBgAttrs;
+ bool g_attrsInitted = false;
+
+ int colors_init() {
+ if(!g_attrsInitted) {
+ g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
+ g_attrsInitted = true;
+ CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+ GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo);
+ g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED |
+ BACKGROUND_BLUE | BACKGROUND_INTENSITY);
+ g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED |
+ FOREGROUND_BLUE | FOREGROUND_INTENSITY);
+ }
+ return 0;
+ }
+
+ int dumy_init_console_colors = colors_init();
+#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+ void color_to_stream(std::ostream& s, Color::Enum code) {
+ ((void)s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS
+ ((void)code); // for DOCTEST_CONFIG_COLORS_NONE
+#ifdef DOCTEST_CONFIG_COLORS_ANSI
+ if(g_no_colors ||
+ (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false))
+ return;
+
+ auto col = "";
+ // clang-format off
+ switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement
+ case Color::Red: col = "[0;31m"; break;
+ case Color::Green: col = "[0;32m"; break;
+ case Color::Blue: col = "[0;34m"; break;
+ case Color::Cyan: col = "[0;36m"; break;
+ case Color::Yellow: col = "[0;33m"; break;
+ case Color::Grey: col = "[1;30m"; break;
+ case Color::LightGrey: col = "[0;37m"; break;
+ case Color::BrightRed: col = "[1;31m"; break;
+ case Color::BrightGreen: col = "[1;32m"; break;
+ case Color::BrightWhite: col = "[1;37m"; break;
+ case Color::Bright: // invalid
+ case Color::None:
+ case Color::White:
+ default: col = "[0m";
+ }
+ // clang-format on
+ s << "\033" << col;
+#endif // DOCTEST_CONFIG_COLORS_ANSI
+
+#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
+ if(g_no_colors ||
+ (isatty(fileno(stdout)) == false && getContextOptions()->force_colors == false))
+ return;
+
+#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs)
+
+ // clang-format off
+ switch (code) {
+ case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
+ case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break;
+ case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break;
+ case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break;
+ case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break;
+ case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break;
+ case Color::Grey: DOCTEST_SET_ATTR(0); break;
+ case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break;
+ case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break;
+ case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break;
+ case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
+ case Color::None:
+ case Color::Bright: // invalid
+ default: DOCTEST_SET_ATTR(g_origFgAttrs);
+ }
+ // clang-format on
+#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+ }
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+ std::vector<const IExceptionTranslator*>& getExceptionTranslators() {
+ static std::vector<const IExceptionTranslator*> data;
+ return data;
+ }
+
+ String translateActiveException() {
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ String res;
+ auto& translators = getExceptionTranslators();
+ for(auto& curr : translators)
+ if(curr->translate(res))
+ return res;
+ // clang-format off
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value")
+ try {
+ throw;
+ } catch(std::exception& ex) {
+ return ex.what();
+ } catch(std::string& msg) {
+ return msg.c_str();
+ } catch(const char* msg) {
+ return msg;
+ } catch(...) {
+ return "unknown exception";
+ }
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
+// clang-format on
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS
+ return "";
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+ }
+} // namespace
+
+namespace detail {
+ // used by the macros for registering tests
+ int regTest(const TestCase& tc) {
+ getRegisteredTests().insert(tc);
+ return 0;
+ }
+
+ // sets the current test suite
+ int setTestSuite(const TestSuite& ts) {
+ doctest_detail_test_suite_ns::getCurrentTestSuite() = ts;
+ return 0;
+ }
+
+#ifdef DOCTEST_IS_DEBUGGER_ACTIVE
+ bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); }
+#else // DOCTEST_IS_DEBUGGER_ACTIVE
+#ifdef DOCTEST_PLATFORM_MAC
+ // The following function is taken directly from the following technical note:
+ // https://developer.apple.com/library/archive/qa/qa1361/_index.html
+ // Returns true if the current process is being debugged (either
+ // running under the debugger or has a debugger attached post facto).
+ bool isDebuggerActive() {
+ int mib[4];
+ kinfo_proc info;
+ size_t size;
+ // Initialize the flags so that, if sysctl fails for some bizarre
+ // reason, we get a predictable result.
+ info.kp_proc.p_flag = 0;
+ // Initialize mib, which tells sysctl the info we want, in this case
+ // we're looking for information about a specific process ID.
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = getpid();
+ // Call sysctl.
+ size = sizeof(info);
+ if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) {
+ std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n";
+ return false;
+ }
+ // We're being debugged if the P_TRACED flag is set.
+ return ((info.kp_proc.p_flag & P_TRACED) != 0);
+ }
+#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__)
+ bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; }
+#else
+ bool isDebuggerActive() { return false; }
+#endif // Platform
+#endif // DOCTEST_IS_DEBUGGER_ACTIVE
+
+ void registerExceptionTranslatorImpl(const IExceptionTranslator* et) {
+ if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) ==
+ getExceptionTranslators().end())
+ getExceptionTranslators().push_back(et);
+ }
+
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+ void toStream(std::ostream* s, char* in) { *s << in; }
+ void toStream(std::ostream* s, const char* in) { *s << in; }
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+ void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; }
+ void toStream(std::ostream* s, float in) { *s << in; }
+ void toStream(std::ostream* s, double in) { *s << in; }
+ void toStream(std::ostream* s, double long in) { *s << in; }
+
+ void toStream(std::ostream* s, char in) { *s << in; }
+ void toStream(std::ostream* s, char signed in) { *s << in; }
+ void toStream(std::ostream* s, char unsigned in) { *s << in; }
+ void toStream(std::ostream* s, int short in) { *s << in; }
+ void toStream(std::ostream* s, int short unsigned in) { *s << in; }
+ void toStream(std::ostream* s, int in) { *s << in; }
+ void toStream(std::ostream* s, int unsigned in) { *s << in; }
+ void toStream(std::ostream* s, int long in) { *s << in; }
+ void toStream(std::ostream* s, int long unsigned in) { *s << in; }
+ void toStream(std::ostream* s, int long long in) { *s << in; }
+ void toStream(std::ostream* s, int long long unsigned in) { *s << in; }
+
+ DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO()
+
+ ContextScopeBase::ContextScopeBase() {
+ g_infoContexts.push_back(this);
+ }
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+
+ // destroy cannot be inlined into the destructor because that would mean calling stringify after
+ // ContextScope has been destroyed (base class destructors run after derived class destructors).
+ // Instead, ContextScope calls this method directly from its destructor.
+ void ContextScopeBase::destroy() {
+#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L
+ if(std::uncaught_exceptions() > 0) {
+#else
+ if(std::uncaught_exception()) {
+#endif
+ std::ostringstream s;
+ this->stringify(&s);
+ g_cs->stringifiedContexts.push_back(s.str().c_str());
+ }
+ g_infoContexts.pop_back();
+ }
+
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+} // namespace detail
+namespace {
+ using namespace detail;
+
+#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
+ struct FatalConditionHandler
+ {
+ void reset() {}
+ };
+#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
+
+ void reportFatal(const std::string&);
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+
+ struct SignalDefs
+ {
+ DWORD id;
+ const char* name;
+ };
+ // There is no 1-1 mapping between signals and windows exceptions.
+ // Windows can easily distinguish between SO and SigSegV,
+ // but SigInt, SigTerm, etc are handled differently.
+ SignalDefs signalDefs[] = {
+ {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"},
+ {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"},
+ {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"},
+ {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"},
+ };
+
+ struct FatalConditionHandler
+ {
+ static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) {
+ for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+ if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) {
+ reportFatal(signalDefs[i].name);
+ break;
+ }
+ }
+ // If its not an exception we care about, pass it along.
+ // This stops us from eating debugger breaks etc.
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ FatalConditionHandler() {
+ isSet = true;
+ // 32k seems enough for doctest to handle stack overflow,
+ // but the value was found experimentally, so there is no strong guarantee
+ guaranteeSize = 32 * 1024;
+ // Register an unhandled exception filter
+ previousTop = SetUnhandledExceptionFilter(handleException);
+ // Pass in guarantee size to be filled
+ SetThreadStackGuarantee(&guaranteeSize);
+ }
+
+ static void reset() {
+ if(isSet) {
+ // Unregister handler and restore the old guarantee
+ SetUnhandledExceptionFilter(previousTop);
+ SetThreadStackGuarantee(&guaranteeSize);
+ previousTop = nullptr;
+ isSet = false;
+ }
+ }
+
+ ~FatalConditionHandler() { reset(); }
+
+ private:
+ static bool isSet;
+ static ULONG guaranteeSize;
+ static LPTOP_LEVEL_EXCEPTION_FILTER previousTop;
+ };
+
+ bool FatalConditionHandler::isSet = false;
+ ULONG FatalConditionHandler::guaranteeSize = 0;
+ LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr;
+
+#else // DOCTEST_PLATFORM_WINDOWS
+
+ struct SignalDefs
+ {
+ int id;
+ const char* name;
+ };
+ SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"},
+ {SIGILL, "SIGILL - Illegal instruction signal"},
+ {SIGFPE, "SIGFPE - Floating point error signal"},
+ {SIGSEGV, "SIGSEGV - Segmentation violation signal"},
+ {SIGTERM, "SIGTERM - Termination request signal"},
+ {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}};
+
+ struct FatalConditionHandler
+ {
+ static bool isSet;
+ static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)];
+ static stack_t oldSigStack;
+ static char altStackMem[4 * SIGSTKSZ];
+
+ static void handleSignal(int sig) {
+ const char* name = "<unknown signal>";
+ for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+ SignalDefs& def = signalDefs[i];
+ if(sig == def.id) {
+ name = def.name;
+ break;
+ }
+ }
+ reset();
+ reportFatal(name);
+ raise(sig);
+ }
+
+ FatalConditionHandler() {
+ isSet = true;
+ stack_t sigStack;
+ sigStack.ss_sp = altStackMem;
+ sigStack.ss_size = sizeof(altStackMem);
+ sigStack.ss_flags = 0;
+ sigaltstack(&sigStack, &oldSigStack);
+ struct sigaction sa = {};
+ sa.sa_handler = handleSignal; // NOLINT
+ sa.sa_flags = SA_ONSTACK;
+ for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+ sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);
+ }
+ }
+
+ ~FatalConditionHandler() { reset(); }
+ static void reset() {
+ if(isSet) {
+ // Set signals back to previous values -- hopefully nobody overwrote them in the meantime
+ for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+ sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);
+ }
+ // Return the old stack
+ sigaltstack(&oldSigStack, nullptr);
+ isSet = false;
+ }
+ }
+ };
+
+ bool FatalConditionHandler::isSet = false;
+ struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {};
+ stack_t FatalConditionHandler::oldSigStack = {};
+ char FatalConditionHandler::altStackMem[] = {};
+
+#endif // DOCTEST_PLATFORM_WINDOWS
+#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
+
+} // namespace
+
+namespace {
+ using namespace detail;
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text)
+#else
+ // TODO: integration with XCode and other IDEs
+#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros)
+#endif // Platform
+
+ void addAssert(assertType::Enum at) {
+ if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional
+ g_cs->numAssertsCurrentTest_atomic++;
+ }
+
+ void addFailedAssert(assertType::Enum at) {
+ if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional
+ g_cs->numAssertsFailedCurrentTest_atomic++;
+ }
+
+#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH)
+ void reportFatal(const std::string& message) {
+ g_cs->failure_flags |= TestCaseFailureReason::Crash;
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true});
+
+ while(g_cs->subcasesStack.size()) {
+ g_cs->subcasesStack.pop_back();
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);
+ }
+
+ g_cs->finalizeTestCaseData();
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs);
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs);
+ }
+#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
+} // namespace
+namespace detail {
+
+ ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type, const char* exception_string) {
+ m_test_case = g_cs->currentTest;
+ m_at = at;
+ m_file = file;
+ m_line = line;
+ m_expr = expr;
+ m_failed = true;
+ m_threw = false;
+ m_threw_as = false;
+ m_exception_type = exception_type;
+ m_exception_string = exception_string;
+#if DOCTEST_MSVC
+ if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC
+ ++m_expr;
+#endif // MSVC
+ }
+
+ void ResultBuilder::setResult(const Result& res) {
+ m_decomp = res.m_decomp;
+ m_failed = !res.m_passed;
+ }
+
+ void ResultBuilder::translateException() {
+ m_threw = true;
+ m_exception = translateActiveException();
+ }
+
+ bool ResultBuilder::log() {
+ if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional
+ m_failed = !m_threw;
+ } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT
+ m_failed = !m_threw_as || (m_exception != m_exception_string);
+ } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional
+ m_failed = !m_threw_as;
+ } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional
+ m_failed = m_exception != m_exception_string;
+ } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional
+ m_failed = m_threw;
+ }
+
+ if(m_exception.size())
+ m_exception = String("\"") + m_exception + "\"";
+
+ if(is_running_in_test) {
+ addAssert(m_at);
+ DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this);
+
+ if(m_failed)
+ addFailedAssert(m_at);
+ } else if(m_failed) {
+ failed_out_of_a_testing_context(*this);
+ }
+
+ return m_failed && isDebuggerActive() &&
+ !getContextOptions()->no_breaks; // break into debugger
+ }
+
+ void ResultBuilder::react() const {
+ if(m_failed && checkIfShouldThrow(m_at))
+ throwException();
+ }
+
+ void failed_out_of_a_testing_context(const AssertData& ad) {
+ if(g_cs->ah)
+ g_cs->ah(ad);
+ else
+ std::abort();
+ }
+
+ void decomp_assert(assertType::Enum at, const char* file, int line, const char* expr,
+ Result result) {
+ bool failed = !result.m_passed;
+
+ // ###################################################################################
+ // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
+ // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
+ // ###################################################################################
+ DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp);
+ DOCTEST_ASSERT_IN_TESTS(result.m_decomp);
+ }
+
+ MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) {
+ m_stream = getTlsOss();
+ m_file = file;
+ m_line = line;
+ m_severity = severity;
+ }
+
+ IExceptionTranslator::IExceptionTranslator() = default;
+ IExceptionTranslator::~IExceptionTranslator() = default;
+
+ bool MessageBuilder::log() {
+ m_string = getTlsOssResult();
+ DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this);
+
+ const bool isWarn = m_severity & assertType::is_warn;
+
+ // warn is just a message in this context so we don't treat it as an assert
+ if(!isWarn) {
+ addAssert(m_severity);
+ addFailedAssert(m_severity);
+ }
+
+ return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn; // break
+ }
+
+ void MessageBuilder::react() {
+ if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional
+ throwException();
+ }
+
+ MessageBuilder::~MessageBuilder() = default;
+} // namespace detail
+namespace {
+ using namespace detail;
+
+ template <typename Ex>
+ DOCTEST_NORETURN void throw_exception(Ex const& e) {
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ throw e;
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS
+ std::cerr << "doctest will terminate because it needed to throw an exception.\n"
+ << "The message was: " << e.what() << '\n';
+ std::terminate();
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+ }
+
+#ifndef DOCTEST_INTERNAL_ERROR
+#define DOCTEST_INTERNAL_ERROR(msg) \
+ throw_exception(std::logic_error( \
+ __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg))
+#endif // DOCTEST_INTERNAL_ERROR
+
+ // clang-format off
+
+// =================================================================================================
+// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp
+// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched.
+// =================================================================================================
+
+ class XmlEncode {
+ public:
+ enum ForWhat { ForTextNodes, ForAttributes };
+
+ XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes );
+
+ void encodeTo( std::ostream& os ) const;
+
+ friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode );
+
+ private:
+ std::string m_str;
+ ForWhat m_forWhat;
+ };
+
+ class XmlWriter {
+ public:
+
+ class ScopedElement {
+ public:
+ ScopedElement( XmlWriter* writer );
+
+ ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT;
+ ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT;
+
+ ~ScopedElement();
+
+ ScopedElement& writeText( std::string const& text, bool indent = true );
+
+ template<typename T>
+ ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
+ m_writer->writeAttribute( name, attribute );
+ return *this;
+ }
+
+ private:
+ mutable XmlWriter* m_writer = nullptr;
+ };
+
+ XmlWriter( std::ostream& os = std::cout );
+ ~XmlWriter();
+
+ XmlWriter( XmlWriter const& ) = delete;
+ XmlWriter& operator=( XmlWriter const& ) = delete;
+
+ XmlWriter& startElement( std::string const& name );
+
+ ScopedElement scopedElement( std::string const& name );
+
+ XmlWriter& endElement();
+
+ XmlWriter& writeAttribute( std::string const& name, std::string const& attribute );
+
+ XmlWriter& writeAttribute( std::string const& name, const char* attribute );
+
+ XmlWriter& writeAttribute( std::string const& name, bool attribute );
+
+ template<typename T>
+ XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
+ std::stringstream rss;
+ rss << attribute;
+ return writeAttribute( name, rss.str() );
+ }
+
+ XmlWriter& writeText( std::string const& text, bool indent = true );
+
+ //XmlWriter& writeComment( std::string const& text );
+
+ //void writeStylesheetRef( std::string const& url );
+
+ //XmlWriter& writeBlankLine();
+
+ void ensureTagClosed();
+
+ private:
+
+ void writeDeclaration();
+
+ void newlineIfNecessary();
+
+ bool m_tagIsOpen = false;
+ bool m_needsNewline = false;
+ std::vector<std::string> m_tags;
+ std::string m_indent;
+ std::ostream& m_os;
+ };
+
+// =================================================================================================
+// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp
+// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched.
+// =================================================================================================
+
+using uchar = unsigned char;
+
+namespace {
+
+ size_t trailingBytes(unsigned char c) {
+ if ((c & 0xE0) == 0xC0) {
+ return 2;
+ }
+ if ((c & 0xF0) == 0xE0) {
+ return 3;
+ }
+ if ((c & 0xF8) == 0xF0) {
+ return 4;
+ }
+ DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+ }
+
+ uint32_t headerValue(unsigned char c) {
+ if ((c & 0xE0) == 0xC0) {
+ return c & 0x1F;
+ }
+ if ((c & 0xF0) == 0xE0) {
+ return c & 0x0F;
+ }
+ if ((c & 0xF8) == 0xF0) {
+ return c & 0x07;
+ }
+ DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+ }
+
+ void hexEscapeChar(std::ostream& os, unsigned char c) {
+ std::ios_base::fmtflags f(os.flags());
+ os << "\\x"
+ << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
+ << static_cast<int>(c);
+ os.flags(f);
+ }
+
+} // anonymous namespace
+
+ XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )
+ : m_str( str ),
+ m_forWhat( forWhat )
+ {}
+
+ void XmlEncode::encodeTo( std::ostream& os ) const {
+ // Apostrophe escaping not necessary if we always use " to write attributes
+ // (see: https://www.w3.org/TR/xml/#syntax)
+
+ for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {
+ uchar c = m_str[idx];
+ switch (c) {
+ case '<': os << "&lt;"; break;
+ case '&': os << "&amp;"; break;
+
+ case '>':
+ // See: https://www.w3.org/TR/xml/#syntax
+ if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')
+ os << "&gt;";
+ else
+ os << c;
+ break;
+
+ case '\"':
+ if (m_forWhat == ForAttributes)
+ os << "&quot;";
+ else
+ os << c;
+ break;
+
+ default:
+ // Check for control characters and invalid utf-8
+
+ // Escape control characters in standard ascii
+ // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
+ if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {
+ hexEscapeChar(os, c);
+ break;
+ }
+
+ // Plain ASCII: Write it to stream
+ if (c < 0x7F) {
+ os << c;
+ break;
+ }
+
+ // UTF-8 territory
+ // Check if the encoding is valid and if it is not, hex escape bytes.
+ // Important: We do not check the exact decoded values for validity, only the encoding format
+ // First check that this bytes is a valid lead byte:
+ // This means that it is not encoded as 1111 1XXX
+ // Or as 10XX XXXX
+ if (c < 0xC0 ||
+ c >= 0xF8) {
+ hexEscapeChar(os, c);
+ break;
+ }
+
+ auto encBytes = trailingBytes(c);
+ // Are there enough bytes left to avoid accessing out-of-bounds memory?
+ if (idx + encBytes - 1 >= m_str.size()) {
+ hexEscapeChar(os, c);
+ break;
+ }
+ // The header is valid, check data
+ // The next encBytes bytes must together be a valid utf-8
+ // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)
+ bool valid = true;
+ uint32_t value = headerValue(c);
+ for (std::size_t n = 1; n < encBytes; ++n) {
+ uchar nc = m_str[idx + n];
+ valid &= ((nc & 0xC0) == 0x80);
+ value = (value << 6) | (nc & 0x3F);
+ }
+
+ if (
+ // Wrong bit pattern of following bytes
+ (!valid) ||
+ // Overlong encodings
+ (value < 0x80) ||
+ ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant
+ (0x800 < value && value < 0x10000 && encBytes > 3) ||
+ // Encoded value out of range
+ (value >= 0x110000)
+ ) {
+ hexEscapeChar(os, c);
+ break;
+ }
+
+ // If we got here, this is in fact a valid(ish) utf-8 sequence
+ for (std::size_t n = 0; n < encBytes; ++n) {
+ os << m_str[idx + n];
+ }
+ idx += encBytes - 1;
+ break;
+ }
+ }
+ }
+
+ std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
+ xmlEncode.encodeTo( os );
+ return os;
+ }
+
+ XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer )
+ : m_writer( writer )
+ {}
+
+ XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT
+ : m_writer( other.m_writer ){
+ other.m_writer = nullptr;
+ }
+ XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT {
+ if ( m_writer ) {
+ m_writer->endElement();
+ }
+ m_writer = other.m_writer;
+ other.m_writer = nullptr;
+ return *this;
+ }
+
+
+ XmlWriter::ScopedElement::~ScopedElement() {
+ if( m_writer )
+ m_writer->endElement();
+ }
+
+ XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) {
+ m_writer->writeText( text, indent );
+ return *this;
+ }
+
+ XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )
+ {
+ writeDeclaration();
+ }
+
+ XmlWriter::~XmlWriter() {
+ while( !m_tags.empty() )
+ endElement();
+ }
+
+ XmlWriter& XmlWriter::startElement( std::string const& name ) {
+ ensureTagClosed();
+ newlineIfNecessary();
+ m_os << m_indent << '<' << name;
+ m_tags.push_back( name );
+ m_indent += " ";
+ m_tagIsOpen = true;
+ return *this;
+ }
+
+ XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) {
+ ScopedElement scoped( this );
+ startElement( name );
+ return scoped;
+ }
+
+ XmlWriter& XmlWriter::endElement() {
+ newlineIfNecessary();
+ m_indent = m_indent.substr( 0, m_indent.size()-2 );
+ if( m_tagIsOpen ) {
+ m_os << "/>";
+ m_tagIsOpen = false;
+ }
+ else {
+ m_os << m_indent << "</" << m_tags.back() << ">";
+ }
+ m_os << std::endl;
+ m_tags.pop_back();
+ return *this;
+ }
+
+ XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) {
+ if( !name.empty() && !attribute.empty() )
+ m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
+ return *this;
+ }
+
+ XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) {
+ if( !name.empty() && attribute && attribute[0] != '\0' )
+ m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
+ return *this;
+ }
+
+ XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) {
+ m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"';
+ return *this;
+ }
+
+ XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) {
+ if( !text.empty() ){
+ bool tagWasOpen = m_tagIsOpen;
+ ensureTagClosed();
+ if( tagWasOpen && indent )
+ m_os << m_indent;
+ m_os << XmlEncode( text );
+ m_needsNewline = true;
+ }
+ return *this;
+ }
+
+ //XmlWriter& XmlWriter::writeComment( std::string const& text ) {
+ // ensureTagClosed();
+ // m_os << m_indent << "<!--" << text << "-->";
+ // m_needsNewline = true;
+ // return *this;
+ //}
+
+ //void XmlWriter::writeStylesheetRef( std::string const& url ) {
+ // m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n";
+ //}
+
+ //XmlWriter& XmlWriter::writeBlankLine() {
+ // ensureTagClosed();
+ // m_os << '\n';
+ // return *this;
+ //}
+
+ void XmlWriter::ensureTagClosed() {
+ if( m_tagIsOpen ) {
+ m_os << ">" << std::endl;
+ m_tagIsOpen = false;
+ }
+ }
+
+ void XmlWriter::writeDeclaration() {
+ m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ }
+
+ void XmlWriter::newlineIfNecessary() {
+ if( m_needsNewline ) {
+ m_os << std::endl;
+ m_needsNewline = false;
+ }
+ }
+
+// =================================================================================================
+// End of copy-pasted code from Catch
+// =================================================================================================
+
+ // clang-format on
+
+ struct XmlReporter : public IReporter
+ {
+ XmlWriter xml;
+ std::mutex mutex;
+
+ // caching pointers/references to objects of these types - safe to do
+ const ContextOptions& opt;
+ const TestCaseData* tc = nullptr;
+
+ XmlReporter(const ContextOptions& co)
+ : xml(*co.cout)
+ , opt(co) {}
+
+ void log_contexts() {
+ int num_contexts = get_num_active_contexts();
+ if(num_contexts) {
+ auto contexts = get_active_contexts();
+ std::stringstream ss;
+ for(int i = 0; i < num_contexts; ++i) {
+ contexts[i]->stringify(&ss);
+ xml.scopedElement("Info").writeText(ss.str());
+ ss.str("");
+ }
+ }
+ }
+
+ unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; }
+
+ void test_case_start_impl(const TestCaseData& in) {
+ bool open_ts_tag = false;
+ if(tc != nullptr) { // we have already opened a test suite
+ if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) {
+ xml.endElement();
+ open_ts_tag = true;
+ }
+ }
+ else {
+ open_ts_tag = true; // first test case ==> first test suite
+ }
+
+ if(open_ts_tag) {
+ xml.startElement("TestSuite");
+ xml.writeAttribute("name", in.m_test_suite);
+ }
+
+ tc = &in;
+ xml.startElement("TestCase")
+ .writeAttribute("name", in.m_name)
+ .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str()))
+ .writeAttribute("line", line(in.m_line))
+ .writeAttribute("description", in.m_description);
+
+ if(Approx(in.m_timeout) != 0)
+ xml.writeAttribute("timeout", in.m_timeout);
+ if(in.m_may_fail)
+ xml.writeAttribute("may_fail", true);
+ if(in.m_should_fail)
+ xml.writeAttribute("should_fail", true);
+ }
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+ // =========================================================================================
+
+ void report_query(const QueryData& in) override {
+ test_run_start();
+ if(opt.list_reporters) {
+ for(auto& curr : getListeners())
+ xml.scopedElement("Listener")
+ .writeAttribute("priority", curr.first.first)
+ .writeAttribute("name", curr.first.second);
+ for(auto& curr : getReporters())
+ xml.scopedElement("Reporter")
+ .writeAttribute("priority", curr.first.first)
+ .writeAttribute("name", curr.first.second);
+ } else if(opt.count || opt.list_test_cases) {
+ for(unsigned i = 0; i < in.num_data; ++i) {
+ xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name)
+ .writeAttribute("testsuite", in.data[i]->m_test_suite)
+ .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str()))
+ .writeAttribute("line", line(in.data[i]->m_line));
+ }
+ xml.scopedElement("OverallResultsTestCases")
+ .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters);
+ } else if(opt.list_test_suites) {
+ for(unsigned i = 0; i < in.num_data; ++i)
+ xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite);
+ xml.scopedElement("OverallResultsTestCases")
+ .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters);
+ xml.scopedElement("OverallResultsTestSuites")
+ .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters);
+ }
+ xml.endElement();
+ }
+
+ void test_run_start() override {
+ // remove .exe extension - mainly to have the same output on UNIX and Windows
+ std::string binary_name = skipPathFromFilename(opt.binary_name.c_str());
+#ifdef DOCTEST_PLATFORM_WINDOWS
+ if(binary_name.rfind(".exe") != std::string::npos)
+ binary_name = binary_name.substr(0, binary_name.length() - 4);
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+ xml.startElement("doctest").writeAttribute("binary", binary_name);
+ if(opt.no_version == false)
+ xml.writeAttribute("version", DOCTEST_VERSION_STR);
+
+ // only the consequential ones (TODO: filters)
+ xml.scopedElement("Options")
+ .writeAttribute("order_by", opt.order_by.c_str())
+ .writeAttribute("rand_seed", opt.rand_seed)
+ .writeAttribute("first", opt.first)
+ .writeAttribute("last", opt.last)
+ .writeAttribute("abort_after", opt.abort_after)
+ .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels)
+ .writeAttribute("case_sensitive", opt.case_sensitive)
+ .writeAttribute("no_throw", opt.no_throw)
+ .writeAttribute("no_skip", opt.no_skip);
+ }
+
+ void test_run_end(const TestRunStats& p) override {
+ if(tc) // the TestSuite tag - only if there has been at least 1 test case
+ xml.endElement();
+
+ xml.scopedElement("OverallResultsAsserts")
+ .writeAttribute("successes", p.numAsserts - p.numAssertsFailed)
+ .writeAttribute("failures", p.numAssertsFailed);
+
+ xml.startElement("OverallResultsTestCases")
+ .writeAttribute("successes",
+ p.numTestCasesPassingFilters - p.numTestCasesFailed)
+ .writeAttribute("failures", p.numTestCasesFailed);
+ if(opt.no_skipped_summary == false)
+ xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters);
+ xml.endElement();
+
+ xml.endElement();
+ }
+
+ void test_case_start(const TestCaseData& in) override {
+ test_case_start_impl(in);
+ xml.ensureTagClosed();
+ }
+
+ void test_case_reenter(const TestCaseData&) override {}
+
+ void test_case_end(const CurrentTestCaseStats& st) override {
+ xml.startElement("OverallResultsAsserts")
+ .writeAttribute("successes",
+ st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest)
+ .writeAttribute("failures", st.numAssertsFailedCurrentTest);
+ if(opt.duration)
+ xml.writeAttribute("duration", st.seconds);
+ if(tc->m_expected_failures)
+ xml.writeAttribute("expected_failures", tc->m_expected_failures);
+ xml.endElement();
+
+ xml.endElement();
+ }
+
+ void test_case_exception(const TestCaseException& e) override {
+ std::lock_guard<std::mutex> lock(mutex);
+
+ xml.scopedElement("Exception")
+ .writeAttribute("crash", e.is_crash)
+ .writeText(e.error_string.c_str());
+ }
+
+ void subcase_start(const SubcaseSignature& in) override {
+ std::lock_guard<std::mutex> lock(mutex);
+
+ xml.startElement("SubCase")
+ .writeAttribute("name", in.m_name)
+ .writeAttribute("filename", skipPathFromFilename(in.m_file))
+ .writeAttribute("line", line(in.m_line));
+ xml.ensureTagClosed();
+ }
+
+ void subcase_end() override { xml.endElement(); }
+
+ void log_assert(const AssertData& rb) override {
+ if(!rb.m_failed && !opt.success)
+ return;
+
+ std::lock_guard<std::mutex> lock(mutex);
+
+ xml.startElement("Expression")
+ .writeAttribute("success", !rb.m_failed)
+ .writeAttribute("type", assertString(rb.m_at))
+ .writeAttribute("filename", skipPathFromFilename(rb.m_file))
+ .writeAttribute("line", line(rb.m_line));
+
+ xml.scopedElement("Original").writeText(rb.m_expr);
+
+ if(rb.m_threw)
+ xml.scopedElement("Exception").writeText(rb.m_exception.c_str());
+
+ if(rb.m_at & assertType::is_throws_as)
+ xml.scopedElement("ExpectedException").writeText(rb.m_exception_type);
+ if(rb.m_at & assertType::is_throws_with)
+ xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string);
+ if((rb.m_at & assertType::is_normal) && !rb.m_threw)
+ xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str());
+
+ log_contexts();
+
+ xml.endElement();
+ }
+
+ void log_message(const MessageData& mb) override {
+ std::lock_guard<std::mutex> lock(mutex);
+
+ xml.startElement("Message")
+ .writeAttribute("type", failureString(mb.m_severity))
+ .writeAttribute("filename", skipPathFromFilename(mb.m_file))
+ .writeAttribute("line", line(mb.m_line));
+
+ xml.scopedElement("Text").writeText(mb.m_string.c_str());
+
+ log_contexts();
+
+ xml.endElement();
+ }
+
+ void test_case_skipped(const TestCaseData& in) override {
+ if(opt.no_skipped_summary == false) {
+ test_case_start_impl(in);
+ xml.writeAttribute("skipped", "true");
+ xml.endElement();
+ }
+ }
+ };
+
+ DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter);
+
+ void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) {
+ if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) ==
+ 0) //!OCLINT bitwise operator in conditional
+ s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) "
+ << Color::None;
+
+ if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional
+ s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n";
+ } else if((rb.m_at & assertType::is_throws_as) &&
+ (rb.m_at & assertType::is_throws_with)) { //!OCLINT
+ s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""
+ << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None;
+ if(rb.m_threw) {
+ if(!rb.m_failed) {
+ s << "threw as expected!\n";
+ } else {
+ s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n";
+ }
+ } else {
+ s << "did NOT throw at all!\n";
+ }
+ } else if(rb.m_at &
+ assertType::is_throws_as) { //!OCLINT bitwise operator in conditional
+ s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", "
+ << rb.m_exception_type << " ) " << Color::None
+ << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" :
+ "threw a DIFFERENT exception: ") :
+ "did NOT throw at all!")
+ << Color::Cyan << rb.m_exception << "\n";
+ } else if(rb.m_at &
+ assertType::is_throws_with) { //!OCLINT bitwise operator in conditional
+ s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""
+ << rb.m_exception_string << "\" ) " << Color::None
+ << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" :
+ "threw a DIFFERENT exception: ") :
+ "did NOT throw at all!")
+ << Color::Cyan << rb.m_exception << "\n";
+ } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional
+ s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan
+ << rb.m_exception << "\n";
+ } else {
+ s << (rb.m_threw ? "THREW exception: " :
+ (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n"));
+ if(rb.m_threw)
+ s << rb.m_exception << "\n";
+ else
+ s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n";
+ }
+ }
+
+ // TODO:
+ // - log_contexts()
+ // - log_message()
+ // - respond to queries
+ // - honor remaining options
+ // - more attributes in tags
+ struct JUnitReporter : public IReporter
+ {
+ XmlWriter xml;
+ std::mutex mutex;
+ Timer timer;
+ std::vector<String> deepestSubcaseStackNames;
+
+ struct JUnitTestCaseData
+ {
+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") // gmtime
+ static std::string getCurrentTimestamp() {
+ // Beware, this is not reentrant because of backward compatibility issues
+ // Also, UTC only, again because of backward compatibility (%z is C++11)
+ time_t rawtime;
+ std::time(&rawtime);
+ auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
+
+ std::tm* timeInfo;
+ timeInfo = std::gmtime(&rawtime);
+
+ char timeStamp[timeStampSize];
+ const char* const fmt = "%Y-%m-%dT%H:%M:%SZ";
+
+ std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
+ return std::string(timeStamp);
+ }
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+ struct JUnitTestMessage
+ {
+ JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details)
+ : message(_message), type(_type), details(_details) {}
+
+ JUnitTestMessage(const std::string& _message, const std::string& _details)
+ : message(_message), type(), details(_details) {}
+
+ std::string message, type, details;
+ };
+
+ struct JUnitTestCase
+ {
+ JUnitTestCase(const std::string& _classname, const std::string& _name)
+ : classname(_classname), name(_name), time(0), failures() {}
+
+ std::string classname, name;
+ double time;
+ std::vector<JUnitTestMessage> failures, errors;
+ };
+
+ void add(const std::string& classname, const std::string& name) {
+ testcases.emplace_back(classname, name);
+ }
+
+ void appendSubcaseNamesToLastTestcase(std::vector<String> nameStack) {
+ for(auto& curr: nameStack)
+ if(curr.size())
+ testcases.back().name += std::string("/") + curr.c_str();
+ }
+
+ void addTime(double time) {
+ if(time < 1e-4)
+ time = 0;
+ testcases.back().time = time;
+ totalSeconds += time;
+ }
+
+ void addFailure(const std::string& message, const std::string& type, const std::string& details) {
+ testcases.back().failures.emplace_back(message, type, details);
+ ++totalFailures;
+ }
+
+ void addError(const std::string& message, const std::string& details) {
+ testcases.back().errors.emplace_back(message, details);
+ ++totalErrors;
+ }
+
+ std::vector<JUnitTestCase> testcases;
+ double totalSeconds = 0;
+ int totalErrors = 0, totalFailures = 0;
+ };
+
+ JUnitTestCaseData testCaseData;
+
+ // caching pointers/references to objects of these types - safe to do
+ const ContextOptions& opt;
+ const TestCaseData* tc = nullptr;
+
+ JUnitReporter(const ContextOptions& co)
+ : xml(*co.cout)
+ , opt(co) {}
+
+ unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; }
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+ // =========================================================================================
+
+ void report_query(const QueryData&) override {}
+
+ void test_run_start() override {}
+
+ void test_run_end(const TestRunStats& p) override {
+ // remove .exe extension - mainly to have the same output on UNIX and Windows
+ std::string binary_name = skipPathFromFilename(opt.binary_name.c_str());
+#ifdef DOCTEST_PLATFORM_WINDOWS
+ if(binary_name.rfind(".exe") != std::string::npos)
+ binary_name = binary_name.substr(0, binary_name.length() - 4);
+#endif // DOCTEST_PLATFORM_WINDOWS
+ xml.startElement("testsuites");
+ xml.startElement("testsuite").writeAttribute("name", binary_name)
+ .writeAttribute("errors", testCaseData.totalErrors)
+ .writeAttribute("failures", testCaseData.totalFailures)
+ .writeAttribute("tests", p.numAsserts);
+ if(opt.no_time_in_output == false) {
+ xml.writeAttribute("time", testCaseData.totalSeconds);
+ xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp());
+ }
+ if(opt.no_version == false)
+ xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR);
+
+ for(const auto& testCase : testCaseData.testcases) {
+ xml.startElement("testcase")
+ .writeAttribute("classname", testCase.classname)
+ .writeAttribute("name", testCase.name);
+ if(opt.no_time_in_output == false)
+ xml.writeAttribute("time", testCase.time);
+ // This is not ideal, but it should be enough to mimic gtest's junit output.
+ xml.writeAttribute("status", "run");
+
+ for(const auto& failure : testCase.failures) {
+ xml.scopedElement("failure")
+ .writeAttribute("message", failure.message)
+ .writeAttribute("type", failure.type)
+ .writeText(failure.details, false);
+ }
+
+ for(const auto& error : testCase.errors) {
+ xml.scopedElement("error")
+ .writeAttribute("message", error.message)
+ .writeText(error.details);
+ }
+
+ xml.endElement();
+ }
+ xml.endElement();
+ xml.endElement();
+ }
+
+ void test_case_start(const TestCaseData& in) override {
+ testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name);
+ timer.start();
+ }
+
+ void test_case_reenter(const TestCaseData& in) override {
+ testCaseData.addTime(timer.getElapsedSeconds());
+ testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames);
+ deepestSubcaseStackNames.clear();
+
+ timer.start();
+ testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name);
+ }
+
+ void test_case_end(const CurrentTestCaseStats&) override {
+ testCaseData.addTime(timer.getElapsedSeconds());
+ testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames);
+ deepestSubcaseStackNames.clear();
+ }
+
+ void test_case_exception(const TestCaseException& e) override {
+ std::lock_guard<std::mutex> lock(mutex);
+ testCaseData.addError("exception", e.error_string.c_str());
+ }
+
+ void subcase_start(const SubcaseSignature& in) override {
+ std::lock_guard<std::mutex> lock(mutex);
+ deepestSubcaseStackNames.push_back(in.m_name);
+ }
+
+ void subcase_end() override {}
+
+ void log_assert(const AssertData& rb) override {
+ if(!rb.m_failed) // report only failures & ignore the `success` option
+ return;
+
+ std::lock_guard<std::mutex> lock(mutex);
+
+ std::ostringstream os;
+ os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(")
+ << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl;
+
+ fulltext_log_assert_to_stream(os, rb);
+ testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str());
+ }
+
+ void log_message(const MessageData&) override {}
+
+ void test_case_skipped(const TestCaseData&) override {}
+ };
+
+ DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter);
+
+ struct Whitespace
+ {
+ int nrSpaces;
+ explicit Whitespace(int nr)
+ : nrSpaces(nr) {}
+ };
+
+ std::ostream& operator<<(std::ostream& out, const Whitespace& ws) {
+ if(ws.nrSpaces != 0)
+ out << std::setw(ws.nrSpaces) << ' ';
+ return out;
+ }
+
+ struct ConsoleReporter : public IReporter
+ {
+ std::ostream& s;
+ bool hasLoggedCurrentTestStart;
+ std::vector<SubcaseSignature> subcasesStack;
+ size_t currentSubcaseLevel;
+ std::mutex mutex;
+
+ // caching pointers/references to objects of these types - safe to do
+ const ContextOptions& opt;
+ const TestCaseData* tc;
+
+ ConsoleReporter(const ContextOptions& co)
+ : s(*co.cout)
+ , opt(co) {}
+
+ ConsoleReporter(const ContextOptions& co, std::ostream& ostr)
+ : s(ostr)
+ , opt(co) {}
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE
+ // =========================================================================================
+
+ void separator_to_stream() {
+ s << Color::Yellow
+ << "==============================================================================="
+ "\n";
+ }
+
+ const char* getSuccessOrFailString(bool success, assertType::Enum at,
+ const char* success_str) {
+ if(success)
+ return success_str;
+ return failureString(at);
+ }
+
+ Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) {
+ return success ? Color::BrightGreen :
+ (at & assertType::is_warn) ? Color::Yellow : Color::Red;
+ }
+
+ void successOrFailColoredStringToStream(bool success, assertType::Enum at,
+ const char* success_str = "SUCCESS") {
+ s << getSuccessOrFailColor(success, at)
+ << getSuccessOrFailString(success, at, success_str) << ": ";
+ }
+
+ void log_contexts() {
+ int num_contexts = get_num_active_contexts();
+ if(num_contexts) {
+ auto contexts = get_active_contexts();
+
+ s << Color::None << " logged: ";
+ for(int i = 0; i < num_contexts; ++i) {
+ s << (i == 0 ? "" : " ");
+ contexts[i]->stringify(&s);
+ s << "\n";
+ }
+ }
+
+ s << "\n";
+ }
+
+ // this was requested to be made virtual so users could override it
+ virtual void file_line_to_stream(const char* file, int line,
+ const char* tail = "") {
+ s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(")
+ << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option
+ << (opt.gnu_file_line ? ":" : "):") << tail;
+ }
+
+ void logTestStart() {
+ if(hasLoggedCurrentTestStart)
+ return;
+
+ separator_to_stream();
+ file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n");
+ if(tc->m_description)
+ s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n";
+ if(tc->m_test_suite && tc->m_test_suite[0] != '\0')
+ s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n";
+ if(strncmp(tc->m_name, " Scenario:", 11) != 0)
+ s << Color::Yellow << "TEST CASE: ";
+ s << Color::None << tc->m_name << "\n";
+
+ for(size_t i = 0; i < currentSubcaseLevel; ++i) {
+ if(subcasesStack[i].m_name[0] != '\0')
+ s << " " << subcasesStack[i].m_name << "\n";
+ }
+
+ if(currentSubcaseLevel != subcasesStack.size()) {
+ s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None;
+ for(size_t i = 0; i < subcasesStack.size(); ++i) {
+ if(subcasesStack[i].m_name[0] != '\0')
+ s << " " << subcasesStack[i].m_name << "\n";
+ }
+ }
+
+ s << "\n";
+
+ hasLoggedCurrentTestStart = true;
+ }
+
+ void printVersion() {
+ if(opt.no_version == false)
+ s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \""
+ << DOCTEST_VERSION_STR << "\"\n";
+ }
+
+ void printIntro() {
+ printVersion();
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n";
+ }
+
+ void printHelp() {
+ int sizePrefixDisplay = static_cast<int>(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY));
+ printVersion();
+ // clang-format off
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n";
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "filter values: \"str1,str2,str3\" (comma separated strings)\n";
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "filters use wildcards for matching strings\n";
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "something passes a filter if any of the strings in a filter matches\n";
+#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n";
+#endif
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "Query flags - the program quits after them. Available:\n\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h "
+ << Whitespace(sizePrefixDisplay*0) << "prints this message\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version "
+ << Whitespace(sizePrefixDisplay*1) << "prints the version\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count "
+ << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases "
+ << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites "
+ << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters "
+ << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n";
+ // ================================================================================== << 79
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "The available <int>/<string> options/filters are:\n\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters=<filters> "
+ << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out=<string> "
+ << Whitespace(sizePrefixDisplay*1) << "output filename\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by=<string> "
+ << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n";
+ s << Whitespace(sizePrefixDisplay*3) << " <string> - by [file/suite/name/rand]\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed=<int> "
+ << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first=<int> "
+ << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n";
+ s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last=<int> "
+ << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n";
+ s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after=<int> "
+ << Whitespace(sizePrefixDisplay*1) << "stop after <int> failed assertions\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels=<int> "
+ << Whitespace(sizePrefixDisplay*1) << "apply filters for the first <int> levels\n";
+ s << Color::Cyan << "\n[doctest] " << Color::None;
+ s << "Bool options - can be used like flags and true is assumed. Available:\n\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n";
+ // ================================================================================== << 79
+ // clang-format on
+
+ s << Color::Cyan << "\n[doctest] " << Color::None;
+ s << "for more information visit the project documentation\n\n";
+ }
+
+ void printRegisteredReporters() {
+ printVersion();
+ auto printReporters = [this] (const reporterMap& reporters, const char* type) {
+ if(reporters.size()) {
+ s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n";
+ for(auto& curr : reporters)
+ s << "priority: " << std::setw(5) << curr.first.first
+ << " name: " << curr.first.second << "\n";
+ }
+ };
+ printReporters(getListeners(), "listeners");
+ printReporters(getReporters(), "reporters");
+ }
+
+ void list_query_results() {
+ separator_to_stream();
+ if(opt.count || opt.list_test_cases) {
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "unskipped test cases passing the current filters: "
+ << g_cs->numTestCasesPassingFilters << "\n";
+ } else if(opt.list_test_suites) {
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "unskipped test cases passing the current filters: "
+ << g_cs->numTestCasesPassingFilters << "\n";
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "test suites with unskipped test cases passing the current filters: "
+ << g_cs->numTestSuitesPassingFilters << "\n";
+ }
+ }
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+ // =========================================================================================
+
+ void report_query(const QueryData& in) override {
+ if(opt.version) {
+ printVersion();
+ } else if(opt.help) {
+ printHelp();
+ } else if(opt.list_reporters) {
+ printRegisteredReporters();
+ } else if(opt.count || opt.list_test_cases) {
+ if(opt.list_test_cases) {
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "listing all test case names\n";
+ separator_to_stream();
+ }
+
+ for(unsigned i = 0; i < in.num_data; ++i)
+ s << Color::None << in.data[i]->m_name << "\n";
+
+ separator_to_stream();
+
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "unskipped test cases passing the current filters: "
+ << g_cs->numTestCasesPassingFilters << "\n";
+
+ } else if(opt.list_test_suites) {
+ s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n";
+ separator_to_stream();
+
+ for(unsigned i = 0; i < in.num_data; ++i)
+ s << Color::None << in.data[i]->m_test_suite << "\n";
+
+ separator_to_stream();
+
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "unskipped test cases passing the current filters: "
+ << g_cs->numTestCasesPassingFilters << "\n";
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "test suites with unskipped test cases passing the current filters: "
+ << g_cs->numTestSuitesPassingFilters << "\n";
+ }
+ }
+
+ void test_run_start() override { printIntro(); }
+
+ void test_run_end(const TestRunStats& p) override {
+ separator_to_stream();
+ s << std::dec;
+
+ const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;
+ s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6)
+ << p.numTestCasesPassingFilters << " | "
+ << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None :
+ Color::Green)
+ << std::setw(6) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed"
+ << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None)
+ << std::setw(6) << p.numTestCasesFailed << " failed" << Color::None << " | ";
+ if(opt.no_skipped_summary == false) {
+ const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters;
+ s << (numSkipped == 0 ? Color::None : Color::Yellow) << std::setw(6) << numSkipped
+ << " skipped" << Color::None;
+ }
+ s << "\n";
+ s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(6)
+ << p.numAsserts << " | "
+ << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green)
+ << std::setw(6) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None
+ << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6)
+ << p.numAssertsFailed << " failed" << Color::None << " |\n";
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green)
+ << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl;
+ }
+
+ void test_case_start(const TestCaseData& in) override {
+ hasLoggedCurrentTestStart = false;
+ tc = &in;
+ subcasesStack.clear();
+ currentSubcaseLevel = 0;
+ }
+
+ void test_case_reenter(const TestCaseData&) override {
+ subcasesStack.clear();
+ }
+
+ void test_case_end(const CurrentTestCaseStats& st) override {
+ // log the preamble of the test case only if there is something
+ // else to print - something other than that an assert has failed
+ if(opt.duration ||
+ (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure))
+ logTestStart();
+
+ if(opt.duration)
+ s << Color::None << std::setprecision(6) << std::fixed << st.seconds
+ << " s: " << tc->m_name << "\n";
+
+ if(st.failure_flags & TestCaseFailureReason::Timeout)
+ s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6)
+ << std::fixed << tc->m_timeout << "!\n";
+
+ if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) {
+ s << Color::Red << "Should have failed but didn't! Marking it as failed!\n";
+ } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) {
+ s << Color::Yellow << "Failed as expected so marking it as not failed\n";
+ } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) {
+ s << Color::Yellow << "Allowed to fail so marking it as not failed\n";
+ } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) {
+ s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures
+ << " times so marking it as failed!\n";
+ } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) {
+ s << Color::Yellow << "Failed exactly " << tc->m_expected_failures
+ << " times as expected so marking it as not failed!\n";
+ }
+ if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) {
+ s << Color::Red << "Aborting - too many failed asserts!\n";
+ }
+ s << Color::None; // lgtm [cpp/useless-expression]
+ }
+
+ void test_case_exception(const TestCaseException& e) override {
+ logTestStart();
+
+ file_line_to_stream(tc->m_file.c_str(), tc->m_line, " ");
+ successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require :
+ assertType::is_check);
+ s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ")
+ << Color::Cyan << e.error_string << "\n";
+
+ int num_stringified_contexts = get_num_stringified_contexts();
+ if(num_stringified_contexts) {
+ auto stringified_contexts = get_stringified_contexts();
+ s << Color::None << " logged: ";
+ for(int i = num_stringified_contexts; i > 0; --i) {
+ s << (i == num_stringified_contexts ? "" : " ")
+ << stringified_contexts[i - 1] << "\n";
+ }
+ }
+ s << "\n" << Color::None;
+ }
+
+ void subcase_start(const SubcaseSignature& subc) override {
+ std::lock_guard<std::mutex> lock(mutex);
+ subcasesStack.push_back(subc);
+ ++currentSubcaseLevel;
+ hasLoggedCurrentTestStart = false;
+ }
+
+ void subcase_end() override {
+ std::lock_guard<std::mutex> lock(mutex);
+ --currentSubcaseLevel;
+ hasLoggedCurrentTestStart = false;
+ }
+
+ void log_assert(const AssertData& rb) override {
+ if(!rb.m_failed && !opt.success)
+ return;
+
+ std::lock_guard<std::mutex> lock(mutex);
+
+ logTestStart();
+
+ file_line_to_stream(rb.m_file, rb.m_line, " ");
+ successOrFailColoredStringToStream(!rb.m_failed, rb.m_at);
+
+ fulltext_log_assert_to_stream(s, rb);
+
+ log_contexts();
+ }
+
+ void log_message(const MessageData& mb) override {
+ std::lock_guard<std::mutex> lock(mutex);
+
+ logTestStart();
+
+ file_line_to_stream(mb.m_file, mb.m_line, " ");
+ s << getSuccessOrFailColor(false, mb.m_severity)
+ << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity,
+ "MESSAGE") << ": ";
+ s << Color::None << mb.m_string << "\n";
+ log_contexts();
+ }
+
+ void test_case_skipped(const TestCaseData&) override {}
+ };
+
+ DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter);
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+ struct DebugOutputWindowReporter : public ConsoleReporter
+ {
+ DOCTEST_THREAD_LOCAL static std::ostringstream oss;
+
+ DebugOutputWindowReporter(const ContextOptions& co)
+ : ConsoleReporter(co, oss) {}
+
+#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \
+ void func(type arg) override { \
+ bool with_col = g_no_colors; \
+ g_no_colors = false; \
+ ConsoleReporter::func(arg); \
+ DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \
+ oss.str(""); \
+ g_no_colors = with_col; \
+ }
+
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in)
+ DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in)
+ };
+
+ DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss;
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+ // the implementation of parseOption()
+ bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) {
+ // going from the end to the beginning and stopping on the first occurrence from the end
+ for(int i = argc; i > 0; --i) {
+ auto index = i - 1;
+ auto temp = std::strstr(argv[index], pattern);
+ if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue
+ // eliminate matches in which the chars before the option are not '-'
+ bool noBadCharsFound = true;
+ auto curr = argv[index];
+ while(curr != temp) {
+ if(*curr++ != '-') {
+ noBadCharsFound = false;
+ break;
+ }
+ }
+ if(noBadCharsFound && argv[index][0] == '-') {
+ if(value) {
+ // parsing the value of an option
+ temp += strlen(pattern);
+ const unsigned len = strlen(temp);
+ if(len) {
+ *value = temp;
+ return true;
+ }
+ } else {
+ // just a flag - no value
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ // parses an option and returns the string after the '=' character
+ bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr,
+ const String& defaultVal = String()) {
+ if(value)
+ *value = defaultVal;
+#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+ // offset (normally 3 for "dt-") to skip prefix
+ if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value))
+ return true;
+#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+ return parseOptionImpl(argc, argv, pattern, value);
+ }
+
+ // locates a flag on the command line
+ bool parseFlag(int argc, const char* const* argv, const char* pattern) {
+ return parseOption(argc, argv, pattern);
+ }
+
+ // parses a comma separated list of words after a pattern in one of the arguments in argv
+ bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern,
+ std::vector<String>& res) {
+ String filtersString;
+ if(parseOption(argc, argv, pattern, &filtersString)) {
+ // tokenize with "," as a separator
+ // cppcheck-suppress strtokCalled
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+ auto pch = std::strtok(filtersString.c_str(), ","); // modifies the string
+ while(pch != nullptr) {
+ if(strlen(pch))
+ res.push_back(pch);
+ // uses the strtok() internal state to go to the next token
+ // cppcheck-suppress strtokCalled
+ pch = std::strtok(nullptr, ",");
+ }
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ return true;
+ }
+ return false;
+ }
+
+ enum optionType
+ {
+ option_bool,
+ option_int
+ };
+
+ // parses an int/bool option from the command line
+ bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type,
+ int& res) {
+ String parsedValue;
+ if(!parseOption(argc, argv, pattern, &parsedValue))
+ return false;
+
+ if(type == 0) {
+ // boolean
+ const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1
+ const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1
+
+ // if the value matches any of the positive/negative possibilities
+ for(unsigned i = 0; i < 4; i++) {
+ if(parsedValue.compare(positive[i], true) == 0) {
+ res = 1; //!OCLINT parameter reassignment
+ return true;
+ }
+ if(parsedValue.compare(negative[i], true) == 0) {
+ res = 0; //!OCLINT parameter reassignment
+ return true;
+ }
+ }
+ } else {
+ // integer
+ // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse...
+ int theInt = std::atoi(parsedValue.c_str()); // NOLINT
+ if(theInt != 0) {
+ res = theInt; //!OCLINT parameter reassignment
+ return true;
+ }
+ }
+ return false;
+ }
+} // namespace
+
+Context::Context(int argc, const char* const* argv)
+ : p(new detail::ContextState) {
+ parseArgs(argc, argv, true);
+ if(argc)
+ p->binary_name = argv[0];
+}
+
+Context::~Context() {
+ if(g_cs == p)
+ g_cs = nullptr;
+ delete p;
+}
+
+void Context::applyCommandLine(int argc, const char* const* argv) {
+ parseArgs(argc, argv);
+ if(argc)
+ p->binary_name = argv[0];
+}
+
+// parses args
+void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) {
+ using namespace detail;
+
+ // clang-format off
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]);
+ parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]);
+ // clang-format on
+
+ int intRes = 0;
+ String strRes;
+
+#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \
+ if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \
+ parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \
+ p->var = !!intRes; \
+ else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \
+ p->var = true; \
+ else if(withDefaults) \
+ p->var = default
+
+#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \
+ if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \
+ parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \
+ p->var = intRes; \
+ else if(withDefaults) \
+ p->var = default
+
+#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \
+ if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) || \
+ parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \
+ withDefaults) \
+ p->var = strRes
+
+ // clang-format off
+ DOCTEST_PARSE_STR_OPTION("out", "o", out, "");
+ DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file");
+ DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0);
+
+ DOCTEST_PARSE_INT_OPTION("first", "f", first, 0);
+ DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX);
+
+ DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0);
+ DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX);
+
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC));
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false);
+ // clang-format on
+
+ if(withDefaults) {
+ p->help = false;
+ p->version = false;
+ p->count = false;
+ p->list_test_cases = false;
+ p->list_test_suites = false;
+ p->list_reporters = false;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) {
+ p->help = true;
+ p->exit = true;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) {
+ p->version = true;
+ p->exit = true;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) {
+ p->count = true;
+ p->exit = true;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) {
+ p->list_test_cases = true;
+ p->exit = true;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) {
+ p->list_test_suites = true;
+ p->exit = true;
+ }
+ if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") ||
+ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) {
+ p->list_reporters = true;
+ p->exit = true;
+ }
+}
+
+// allows the user to add procedurally to the filters from the command line
+void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); }
+
+// allows the user to clear all filters from the command line
+void Context::clearFilters() {
+ for(auto& curr : p->filters)
+ curr.clear();
+}
+
+// allows the user to override procedurally the int/bool options from the command line
+void Context::setOption(const char* option, int value) {
+ setOption(option, toString(value).c_str());
+}
+
+// allows the user to override procedurally the string options from the command line
+void Context::setOption(const char* option, const char* value) {
+ auto argv = String("-") + option + "=" + value;
+ auto lvalue = argv.c_str();
+ parseArgs(1, &lvalue);
+}
+
+// users should query this in their main() and exit the program if true
+bool Context::shouldExit() { return p->exit; }
+
+void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; }
+
+void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; }
+
+// the main function that does all the filtering and test running
+int Context::run() {
+ using namespace detail;
+
+ // save the old context state in case such was setup - for using asserts out of a testing context
+ auto old_cs = g_cs;
+ // this is the current contest
+ g_cs = p;
+ is_running_in_test = true;
+
+ g_no_colors = p->no_colors;
+ p->resetRunData();
+
+ // stdout by default
+ p->cout = &std::cout;
+ p->cerr = &std::cerr;
+
+ // or to a file if specified
+ std::fstream fstr;
+ if(p->out.size()) {
+ fstr.open(p->out.c_str(), std::fstream::out);
+ p->cout = &fstr;
+ }
+
+ auto cleanup_and_return = [&]() {
+ if(fstr.is_open())
+ fstr.close();
+
+ // restore context
+ g_cs = old_cs;
+ is_running_in_test = false;
+
+ // we have to free the reporters which were allocated when the run started
+ for(auto& curr : p->reporters_currently_used)
+ delete curr;
+ p->reporters_currently_used.clear();
+
+ if(p->numTestCasesFailed && !p->no_exitcode)
+ return EXIT_FAILURE;
+ return EXIT_SUCCESS;
+ };
+
+ // setup default reporter if none is given through the command line
+ if(p->filters[8].empty())
+ p->filters[8].push_back("console");
+
+ // check to see if any of the registered reporters has been selected
+ for(auto& curr : getReporters()) {
+ if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive))
+ p->reporters_currently_used.push_back(curr.second(*g_cs));
+ }
+
+ // TODO: check if there is nothing in reporters_currently_used
+
+ // prepend all listeners
+ for(auto& curr : getListeners())
+ p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs));
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+ if(isDebuggerActive())
+ p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs));
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+ // handle version, help and no_run
+ if(p->no_run || p->version || p->help || p->list_reporters) {
+ DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData());
+
+ return cleanup_and_return();
+ }
+
+ std::vector<const TestCase*> testArray;
+ for(auto& curr : getRegisteredTests())
+ testArray.push_back(&curr);
+ p->numTestCases = testArray.size();
+
+ // sort the collected records
+ if(!testArray.empty()) {
+ if(p->order_by.compare("file", true) == 0) {
+ std::sort(testArray.begin(), testArray.end(), fileOrderComparator);
+ } else if(p->order_by.compare("suite", true) == 0) {
+ std::sort(testArray.begin(), testArray.end(), suiteOrderComparator);
+ } else if(p->order_by.compare("name", true) == 0) {
+ std::sort(testArray.begin(), testArray.end(), nameOrderComparator);
+ } else if(p->order_by.compare("rand", true) == 0) {
+ std::srand(p->rand_seed);
+
+ // random_shuffle implementation
+ const auto first = &testArray[0];
+ for(size_t i = testArray.size() - 1; i > 0; --i) {
+ int idxToSwap = std::rand() % (i + 1); // NOLINT
+
+ const auto temp = first[i];
+
+ first[i] = first[idxToSwap];
+ first[idxToSwap] = temp;
+ }
+ }
+ }
+
+ std::set<String> testSuitesPassingFilt;
+
+ bool query_mode = p->count || p->list_test_cases || p->list_test_suites;
+ std::vector<const TestCaseData*> queryResults;
+
+ if(!query_mode)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY);
+
+ // invoke the registered functions if they match the filter criteria (or just count them)
+ for(auto& curr : testArray) {
+ const auto& tc = *curr;
+
+ bool skip_me = false;
+ if(tc.m_skip && !p->no_skip)
+ skip_me = true;
+
+ if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive))
+ skip_me = true;
+ if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive))
+ skip_me = true;
+ if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive))
+ skip_me = true;
+
+ if(!skip_me)
+ p->numTestCasesPassingFilters++;
+
+ // skip the test if it is not in the execution range
+ if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) ||
+ (p->first > p->numTestCasesPassingFilters))
+ skip_me = true;
+
+ if(skip_me) {
+ if(!query_mode)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc);
+ continue;
+ }
+
+ // do not execute the test if we are to only count the number of filter passing tests
+ if(p->count)
+ continue;
+
+ // print the name of the test and don't execute it
+ if(p->list_test_cases) {
+ queryResults.push_back(&tc);
+ continue;
+ }
+
+ // print the name of the test suite if not done already and don't execute it
+ if(p->list_test_suites) {
+ if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') {
+ queryResults.push_back(&tc);
+ testSuitesPassingFilt.insert(tc.m_test_suite);
+ p->numTestSuitesPassingFilters++;
+ }
+ continue;
+ }
+
+ // execute the test if it passes all the filtering
+ {
+ p->currentTest = &tc;
+
+ p->failure_flags = TestCaseFailureReason::None;
+ p->seconds = 0;
+
+ // reset atomic counters
+ p->numAssertsFailedCurrentTest_atomic = 0;
+ p->numAssertsCurrentTest_atomic = 0;
+
+ p->subcasesPassed.clear();
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc);
+
+ p->timer.start();
+
+ bool run_test = true;
+
+ do {
+ // reset some of the fields for subcases (except for the set of fully passed ones)
+ p->should_reenter = false;
+ p->subcasesCurrentMaxLevel = 0;
+ p->subcasesStack.clear();
+
+ p->shouldLogCurrentException = true;
+
+ // reset stuff for logging with INFO()
+ p->stringifiedContexts.clear();
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ try {
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+ FatalConditionHandler fatalConditionHandler; // Handle signals
+ // execute the test
+ tc.m_test();
+ fatalConditionHandler.reset();
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ } catch(const TestFailureException&) {
+ p->failure_flags |= TestCaseFailureReason::AssertFailure;
+ } catch(...) {
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception,
+ {translateActiveException(), false});
+ p->failure_flags |= TestCaseFailureReason::Exception;
+ }
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+ // exit this loop if enough assertions have failed - even if there are more subcases
+ if(p->abort_after > 0 &&
+ p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) {
+ run_test = false;
+ p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts;
+ }
+
+ if(p->should_reenter && run_test)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc);
+ if(!p->should_reenter)
+ run_test = false;
+ } while(run_test);
+
+ p->finalizeTestCaseData();
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs);
+
+ p->currentTest = nullptr;
+
+ // stop executing tests if enough assertions have failed
+ if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after)
+ break;
+ }
+ }
+
+ if(!query_mode) {
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs);
+ } else {
+ QueryData qdata;
+ qdata.run_stats = g_cs;
+ qdata.data = queryResults.data();
+ qdata.num_data = unsigned(queryResults.size());
+ DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata);
+ }
+
+ // see these issues on the reasoning for this:
+ // - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903
+ // - https://github.com/onqtam/doctest/issues/126
+ auto DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS = []() DOCTEST_NOINLINE
+ { std::cout << std::string(); };
+ DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS();
+
+ return cleanup_and_return();
+}
+
+IReporter::~IReporter() = default;
+
+int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); }
+const IContextScope* const* IReporter::get_active_contexts() {
+ return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr;
+}
+
+int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); }
+const String* IReporter::get_stringified_contexts() {
+ return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr;
+}
+
+namespace detail {
+ void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) {
+ if(isReporter)
+ getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
+ else
+ getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
+ }
+} // namespace detail
+
+} // namespace doctest
+
+#endif // DOCTEST_CONFIG_DISABLE
+
+#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182
+int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); }
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+#endif // DOCTEST_LIBRARY_IMPLEMENTATION
+#endif // DOCTEST_CONFIG_IMPLEMENT
diff --git a/thirdparty/doctest/patches/fix-arm64-mac.patch b/thirdparty/doctest/patches/fix-arm64-mac.patch
new file mode 100644
index 0000000000..f78014534f
--- /dev/null
+++ b/thirdparty/doctest/patches/fix-arm64-mac.patch
@@ -0,0 +1,18 @@
+diff --git a/thirdparty/doctest/doctest.h b/thirdparty/doctest/doctest.h
+index 9444698286..e4fed12767 100644
+--- a/thirdparty/doctest/doctest.h
++++ b/thirdparty/doctest/doctest.h
+@@ -356,7 +356,13 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum'
+ #ifndef DOCTEST_BREAK_INTO_DEBUGGER
+ // should probably take a look at https://github.com/scottt/debugbreak
+ #ifdef DOCTEST_PLATFORM_MAC
++// -- GODOT start --
++#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__)
+ #define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :)
++#else
++#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0");
++#endif
++// -- GODOT end --
+ #elif DOCTEST_MSVC
+ #define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak()
+ #elif defined(__MINGW32__)
diff --git a/thirdparty/enet/godot.cpp b/thirdparty/enet/godot.cpp
index 36b5131f80..55b160d752 100644
--- a/thirdparty/enet/godot.cpp
+++ b/thirdparty/enet/godot.cpp
@@ -44,15 +44,14 @@
/// Abstract ENet interface for UDP/DTLS.
class ENetGodotSocket {
-
public:
virtual Error bind(IP_Address p_ip, uint16_t p_port) = 0;
virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP_Address p_ip, uint16_t p_port) = 0;
virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port) = 0;
virtual int set_option(ENetSocketOption p_option, int p_value) = 0;
virtual void close() = 0;
- virtual void set_refuse_new_connections(bool p_refuse) { /* Only used by dtls server */ }
- virtual ~ENetGodotSocket(){};
+ virtual void set_refuse_new_connections(bool p_enable) {} /* Only used by dtls server */
+ virtual ~ENetGodotSocket() {}
};
class ENetDTLSClient;
@@ -60,21 +59,19 @@ class ENetDTLSServer;
/// NetSocket interface
class ENetUDP : public ENetGodotSocket {
-
friend class ENetDTLSClient;
friend class ENetDTLSServer;
private:
Ref<NetSocket> sock;
IP_Address address;
- uint16_t port;
- bool bound;
+ uint16_t port = 0;
+ bool bound = false;
public:
ENetUDP() {
sock = Ref<NetSocket>(NetSocket::create());
IP::Type ip_type = IP::TYPE_ANY;
- bound = false;
sock->open(NetSocket::TYPE_UDP, ip_type);
}
@@ -95,8 +92,9 @@ public:
Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port) {
Error err = sock->poll(NetSocket::POLL_TYPE_IN, 0);
- if (err != OK)
+ if (err != OK) {
return err;
+ }
return sock->recvfrom(p_buffer, p_len, r_read, r_ip, r_port);
}
@@ -149,11 +147,10 @@ public:
/// DTLS Client ENet interface
class ENetDTLSClient : public ENetGodotSocket {
-
- bool connected;
+ bool connected = false;
Ref<PacketPeerUDP> udp;
Ref<PacketPeerDTLS> dtls;
- bool verify;
+ bool verify = false;
String for_hostname;
Ref<X509Certificate> cert;
@@ -168,7 +165,6 @@ public:
if (p_base->bound) {
bind(p_base->address, p_base->port);
}
- connected = false;
}
~ENetDTLSClient() {
@@ -186,25 +182,29 @@ public:
connected = true;
}
dtls->poll();
- if (dtls->get_status() == PacketPeerDTLS::STATUS_HANDSHAKING)
+ if (dtls->get_status() == PacketPeerDTLS::STATUS_HANDSHAKING) {
return ERR_BUSY;
- else if (dtls->get_status() != PacketPeerDTLS::STATUS_CONNECTED)
+ } else if (dtls->get_status() != PacketPeerDTLS::STATUS_CONNECTED) {
return FAILED;
+ }
r_sent = p_len;
return dtls->put_packet(p_buffer, p_len);
}
Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port) {
dtls->poll();
- if (dtls->get_status() == PacketPeerDTLS::STATUS_HANDSHAKING)
+ if (dtls->get_status() == PacketPeerDTLS::STATUS_HANDSHAKING) {
return ERR_BUSY;
- if (dtls->get_status() != PacketPeerDTLS::STATUS_CONNECTED)
+ }
+ if (dtls->get_status() != PacketPeerDTLS::STATUS_CONNECTED) {
return FAILED;
+ }
int pc = dtls->get_available_packet_count();
- if (pc == 0)
+ if (pc == 0) {
return ERR_BUSY;
- else if (pc < 0)
+ } else if (pc < 0) {
return FAILED;
+ }
const uint8_t *buffer;
Error err = dtls->get_packet(&buffer, r_read);
@@ -229,15 +229,13 @@ public:
/// DTLSServer - ENet interface
class ENetDTLSServer : public ENetGodotSocket {
-
Ref<DTLSServer> server;
Ref<UDPServer> udp_server;
- Map<String, Ref<PacketPeerDTLS> > peers;
- int last_service;
+ Map<String, Ref<PacketPeerDTLS>> peers;
+ int last_service = 0;
public:
ENetDTLSServer(ENetUDP *p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert) {
- last_service = 0;
udp_server.instance();
p_base->close();
if (p_base->bound) {
@@ -264,12 +262,13 @@ public:
ERR_FAIL_COND_V(!peers.has(key), ERR_UNAVAILABLE);
Ref<PacketPeerDTLS> peer = peers[key];
Error err = peer->put_packet(p_buffer, p_len);
- if (err == OK)
+ if (err == OK) {
r_sent = p_len;
- else if (err == ERR_BUSY)
+ } else if (err == ERR_BUSY) {
r_sent = 0;
- else
+ } else {
r_sent = -1;
+ }
return err;
}
@@ -291,13 +290,13 @@ public:
List<String> remove;
Error err = ERR_BUSY;
// TODO this needs to be fair!
- for (Map<String, Ref<PacketPeerDTLS> >::Element *E = peers.front(); E; E = E->next()) {
+ for (Map<String, Ref<PacketPeerDTLS>>::Element *E = peers.front(); E; E = E->next()) {
Ref<PacketPeerDTLS> peer = E->get();
peer->poll();
- if (peer->get_status() == PacketPeerDTLS::STATUS_HANDSHAKING)
+ if (peer->get_status() == PacketPeerDTLS::STATUS_HANDSHAKING) {
continue;
- else if (peer->get_status() != PacketPeerDTLS::STATUS_CONNECTED) {
+ } else if (peer->get_status() != PacketPeerDTLS::STATUS_CONNECTED) {
// Peer disconnected, removing it.
remove.push_back(E->key());
continue;
@@ -336,7 +335,7 @@ public:
}
void close() {
- for (Map<String, Ref<PacketPeerDTLS> >::Element *E = peers.front(); E; E = E->next()) {
+ for (Map<String, Ref<PacketPeerDTLS>>::Element *E = peers.front(); E; E = E->next()) {
E->get()->disconnect_from_peer();
}
peers.clear();
@@ -348,7 +347,6 @@ public:
static enet_uint32 timeBase = 0;
int enet_initialize(void) {
-
return 0;
}
@@ -356,22 +354,18 @@ void enet_deinitialize(void) {
}
enet_uint32 enet_host_random_seed(void) {
-
return (enet_uint32)OS::get_singleton()->get_unix_time();
}
enet_uint32 enet_time_get(void) {
-
return OS::get_singleton()->get_ticks_msec() - timeBase;
}
void enet_time_set(enet_uint32 newTimeBase) {
-
timeBase = OS::get_singleton()->get_ticks_msec() - newTimeBase;
}
int enet_address_set_host(ENetAddress *address, const char *name) {
-
IP_Address ip = IP::get_singleton()->resolve_hostname(name);
ERR_FAIL_COND_V(!ip.is_valid(), -1);
@@ -380,24 +374,20 @@ int enet_address_set_host(ENetAddress *address, const char *name) {
}
void enet_address_set_ip(ENetAddress *address, const uint8_t *ip, size_t size) {
-
int len = size > 16 ? 16 : size;
memset(address->host, 0, 16);
memcpy(address->host, ip, len);
}
int enet_address_get_host_ip(const ENetAddress *address, char *name, size_t nameLength) {
-
return -1;
}
int enet_address_get_host(const ENetAddress *address, char *name, size_t nameLength) {
-
return -1;
}
ENetSocket enet_socket_create(ENetSocketType type) {
-
ENetUDP *socket = memnew(ENetUDP);
return socket;
@@ -421,7 +411,6 @@ void enet_host_refuse_new_connections(ENetHost *host, int p_refuse) {
}
int enet_socket_bind(ENetSocket socket, const ENetAddress *address) {
-
IP_Address ip;
if (address->wildcard) {
ip = IP_Address("*");
@@ -443,8 +432,7 @@ void enet_socket_destroy(ENetSocket socket) {
}
int enet_socket_send(ENetSocket socket, const ENetAddress *address, const ENetBuffer *buffers, size_t bufferCount) {
-
- ERR_FAIL_COND_V(address == NULL, -1);
+ ERR_FAIL_COND_V(address == nullptr, -1);
ENetGodotSocket *sock = (ENetGodotSocket *)socket;
IP_Address dest;
@@ -455,7 +443,7 @@ int enet_socket_send(ENetSocket socket, const ENetAddress *address, const ENetBu
// Create a single packet.
Vector<uint8_t> out;
- uint8_t* w;
+ uint8_t *w;
int size = 0;
int pos = 0;
for (i = 0; i < bufferCount; i++) {
@@ -472,7 +460,6 @@ int enet_socket_send(ENetSocket socket, const ENetAddress *address, const ENetBu
int sent = 0;
err = sock->sendto((const uint8_t *)&w[0], size, sent, dest, address->port);
if (err != OK) {
-
if (err == ERR_BUSY) { // Blocking call
return 0;
}
@@ -485,7 +472,6 @@ int enet_socket_send(ENetSocket socket, const ENetAddress *address, const ENetBu
}
int enet_socket_receive(ENetSocket socket, ENetAddress *address, ENetBuffer *buffers, size_t bufferCount) {
-
ERR_FAIL_COND_V(bufferCount != 1, -1);
ENetGodotSocket *sock = (ENetGodotSocket *)socket;
@@ -494,11 +480,13 @@ int enet_socket_receive(ENetSocket socket, ENetAddress *address, ENetBuffer *buf
IP_Address ip;
Error err = sock->recvfrom((uint8_t *)buffers[0].data, buffers[0].dataLength, read, ip, address->port);
- if (err == ERR_BUSY)
+ if (err == ERR_BUSY) {
return 0;
+ }
- if (err != OK)
+ if (err != OK) {
return -1;
+ }
enet_address_set_ip(address, ip.get_ipv6(), 16);
@@ -507,47 +495,38 @@ int enet_socket_receive(ENetSocket socket, ENetAddress *address, ENetBuffer *buf
// Not implemented
int enet_socket_wait(ENetSocket socket, enet_uint32 *condition, enet_uint32 timeout) {
-
return 0; // do we need this function?
}
int enet_socket_get_address(ENetSocket socket, ENetAddress *address) {
-
return -1; // do we need this function?
}
int enet_socketset_select(ENetSocket maxSocket, ENetSocketSet *readSet, ENetSocketSet *writeSet, enet_uint32 timeout) {
-
return -1;
}
int enet_socket_listen(ENetSocket socket, int backlog) {
-
return -1;
}
int enet_socket_set_option(ENetSocket socket, ENetSocketOption option, int value) {
-
ENetGodotSocket *sock = (ENetGodotSocket *)socket;
return sock->set_option(option, value);
}
int enet_socket_get_option(ENetSocket socket, ENetSocketOption option, int *value) {
-
return -1;
}
int enet_socket_connect(ENetSocket socket, const ENetAddress *address) {
-
return -1;
}
ENetSocket enet_socket_accept(ENetSocket socket, ENetAddress *address) {
-
- return NULL;
+ return nullptr;
}
int enet_socket_shutdown(ENetSocket socket, ENetSocketShutdown how) {
-
return -1;
}
diff --git a/thirdparty/misc/stb_vorbis.c b/thirdparty/misc/stb_vorbis.c
index 52c9c666a2..a8cbfa6c23 100644
--- a/thirdparty/misc/stb_vorbis.c
+++ b/thirdparty/misc/stb_vorbis.c
@@ -1,4 +1,4 @@
-// Ogg Vorbis audio decoder - v1.19 - public domain
+// Ogg Vorbis audio decoder - v1.20 - public domain
// http://nothings.org/stb_vorbis/
//
// Original version written by Sean Barrett in 2007.
@@ -31,9 +31,11 @@
// Phillip Bennefall Rohit Thiago Goulart
// github:manxorist saga musix github:infatum
// Timur Gagiev Maxwell Koo Peter Waller
-// github:audinowho Dougall Johnson
+// github:audinowho Dougall Johnson David Reid
+// github:Clownacy Pedro J. Estebanez Remi Verschelde
//
// Partial history:
+// 1.20 - 2020-07-11 - several small fixes
// 1.19 - 2020-02-05 - warnings
// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc.
// 1.17 - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure)
@@ -577,7 +579,7 @@ enum STBVorbisError
#if defined(_MSC_VER) || defined(__MINGW32__)
#include <malloc.h>
#endif
- #if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__)
+ #if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__)
#include <alloca.h>
#endif
#else // STB_VORBIS_NO_CRT
@@ -599,7 +601,9 @@ enum STBVorbisError
#undef __forceinline
#endif
#define __forceinline
+ #ifndef alloca
#define alloca __builtin_alloca
+ #endif
#elif !defined(_MSC_VER)
#if __GNUC__
#define __forceinline inline
@@ -1600,7 +1604,8 @@ static uint32 get_bits(vorb *f, int n)
f->valid_bits += 8;
}
}
- if (f->valid_bits < 0) return 0;
+
+ assert(f->valid_bits >= n);
z = f->acc & ((1 << n)-1);
f->acc >>= n;
f->valid_bits -= n;
@@ -4256,7 +4261,7 @@ static void vorbis_init(stb_vorbis *p, const stb_vorbis_alloc *z)
memset(p, 0, sizeof(*p)); // NULL out all malloc'd pointers to start
if (z) {
p->alloc = *z;
- p->alloc.alloc_buffer_length_in_bytes = (p->alloc.alloc_buffer_length_in_bytes+3) & ~3;
+ p->alloc.alloc_buffer_length_in_bytes &= ~7;
p->temp_offset = p->alloc.alloc_buffer_length_in_bytes;
}
p->eof = 0;
diff --git a/thirdparty/vulkan/patches/VMA-assert-remove.patch b/thirdparty/vulkan/patches/VMA-assert-remove.patch
new file mode 100644
index 0000000000..3d57ab7d42
--- /dev/null
+++ b/thirdparty/vulkan/patches/VMA-assert-remove.patch
@@ -0,0 +1,29 @@
+diff --git a/thirdparty/vulkan/vk_mem_alloc.h b/thirdparty/vulkan/vk_mem_alloc.h
+index 0dfb66efc6..8a42699e7f 100644
+--- a/thirdparty/vulkan/vk_mem_alloc.h
++++ b/thirdparty/vulkan/vk_mem_alloc.h
+@@ -17508,24 +17508,6 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(
+ allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq,
+ requiresDedicatedAllocation, prefersDedicatedAllocation);
+
+- // Make sure alignment requirements for specific buffer usages reported
+- // in Physical Device Properties are included in alignment reported by memory requirements.
+- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0)
+- {
+- VMA_ASSERT(vkMemReq.alignment %
+- allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0);
+- }
+- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0)
+- {
+- VMA_ASSERT(vkMemReq.alignment %
+- allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0);
+- }
+- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0)
+- {
+- VMA_ASSERT(vkMemReq.alignment %
+- allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0);
+- }
+-
+ // 3. Allocate memory using allocator.
+ res = allocator->AllocateMemory(
+ vkMemReq,
diff --git a/thirdparty/vulkan/vk_mem_alloc.h b/thirdparty/vulkan/vk_mem_alloc.h
index 0dfb66efc6..8a42699e7f 100644
--- a/thirdparty/vulkan/vk_mem_alloc.h
+++ b/thirdparty/vulkan/vk_mem_alloc.h
@@ -17508,24 +17508,6 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(
allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq,
requiresDedicatedAllocation, prefersDedicatedAllocation);
- // Make sure alignment requirements for specific buffer usages reported
- // in Physical Device Properties are included in alignment reported by memory requirements.
- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0)
- {
- VMA_ASSERT(vkMemReq.alignment %
- allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0);
- }
- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0)
- {
- VMA_ASSERT(vkMemReq.alignment %
- allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0);
- }
- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0)
- {
- VMA_ASSERT(vkMemReq.alignment %
- allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0);
- }
-
// 3. Allocate memory using allocator.
res = allocator->AllocateMemory(
vkMemReq,