summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/android_builds.yml72
-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/main.yml322
-rw-r--r--.github/workflows/static_checks.yml (renamed from .github/workflows/static-checks.yml)2
-rw-r--r--.github/workflows/windows_builds.yml123
-rw-r--r--.gitignore4
-rw-r--r--.travis.yml83
-rw-r--r--COPYRIGHT.txt5
-rw-r--r--README.md2
-rw-r--r--SConstruct3
-rw-r--r--core/object.cpp2
-rw-r--r--core/script_language.h3
-rw-r--r--core/string_name.h4
-rw-r--r--editor/animation_track_editor.cpp4
-rw-r--r--editor/code_editor.cpp1
-rw-r--r--editor/create_dialog.cpp1
-rw-r--r--editor/editor_autoload_settings.cpp4
-rw-r--r--editor/editor_file_dialog.cpp2
-rw-r--r--editor/editor_node.cpp10
-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/node_3d_editor_plugin.cpp10
-rw-r--r--editor/plugins/script_text_editor.cpp6
-rw-r--r--editor/plugins/theme_editor_plugin.cpp4
-rw-r--r--editor/plugins/tile_set_editor_plugin.cpp2
-rw-r--r--editor/rename_dialog.cpp20
-rw-r--r--editor/scene_tree_dock.cpp3
-rw-r--r--editor/script_create_dialog.cpp4
-rw-r--r--main/SCsub1
-rw-r--r--main/main.cpp384
-rw-r--r--main/main.h17
-rw-r--r--main/tests/test_gdscript.cpp982
-rw-r--r--main/tests/test_main.cpp111
-rw-r--r--main/tests/test_main.h2
-rw-r--r--main/tests/test_string.cpp1207
-rw-r--r--main/tests/test_string.h758
-rw-r--r--main/tests/test_validate_testing.h42
-rw-r--r--methods.py2
-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--platform/android/export/export.cpp190
-rw-r--r--platform/android/export/gradle_export_util.h100
-rw-r--r--platform/android/java/app/build.gradle2
-rw-r--r--platform/android/java/app/config.gradle16
-rw-r--r--platform/iphone/godot_iphone.cpp3
-rw-r--r--platform/javascript/javascript_main.cpp4
-rw-r--r--platform/linuxbsd/godot_linuxbsd.cpp3
-rw-r--r--platform/osx/detect.py6
-rw-r--r--platform/osx/godot_main_osx.mm5
-rw-r--r--platform/server/godot_server.cpp3
-rw-r--r--platform/windows/godot_windows.cpp8
-rw-r--r--scene/gui/box_container.cpp11
-rw-r--r--scene/gui/file_dialog.cpp2
-rw-r--r--scene/gui/line_edit.cpp38
-rw-r--r--scene/gui/line_edit.h4
-rw-r--r--scene/resources/animation.cpp2
-rw-r--r--thirdparty/README.md11
-rw-r--r--thirdparty/doctest/LICENSE.txt21
-rw-r--r--thirdparty/doctest/doctest.h6211
-rw-r--r--thirdparty/doctest/patches/fix-arm64-mac.patch18
83 files changed, 21669 insertions, 16800 deletions
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/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..be7737593a
--- /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 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..4a9fa910d0
--- /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 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/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index 77554ea0ef..0000000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,322 +0,0 @@
-name: Godot
-# events to run the build steps
-on: [push, pull_request]
-
-# Global Cache Settings
-# SCONS_CACHE for windows must be set in the build environment
-env:
- SCONS_CACHE_MSVC_CONFIG: true
- SCONS_CACHE_LIMIT: 8192
-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)
-
- 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.
- - name: Load .scons_cache directory
- id: windows-editor-cache
- uses: RevoluPowered/cache@v2.1
- with:
- path: /.scons_cache/
- key: ${{runner.os}}-editor-${{github.sha}}
- restore-keys: |
- ${{runner.os}}-editor-${{github.sha}}
- ${{runner.os}}-editor
- ${{runner.os}}
-
- # 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 pywin32
- 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: /.scons_cache/
- run: |
- scons -j2 verbose=yes warnings=all werror=yes platform=windows tools=yes target=release_debug
-
-# 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
-# tested this code and it works fine so just enable it to get them back
-# - name: publishing godot windows-editor
-# uses: actions/upload-artifact@v1
-# with:
-# 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)
-
- 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.
- - name: Load .scons_cache directory
- id: windows-template-cache
- uses: RevoluPowered/cache@v2.1
- with:
- path: /.scons_cache/
- key: ${{runner.os}}-template-${{github.sha}}
- restore-keys: |
- ${{runner.os}}-template-${{github.sha}}
- ${{runner.os}}-template
- ${{runner.os}}
-
- # 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 pywin32
- python --version
- scons --version
- - name: Compilation
- env:
- SCONS_CACHE: /.scons_cache/
- run: |
- scons -j2 verbose=yes warnings=all werror=yes platform=windows target=release tools=no
-
-# 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
-# tested this code and it works fine so just enable it to get them back
-# - name: publishing godot windows-template
-# uses: actions/upload-artifact@v1
-# with:
-# name: windows-template (x64)
-# path: bin/godot.windows.opt.64.exe
-
- macos-editor:
- runs-on: "macos-latest"
-
- name: MacOS 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.
- - name: Load .scons_cache directory
- id: macos-editor-cache
- uses: actions/cache@v2
- with:
- path: ${{github.workspace}}/.scons_cache/
- key: ${{runner.os}}-editor-${{github.sha}}
- restore-keys: |
- ${{runner.os}}-editor-${{github.sha}}
- ${{runner.os}}-editor
- ${{runner.os}}
-
- # 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 target=release_debug
-
- macos-template:
- runs-on: "macos-latest"
- name: MacOS 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.
- - name: Load .scons_cache directory
- id: macos-template-cache
- uses: actions/cache@v2
- with:
- path: ${{github.workspace}}/.scons_cache/
- key: ${{runner.os}}-template-${{github.sha}}
- restore-keys: |
- ${{runner.os}}-template-${{github.sha}}
- ${{runner.os}}-template
- ${{runner.os}}
-
- # 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
-
- linux-editor:
- runs-on: "ubuntu-20.04"
-
- # Windows Editor - checkout with the plugin
- name: Linux Editor (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
- # 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.
- - name: Load .scons_cache directory
- id: linux-editor-cache
- uses: actions/cache@v2
- with:
- path: ${{github.workspace}}/.scons_cache/
- key: ${{runner.os}}-editor-${{github.sha}}
- restore-keys: |
- ${{runner.os}}-editor-${{github.sha}}
- ${{runner.os}}-editor
- ${{runner.os}}
-
- # 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 target=release_debug
-
- linux-template:
- runs-on: "ubuntu-20.04"
- name: Linux 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 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
- # 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.
- - name: Load .scons_cache directory
- id: linux-template-cache
- uses: actions/cache@v2
- with:
- path: ${{github.workspace}}/.scons_cache/
- key: ${{runner.os}}-template-${{github.sha}}
- restore-keys: |
- ${{runner.os}}-template-${{github.sha}}
- ${{runner.os}}-template
- ${{runner.os}}
-
- # 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
diff --git a/.github/workflows/static-checks.yml b/.github/workflows/static_checks.yml
index 095bf32e1f..87339da776 100644
--- a/.github/workflows/static-checks.yml
+++ b/.github/workflows/static_checks.yml
@@ -1,4 +1,4 @@
-name: Godot
+name: Static Checks
on: [push, pull_request]
jobs:
diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml
new file mode 100644
index 0000000000..c58bae8275
--- /dev/null
+++ b/.github/workflows/windows_builds.yml
@@ -0,0 +1,123 @@
+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: 4096
+
+jobs:
+ windows-editor:
+ # Windows 10 with latest image
+ runs-on: "windows-latest"
+
+ # Windows Editor - checkout with the plugin
+ 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 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: ${{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 pywin32
+ 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: /.scons_cache/
+ run: |
+ scons -j2 verbose=yes warnings=all werror=yes platform=windows tools=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
+# tested this code and it works fine so just enable it to get them back
+# - name: publishing godot windows-editor
+# uses: actions/upload-artifact@v1
+# with:
+# name: windows-editor (x64)
+# path: bin/godot.windows.opt.tools.64.exe
+
+ windows-template:
+ runs-on: "windows-latest"
+ 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 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: ${{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 pywin32
+ python --version
+ scons --version
+
+ - name: Compilation
+ env:
+ SCONS_CACHE: /.scons_cache/
+ run: |
+ scons -j2 verbose=yes warnings=all werror=yes platform=windows target=release tools=no
+
+# 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
+# tested this code and it works fine so just enable it to get them back
+# - name: publishing godot windows-template
+# uses: actions/upload-artifact@v1
+# with:
+# name: windows-template (x64)
+# path: bin/godot.windows.opt.64.exe
diff --git a/.gitignore b/.gitignore
index f4af79929c..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/
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index cb576efee7..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,83 +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"
-
-cache:
- directories:
- - $SCONS_CACHE
-
-matrix:
- include:
- - 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
-
-# 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: 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/ci/android-tools-linux.sh;
- fi
- - if [ "$PLATFORM" = "javascript" ]; then
- git clone --depth 1 https://github.com/emscripten-core/emsdk;
- ./emsdk/emsdk install latest;
- ./emsdk/emsdk activate latest;
- source ./emsdk/emsdk_env.sh;
- 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
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 5cfa10c5dd..e962f90333 100644
--- a/README.md
+++ b/README.md
@@ -66,9 +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)
[![Actions Build Status](https://github.com/godotengine/godot/workflows/Godot/badge.svg?branch=master)](https://github.com/godotengine/godot/actions)
-[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/bfiihqq6byxsjxxh/branch/master?svg=true)](https://ci.appveyor.com/project/akien-mga/godot)
[![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..e23aa1cdbc 100644
--- a/SConstruct
+++ b/SConstruct
@@ -641,6 +641,9 @@ if selected_platform in platform_list:
}
)
+ # enable test framework globally and inform it of configuration method
+ env.Append(CPPDEFINES=["DOCTEST_CONFIG_IMPLEMENT"])
+
scons_cache_path = os.environ.get("SCONS_CACHE")
if scons_cache_path != None:
CacheDir(scons_cache_path)
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/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/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..9a87d6d38c 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -1778,6 +1778,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 c0c3a73957..1e3dc01112 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -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/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 7cff3263f2..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());
}
}
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/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_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 1e03d9dfab..f4fdf8ccb0 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -494,7 +494,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 +506,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.
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 7fb751e3ed..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()) {
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 13fb74987c..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));
@@ -2717,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..bf188d7328 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))
diff --git a/main/main.cpp b/main/main.cpp
index a500e173a2..35aa99c720 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"
@@ -75,12 +76,14 @@
#include "servers/xr_server.h"
#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 +189,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 +199,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 +259,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 +318,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,35 +337,51 @@ 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
OS::get_singleton()->print(" --test <test> Run a unit test [");
const char **test_names = tests_get_names();
@@ -366,6 +395,24 @@ void Main::print_help(const char *p_binary) {
#endif
}
+int Main::test_entrypoint(int argc, char *argv[], bool &tests_need_run) {
+#ifdef TOOLS_ENABLED // templates can't run unit test tool
+ 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
*
* Consists of several methods that are called by each platform's specific main(argc, argv).
@@ -418,7 +465,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 +571,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 +608,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 +657,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 +680,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 +689,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 +711,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 +808,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 +902,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 +944,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 +992,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 +1055,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 +1102,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 +1114,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;
@@ -1106,8 +1206,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);
@@ -1180,10 +1280,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 +1299,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 +1400,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 +1430,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 +1497,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 +1517,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 +1543,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 +1592,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 +1680,6 @@ bool Main::start() {
String positional_arg;
String game_path;
String script;
- String test;
bool check_only = false;
#ifdef TOOLS_ENABLED
@@ -1555,10 +1690,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 +1728,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 +1759,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 +1852,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 +1871,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 +1961,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 +2011,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 +2056,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 +2073,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 +2130,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 +2147,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 +2218,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 +2231,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 +2270,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 +2346,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 +2363,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 +2426,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
index 10586c6495..a50311972f 100644
--- a/main/tests/test_gdscript.cpp
+++ b/main/tests/test_gdscript.cpp
@@ -33,854 +33,93 @@
#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.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);
-}
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif
-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 += ".";
- }
- }
+namespace TestGDScript {
- for (int i = 0; i < p_class->extends_class.size(); i++) {
- if (i != 0) {
- txt += ".";
- }
+static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) {
+ GDScriptTokenizer tokenizer;
+ tokenizer.set_source_code(p_code);
- txt += p_class->extends_class[i];
+ 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);
- return txt;
-}
+ GDScriptTokenizer::Token current = tokenizer.scan();
+ while (current.type != GDScriptTokenizer::Token::TK_EOF) {
+ StringBuilder token;
+ token += " --> "; // Padding for line number.
-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 += ", ";
- }
+ for (int l = current.start_line; l <= current.end_line; l++) {
+ print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));
+ }
- const GDScriptParser::DictionaryNode::Pair &p = dict_node->elements[i];
- txt += _parser_expr(p.key);
- txt += ":";
- txt += _parser_expr(p.value);
+ {
+ // 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.
}
- 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: {
+ for (int col = 1; col < rightmost_column; col++) {
+ if (col < current.leftmost_column) {
+ pointer += " ";
+ } else {
+ pointer += "^";
}
}
-
- } 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));
+ print_line(pointer.as_string());
}
- }
- return txt;
-}
+ token += current.get_name();
-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));
- }
+ 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;
}
- }
-}
-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(token.as_string());
- print_line("\n");
+ print_line("-------------------------------------------------------");
- for (int i = 0; i < p_class->static_functions.size(); i++) {
- _parser_show_function(p_class->static_functions[i], p_indent);
- print_line("\n");
+ current = tokenizer.scan();
}
- 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>";
+ print_line(current.get_name()); // Should be EOF
}
-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();
+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);
- 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 += " ";
+ 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));
}
- 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;
- }
+ GDScriptParser::TreePrinter printer;
- if (incr == 0) {
- ERR_BREAK_MSG(true, "Unhandled opcode: " + itos(code[ip]));
- }
-
- ip += incr;
- if (txt != "") {
- print_line(txt);
- }
- }
- }
+ printer.print_tree(parser);
}
MainLoop *test(TestType p_type) {
@@ -891,12 +130,12 @@ MainLoop *test(TestType p_type) {
}
String test = cmdlargs.back()->get();
- if (!test.ends_with(".gd") && !test.ends_with(".gdc")) {
+ if (!test.ends_with(".gd")) {
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);
+ FileAccessRef fa = FileAccess::open(test, FileAccess::READ);
ERR_FAIL_COND_V_MSG(!fa, nullptr, "Could not open file: " + test);
Vector<uint8_t> buf;
@@ -910,7 +149,6 @@ MainLoop *test(TestType p_type) {
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));
@@ -918,104 +156,18 @@ MainLoop *test(TestType p_type) {
}
}
- 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);
+ 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.");
}
- memdelete(fa);
-
return nullptr;
}
diff --git a/main/tests/test_main.cpp b/main/tests/test_main.cpp
index 5ebdaf1741..91eff28f86 100644
--- a/main/tests/test_main.cpp
+++ b/main/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/main/tests/test_main.h
index bdb1668d21..8273b74eac 100644
--- a/main/tests/test_main.h
+++ b/main/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_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
index 96fa811126..25fd513a1a 100644
--- a/main/tests/test_string.h
+++ b/main/tests/test_string.h
@@ -31,12 +31,768 @@
#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 {
-MainLoop *test();
+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/main/tests/test_validate_testing.h b/main/tests/test_validate_testing.h
new file mode 100644
index 0000000000..5be7d45185
--- /dev/null
+++ b/main/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/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/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..3d37c7f803 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) {
+ 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/platform/android/export/export.cpp b/platform/android/export/export.cpp
index ed85256695..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"
@@ -767,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
@@ -1408,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);
}
}
}
@@ -2005,6 +2088,13 @@ public:
EditorProgress ep("export", "Exporting for Android", 105, true);
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
@@ -2028,6 +2118,10 @@ public:
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");
@@ -2047,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);
@@ -2060,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.
@@ -2157,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) {
@@ -2207,25 +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") {
- if (!use_custom_build) {
+ if (!use_custom_build) {
+ if (file == "AndroidManifest.xml") {
+ _fix_manifest(p_preset, data, p_give_internet);
+ }
+ 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 (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 (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 622860c307..209a664f8f 100644
--- a/platform/android/export/gradle_export_util.h
+++ b/platform/android/export/gradle_export_util.h
@@ -142,4 +142,104 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset
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/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.cpp
index b9d217c9d2..aa5dbd5130 100644
--- a/platform/iphone/godot_iphone.cpp
+++ b/platform/iphone/godot_iphone.cpp
@@ -67,6 +67,9 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
printf("cwd %s\n", cwd);
os = new OSIPhone(width, height, data_dir);
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
+
char *fargv[64];
for (int i = 0; i < argc; i++) {
fargv[i] = argv[i];
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/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 25d230fc89..d700bcd7f6 100644
--- a/platform/osx/detect.py
+++ b/platform/osx/detect.py
@@ -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"])
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/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/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/gui/box_container.cpp b/scene/gui/box_container.cpp
index 191110a669..f130726837 100644
--- a/scene/gui/box_container.cpp
+++ b/scene/gui/box_container.cpp
@@ -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 3a0fbeaac3..646f9f6095 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -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) {
@@ -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();
}
@@ -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();
}
}
@@ -1804,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);
@@ -1870,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() {
@@ -1899,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 2ed63e7d50..d6cc1f1f11 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -136,6 +136,7 @@ private:
void update_placeholder_width();
bool caret_blink_enabled;
+ bool caret_force_displayed;
bool draw_caret;
bool window_has_focus;
@@ -203,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();
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/thirdparty/README.md b/thirdparty/README.md
index 392abea85e..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
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__)