summaryrefslogtreecommitdiff
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/SCsub4
-rw-r--r--modules/gdscript/config.py1
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml227
-rw-r--r--modules/gdscript/doc_classes/GDScript.xml2
-rw-r--r--modules/gdscript/doc_classes/GDScriptFunctionState.xml45
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp45
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h2
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.cpp376
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.h33
-rw-r--r--modules/gdscript/gdscript.cpp321
-rw-r--r--modules/gdscript/gdscript.h36
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp656
-rw-r--r--modules/gdscript/gdscript_analyzer.h20
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp1212
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h443
-rw-r--r--modules/gdscript/gdscript_cache.cpp41
-rw-r--r--modules/gdscript/gdscript_cache.h12
-rw-r--r--modules/gdscript/gdscript_codegen.h165
-rw-r--r--modules/gdscript/gdscript_compiler.cpp2843
-rw-r--r--modules/gdscript/gdscript_compiler.h160
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp810
-rw-r--r--modules/gdscript/gdscript_editor.cpp138
-rw-r--r--modules/gdscript/gdscript_function.cpp1580
-rw-r--r--modules/gdscript/gdscript_function.h217
-rw-r--r--modules/gdscript/gdscript_functions.cpp84
-rw-r--r--modules/gdscript/gdscript_functions.h8
-rw-r--r--modules/gdscript/gdscript_parser.cpp503
-rw-r--r--modules/gdscript/gdscript_parser.h75
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp162
-rw-r--r--modules/gdscript/gdscript_tokenizer.h56
-rw-r--r--modules/gdscript/gdscript_vm.cpp2863
-rw-r--r--modules/gdscript/gdscript_warning.cpp2
-rw-r--r--modules/gdscript/gdscript_warning.h4
-rw-r--r--modules/gdscript/icons/GDScript.svg6
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp12
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.h2
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp5
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.h2
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp7
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.h2
-rw-r--r--modules/gdscript/language_server/lsp.hpp7
-rw-r--r--modules/gdscript/register_types.cpp33
-rw-r--r--modules/gdscript/tests/test_gdscript.cpp306
-rw-r--r--modules/gdscript/tests/test_gdscript.h46
44 files changed, 9334 insertions, 4240 deletions
diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub
index e58a1d8edc..5c8cbdf869 100644
--- a/modules/gdscript/SCsub
+++ b/modules/gdscript/SCsub
@@ -17,3 +17,7 @@ if env["tools"]:
# Using a define in the disabled case, to avoid having an extra define
# in regular builds where all modules are enabled.
env_gdscript.Append(CPPDEFINES=["GDSCRIPT_NO_LSP"])
+
+if env["tests"]:
+ env_gdscript.Append(CPPDEFINES=["TESTS_ENABLED"])
+ env_gdscript.add_source_files(env.modules_sources, "./tests/*.cpp")
diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py
index 6fc227e7f5..61ce6185a5 100644
--- a/modules/gdscript/config.py
+++ b/modules/gdscript/config.py
@@ -10,7 +10,6 @@ def get_doc_classes():
return [
"@GDScript",
"GDScript",
- "GDScriptFunctionState",
]
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 9e40a69712..eeb66ebfc0 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -7,6 +7,7 @@
List of core built-in GDScript functions. Math functions and other utilities. Everything else is provided by objects. (Keywords: builtin, built in, global functions.)
</description>
<tutorials>
+ <link title="Random number generation">https://docs.godotengine.org/en/latest/tutorials/math/random_number_generation.html</link>
</tutorials>
<methods>
<method name="Color8">
@@ -54,8 +55,7 @@
<description>
Returns the absolute value of parameter [code]s[/code] (i.e. positive value).
[codeblock]
- # a is 1
- a = abs(-1)
+ a = abs(-1) # a is 1
[/codeblock]
</description>
</method>
@@ -93,14 +93,15 @@
<argument index="1" name="message" type="String" default="&quot;&quot;">
</argument>
<description>
- Asserts that the [code]condition[/code] is [code]true[/code]. If the [code]condition[/code] is [code]false[/code], an error is generated and the program is halted until you resume it. Only executes in debug builds, or when running the game from the editor. Use it for debugging purposes, to make sure a statement is [code]true[/code] during development.
+ Asserts that the [code]condition[/code] is [code]true[/code]. If the [code]condition[/code] is [code]false[/code], an error is generated. When running from the editor, the running project will also be paused until you resume it. This can be used as a stronger form of [method push_error] for reporting errors to project developers or add-on users.
+ [b]Note:[/b] For performance reasons, the code inside [method assert] is only executed in debug builds or when running the project from the editor. Don't include code that has side effects in an [method assert] call. Otherwise, the project will behave differently when exported in release mode.
The optional [code]message[/code] argument, if given, is shown in addition to the generic "Assertion failed" message. You can use this to provide additional details about why the assertion failed.
[codeblock]
- # Imagine we always want speed to be between 0 and 20
- speed = -10
+ # Imagine we always want speed to be between 0 and 20.
+ var speed = -10
assert(speed &lt; 20) # True, the program will continue
assert(speed &gt;= 0) # False, the program will stop
- assert(speed &gt;= 0 &amp;&amp; speed &lt; 20) # You can also combine the two conditional statements in one check
+ assert(speed &gt;= 0 and speed &lt; 20) # You can also combine the two conditional statements in one check
assert(speed &lt; 20, "speed = %f, but the speed limit is 20" % speed) # Show a message with clarifying details
[/codeblock]
</description>
@@ -164,9 +165,10 @@
<description>
Rounds [code]s[/code] upward (towards positive infinity), returning the smallest whole number that is not less than [code]s[/code].
[codeblock]
- i = ceil(1.45) # i is 2
- i = ceil(1.001) # i is 2
+ a = ceil(1.45) # a is 2.0
+ a = ceil(1.001) # a is 2.0
[/codeblock]
+ See also [method floor], [method round], [method stepify], and [int].
</description>
</method>
<method name="char">
@@ -196,13 +198,9 @@
<description>
Clamps [code]value[/code] and returns a value not less than [code]min[/code] and not more than [code]max[/code].
[codeblock]
- speed = 1000
- # a is 20
- a = clamp(speed, 1, 20)
-
- speed = -10
- # a is 1
- a = clamp(speed, 1, 20)
+ a = clamp(1000, 1, 20) # a is 20
+ a = clamp(-10, 1, 20) # a is 1
+ a = clamp(15, 1, 20) # a is 15
[/codeblock]
</description>
</method>
@@ -233,9 +231,8 @@
<description>
Returns the cosine of angle [code]s[/code] in radians.
[codeblock]
- # Prints 1 then -1
- print(cos(PI * 2))
- print(cos(PI))
+ a = cos(TAU) # a is 1.0
+ a = cos(PI) # a is -1.0
[/codeblock]
</description>
</method>
@@ -247,8 +244,7 @@
<description>
Returns the hyperbolic cosine of [code]s[/code] in radians.
[codeblock]
- # Prints 1.543081
- print(cosh(1))
+ print(cosh(1)) # Prints 1.543081
[/codeblock]
</description>
</method>
@@ -273,8 +269,7 @@
<description>
Returns the result of [code]value[/code] decreased by [code]step[/code] * [code]amount[/code].
[codeblock]
- # a = 59
- a = dectime(60, 10, 0.1))
+ a = dectime(60, 10, 0.1)) # a is 59.0
[/codeblock]
</description>
</method>
@@ -286,8 +281,7 @@
<description>
Converts an angle expressed in degrees to radians.
[codeblock]
- # r is 3.141593
- r = deg2rad(180)
+ r = deg2rad(180) # r is 3.141593
[/codeblock]
</description>
</method>
@@ -297,7 +291,7 @@
<argument index="0" name="dict" type="Dictionary">
</argument>
<description>
- Converts a previously converted instance to a dictionary, back into an instance. Useful for deserializing.
+ Converts a dictionary (previously created with [method inst2dict]) back to an instance. Useful for deserializing.
</description>
</method>
<method name="ease">
@@ -333,12 +327,12 @@
<description>
Rounds [code]s[/code] downward (towards negative infinity), returning the largest whole number that is not more than [code]s[/code].
[codeblock]
- # a is 2.0
- a = floor(2.99)
- # a is -3.0
- a = floor(-2.99)
+ a = floor(2.45) # a is 2.0
+ a = floor(2.99) # a is 2.0
+ a = floor(-2.99) # a is -3.0
[/codeblock]
- [b]Note:[/b] This method returns a float. If you need an integer, you can use [code]int(s)[/code] directly.
+ See also [method ceil], [method round], [method stepify], and [int].
+ [b]Note:[/b] This method returns a float. If you need an integer and [code]s[/code] is a non-negative number, you can use [code]int(s)[/code] directly.
</description>
</method>
<method name="fmod">
@@ -351,8 +345,7 @@
<description>
Returns the floating-point remainder of [code]a/b[/code], keeping the sign of [code]a[/code].
[codeblock]
- # Remainder is 1.5
- var remainder = fmod(7, 5.5)
+ r = fmod(7, 5.5) # r is 1.5
[/codeblock]
For the integer remainder operation, use the % operator.
</description>
@@ -367,42 +360,19 @@
<description>
Returns the floating-point modulus of [code]a/b[/code] that wraps equally in positive and negative.
[codeblock]
- var i = -6
- while i &lt; 5:
- prints(i, fposmod(i, 3))
- i += 1
+ for i in 7:
+ var x = 0.5 * i - 1.5
+ print("%4.1f %4.1f %4.1f" % [x, fmod(x, 1.5), fposmod(x, 1.5)])
[/codeblock]
Produces:
[codeblock]
- -6 0
- -5 1
- -4 2
- -3 0
- -2 1
- -1 2
- 0 0
- 1 1
- 2 2
- 3 0
- 4 1
- [/codeblock]
- </description>
- </method>
- <method name="funcref">
- <return type="FuncRef">
- </return>
- <argument index="0" name="instance" type="Object">
- </argument>
- <argument index="1" name="funcname" type="String">
- </argument>
- <description>
- Returns a reference to the specified function [code]funcname[/code] in the [code]instance[/code] node. As functions aren't first-class objects in GDscript, use [code]funcref[/code] to store a [FuncRef] in a variable and call it later.
- [codeblock]
- func foo():
- return("bar")
-
- a = funcref(self, "foo")
- print(a.call_func()) # Prints bar
+ -1.5 -0.0 0.0
+ -1.0 -1.0 0.5
+ -0.5 -0.5 1.0
+ 0.0 0.0 0.0
+ 0.5 0.5 0.5
+ 1.0 1.0 1.0
+ 1.5 0.0 0.0
[/codeblock]
</description>
</method>
@@ -630,6 +600,7 @@
var main = load("res://main.tscn") # main will contain a PackedScene resource.
[/codeblock]
[b]Important:[/b] The path must be absolute, a local path will just return [code]null[/code].
+ This method is a simplified version of [method ResourceLoader.load], which can be used for more advanced scenarios.
</description>
</method>
<method name="log">
@@ -769,24 +740,18 @@
<description>
Returns the integer modulus of [code]a/b[/code] that wraps equally in positive and negative.
[codeblock]
- var i = -6
- while i &lt; 5:
- prints(i, posmod(i, 3))
- i += 1
+ for i in range(-3, 4):
+ print("%2d %2d %2d" % [i, i % 3, posmod(i, 3)])
[/codeblock]
Produces:
[codeblock]
- -6 0
- -5 1
- -4 2
- -3 0
- -2 1
- -1 2
- 0 0
- 1 1
- 2 2
- 3 0
- 4 1
+ -3 0 0
+ -2 -2 1
+ -1 -1 2
+ 0 0 0
+ 1 1 1
+ 2 2 2
+ 3 0 0
[/codeblock]
</description>
</method>
@@ -800,7 +765,7 @@
<description>
Returns the result of [code]x[/code] raised to the power of [code]y[/code].
[codeblock]
- pow(2, 5) # Returns 32
+ pow(2, 5) # Returns 32.0
[/codeblock]
</description>
</method>
@@ -825,8 +790,9 @@
Converts one or more arguments to strings in the best way possible and prints them to the console.
[codeblock]
a = [1, 2, 3]
- print("a", "b", a) # Prints ab[1, 2, 3]
+ print("a", "=", a) # Prints a=[1, 2, 3]
[/codeblock]
+ [b]Note:[/b] Consider using [method push_error] and [method push_warning] to print error and warning messages instead of [method print]. This distinguishes them from print messages used for debugging purposes, while also displaying a stack trace when an error or warning is printed.
</description>
</method>
<method name="print_debug" qualifiers="vararg">
@@ -900,6 +866,7 @@
[codeblock]
push_error("test error") # Prints "test error" to debugger and terminal as error call
[/codeblock]
+ [b]Note:[/b] Errors printed this way will not pause project execution. To print an error message and pause project execution in debug builds, use [code]assert(false, "test error")[/code] instead.
</description>
</method>
<method name="push_warning">
@@ -922,21 +889,7 @@
<description>
Converts an angle expressed in radians to degrees.
[codeblock]
- rad2deg(0.523599) # Returns 30
- [/codeblock]
- </description>
- </method>
- <method name="rand_range">
- <return type="float">
- </return>
- <argument index="0" name="from" type="float">
- </argument>
- <argument index="1" name="to" type="float">
- </argument>
- <description>
- Random range, any floating point value between [code]from[/code] and [code]to[/code].
- [codeblock]
- prints(rand_range(0, 1), rand_range(0, 1)) # Prints e.g. 0.135591 0.405263
+ rad2deg(0.523599) # Returns 30.0
[/codeblock]
</description>
</method>
@@ -959,6 +912,20 @@
[/codeblock]
</description>
</method>
+ <method name="randf_range">
+ <return type="float">
+ </return>
+ <argument index="0" name="from" type="float">
+ </argument>
+ <argument index="1" name="to" type="float">
+ </argument>
+ <description>
+ Random range, any floating point value between [code]from[/code] and [code]to[/code].
+ [codeblock]
+ prints(randf_range(-10, 10), randf_range(-10, 10)) # Prints e.g. -3.844535 7.45315
+ [/codeblock]
+ </description>
+ </method>
<method name="randi">
<return type="int">
</return>
@@ -972,6 +939,21 @@
[/codeblock]
</description>
</method>
+ <method name="randi_range">
+ <return type="int">
+ </return>
+ <argument index="0" name="from" type="int">
+ </argument>
+ <argument index="1" name="to" type="int">
+ </argument>
+ <description>
+ Random range, any 32-bit integer value between [code]from[/code] and [code]to[/code] (inclusive). If [code]to[/code] is lesser than [code]from[/code] they are swapped.
+ [codeblock]
+ print(randi_range(0, 1)) # Prints 0 or 1
+ print(randi_range(-10, 1000)) # Prints any number from -10 to 1000
+ [/codeblock]
+ </description>
+ </method>
<method name="randomize">
<return type="void">
</return>
@@ -989,27 +971,15 @@
<description>
Returns an array with the given range. Range can be 1 argument N (0 to N-1), two arguments (initial, final-1) or three arguments (initial, final-1, increment).
[codeblock]
- for i in range(4):
- print(i)
- for i in range(2, 5):
- print(i)
- for i in range(0, 6, 2):
- print(i)
+ print(range(4))
+ print(range(2, 5))
+ print(range(0, 6, 2))
[/codeblock]
Output:
[codeblock]
- 0
- 1
- 2
- 3
-
- 2
- 3
- 4
-
- 0
- 2
- 4
+ [0, 1, 2, 3]
+ [2, 3, 4]
+ [0, 2, 4]
[/codeblock]
</description>
</method>
@@ -1041,8 +1011,11 @@
<description>
Rounds [code]s[/code] to the nearest whole number, with halfway cases rounded away from zero.
[codeblock]
- round(2.6) # Returns 3
+ a = round(2.49) # a is 2.0
+ a = round(2.5) # a is 3.0
+ a = round(2.51) # a is 3.0
[/codeblock]
+ See also [method floor], [method ceil], [method stepify], and [int].
</description>
</method>
<method name="seed">
@@ -1112,9 +1085,9 @@
This S-shaped curve is the cubic Hermite interpolator, given by [code]f(s) = 3*s^2 - 2*s^3[/code].
[codeblock]
smoothstep(0, 2, -5.0) # Returns 0.0
- smoothstep(0, 2, 0.5) # Returns 0.15625
- smoothstep(0, 2, 1.0) # Returns 0.5
- smoothstep(0, 2, 2.0) # Returns 1.0
+ smoothstep(0, 2, 0.5) # Returns 0.15625
+ smoothstep(0, 2, 1.0) # Returns 0.5
+ smoothstep(0, 2, 2.0) # Returns 1.0
[/codeblock]
</description>
</method>
@@ -1139,12 +1112,9 @@
<description>
Returns the position of the first non-zero digit, after the decimal point. Note that the maximum return value is 10, which is a design decision in the implementation.
[codeblock]
- # n is 0
- n = step_decimals(5)
- # n is 4
- n = step_decimals(1.0005)
- # n is 9
- n = step_decimals(0.000000005)
+ n = step_decimals(5) # n is 0
+ n = step_decimals(1.0005) # n is 4
+ n = step_decimals(0.000000005) # n is 9
[/codeblock]
</description>
</method>
@@ -1158,9 +1128,10 @@
<description>
Snaps float value [code]s[/code] to a given [code]step[/code]. This can also be used to round a floating point number to an arbitrary number of decimals.
[codeblock]
- stepify(100, 32) # Returns 96
+ stepify(100, 32) # Returns 96.0
stepify(3.14159, 0.01) # Returns 3.14
[/codeblock]
+ See also [method ceil], [method floor], [method round], and [int].
</description>
</method>
<method name="str" qualifiers="vararg">
@@ -1210,8 +1181,8 @@
<description>
Returns the hyperbolic tangent of [code]s[/code].
[codeblock]
- a = log(2.0) # Returns 0.693147
- tanh(a) # Returns 0.6
+ a = log(2.0) # a is 0.693147
+ b = tanh(a) # b is 0.6
[/codeblock]
</description>
</method>
diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml
index 62ccb93901..631a102130 100644
--- a/modules/gdscript/doc_classes/GDScript.xml
+++ b/modules/gdscript/doc_classes/GDScript.xml
@@ -8,7 +8,7 @@
[method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes.
</description>
<tutorials>
- <link>https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link>
+ <link title="GDScript tutorial index">https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link>
</tutorials>
<methods>
<method name="get_as_byte_code" qualifiers="const">
diff --git a/modules/gdscript/doc_classes/GDScriptFunctionState.xml b/modules/gdscript/doc_classes/GDScriptFunctionState.xml
deleted file mode 100644
index 5e369b32d9..0000000000
--- a/modules/gdscript/doc_classes/GDScriptFunctionState.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<class name="GDScriptFunctionState" inherits="Reference" version="4.0">
- <brief_description>
- State of a function call after yielding.
- </brief_description>
- <description>
- FIXME: Outdated docs as of GDScript rewrite in 4.0.
- Calling [code]yield[/code] within a function will cause that function to yield and return its current state as an object of this type. The yielded function call can then be resumed later by calling [method resume] on this state object.
- </description>
- <tutorials>
- </tutorials>
- <methods>
- <method name="is_valid" qualifiers="const">
- <return type="bool">
- </return>
- <argument index="0" name="extended_check" type="bool" default="false">
- </argument>
- <description>
- Check whether the function call may be resumed. This is not the case if the function state was already resumed.
- If [code]extended_check[/code] is enabled, it also checks if the associated script and object still exist. The extended check is done in debug mode as part of [method GDScriptFunctionState.resume], but you can use this if you know you may be trying to resume without knowing for sure the object and/or script have survived up to that point.
- </description>
- </method>
- <method name="resume">
- <return type="Variant">
- </return>
- <argument index="0" name="arg" type="Variant" default="null">
- </argument>
- <description>
- Resume execution of the yielded function call.
- If handed an argument, return the argument from the [code]yield[/code] call in the yielded function call. You can pass e.g. an [Array] to hand multiple arguments.
- This function returns what the resumed function call returns, possibly another function state if yielded again.
- </description>
- </method>
- </methods>
- <signals>
- <signal name="completed">
- <argument index="0" name="result" type="Variant">
- </argument>
- <description>
- </description>
- </signal>
- </signals>
- <constants>
- </constants>
-</class>
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index ae1f2893f1..7f7410a92c 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -33,15 +33,15 @@
#include "../gdscript_tokenizer.h"
#include "editor/editor_settings.h"
-static bool _is_char(CharType c) {
+static bool _is_char(char32_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}
-static bool _is_hex_symbol(CharType c) {
+static bool _is_hex_symbol(char32_t c) {
return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
}
-static bool _is_bin_symbol(CharType c) {
+static bool _is_bin_symbol(char32_t c) {
return (c == '0' || c == '1');
}
@@ -73,6 +73,13 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
color_region_cache[p_line] = -1;
int in_region = -1;
if (p_line != 0) {
+ int prev_region_line = p_line - 1;
+ while (prev_region_line > 0 && !color_region_cache.has(prev_region_line)) {
+ prev_region_line--;
+ }
+ for (int i = prev_region_line; i < p_line - 1; i++) {
+ get_line_syntax_highlighting(i);
+ }
if (!color_region_cache.has(p_line - 1)) {
get_line_syntax_highlighting(p_line - 1);
}
@@ -119,7 +126,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
/* search the line */
bool match = true;
- const CharType *start_key = color_regions[c].start_key.c_str();
+ const char32_t *start_key = color_regions[c].start_key.get_data();
for (int k = 0; k < start_key_length; k++) {
if (start_key[k] != str[from + k]) {
match = false;
@@ -153,18 +160,16 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
/* if we are in one find the end key */
if (in_region != -1) {
- /* check there is enough room */
- int chars_left = line_length - from;
- int end_key_length = color_regions[in_region].end_key.length();
- if (chars_left < end_key_length) {
- continue;
- }
-
/* search the line */
int region_end_index = -1;
- const CharType *end_key = color_regions[in_region].start_key.c_str();
+ int end_key_length = color_regions[in_region].end_key.length();
+ const char32_t *end_key = color_regions[in_region].end_key.get_data();
for (; from < line_length; from++) {
- if (!is_a_symbol) {
+ if (line_length - from < end_key_length) {
+ break;
+ }
+
+ if (!is_symbol(str[from])) {
continue;
}
@@ -173,9 +178,10 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
continue;
}
+ region_end_index = from;
for (int k = 0; k < end_key_length; k++) {
- if (end_key[k] == str[from + k]) {
- region_end_index = from;
+ if (end_key[k] != str[from + k]) {
+ region_end_index = -1;
break;
}
}
@@ -192,7 +198,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
previous_type = REGION;
previous_text = "";
previous_column = j;
- j = from;
+ j = from + (end_key_length - 1);
if (region_end_index == -1) {
color_region_cache[p_line] = in_region;
}
@@ -572,8 +578,12 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons
}
}
+ int at = 0;
for (int i = 0; i < color_regions.size(); i++) {
ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "color region with start key '" + p_start_key + "' already exists.");
+ if (p_start_key.length() < color_regions[i].start_key.length()) {
+ at++;
+ }
}
ColorRegion color_region;
@@ -581,7 +591,8 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons
color_region.start_key = p_start_key;
color_region.end_key = p_end_key;
color_region.line_only = p_line_only;
- color_regions.push_back(color_region);
+ color_regions.insert(at, color_region);
+ clear_highlighting_cache();
}
Ref<EditorSyntaxHighlighter> GDScriptSyntaxHighlighter::_create() const {
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index 49357f3d2e..e38647eaab 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -42,7 +42,7 @@ private:
Color color;
String start_key;
String end_key;
- bool line_only;
+ bool line_only = false;
};
Vector<ColorRegion> color_regions;
Map<int, int> color_region_cache;
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
index 6d454e43f2..944ed859f5 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -37,9 +37,11 @@ void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<Strin
GDScriptLanguage::get_singleton()->get_recognized_extensions(r_extensions);
}
-Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_extracted_strings) {
- // Parse and match all GDScript function API that involves translation string.
- // E.g get_node("Label").text = "something", var test = tr("something"), "something" will be matched and collected.
+Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) {
+ // Extract all translatable strings using the parsed tree from GDSriptParser.
+ // The strategy is to find all ExpressionNode and AssignmentNode from the tree and extract strings if relevant, i.e
+ // Search strings in ExpressionNode -> CallNode -> tr(), set_text(), set_placeholder() etc.
+ // Search strings in AssignmentNode -> text = "__", hint_tooltip = "__" etc.
Error err;
RES loaded_res = ResourceLoader::load(p_path, "", false, &err);
@@ -48,108 +50,302 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
return err;
}
+ ids = r_ids;
+ ids_ctx_plural = r_ids_ctx_plural;
Ref<GDScript> gdscript = loaded_res;
String source_code = gdscript->get_source_code();
- Vector<String> parsed_strings;
-
- // Search translation strings with RegEx.
- regex.clear();
- regex.compile(String("|").join(patterns));
- Array results = regex.search_all(source_code);
- _get_captured_strings(results, &parsed_strings);
-
- // Special handling for FileDialog.
- Vector<String> temp;
- _parse_file_dialog(source_code, &temp);
- parsed_strings.append_array(temp);
-
- // Filter out / and +
- String filter = "(?:\\\\\\n|\"[\\s\\\\]*\\+\\s*\")";
- regex.clear();
- regex.compile(filter);
- for (int i = 0; i < parsed_strings.size(); i++) {
- parsed_strings.set(i, regex.sub(parsed_strings[i], "", true));
+
+ GDScriptParser parser;
+ err = parser.parse(source_code, p_path, false);
+ if (err != OK) {
+ ERR_PRINT("Failed to parse with GDScript with GDScriptParser.");
+ return err;
}
- r_extracted_strings->append_array(parsed_strings);
+ // Traverse through the parsed tree from GDScriptParser.
+ GDScriptParser::ClassNode *c = parser.get_tree();
+ _traverse_class(c);
return OK;
}
-void GDScriptEditorTranslationParserPlugin::_parse_file_dialog(const String &p_source_code, Vector<String> *r_output) {
- // FileDialog API has the form .filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]).
- // First filter: Get "*.png ; PNG Images", "*.gd ; GDScript Files" from PackedStringArray.
- regex.clear();
- regex.compile(String("|").join(file_dialog_patterns));
- Array results = regex.search_all(p_source_code);
-
- Vector<String> temp;
- _get_captured_strings(results, &temp);
- String captured_strings = String(",").join(temp);
-
- // Second filter: Get the texts after semicolon from "*.png ; PNG Images","*.gd ; GDScript Files".
- String second_filter = "\"[^;]+;" + text + "\"";
- regex.clear();
- regex.compile(second_filter);
- results = regex.search_all(captured_strings);
- _get_captured_strings(results, r_output);
- for (int i = 0; i < r_output->size(); i++) {
- r_output->set(i, r_output->get(i).strip_edges());
+void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) {
+ for (int i = 0; i < p_class->members.size(); i++) {
+ const GDScriptParser::ClassNode::Member &m = p_class->members[i];
+ // There are 7 types of Member, but only class, function and variable can contain translatable strings.
+ switch (m.type) {
+ case GDScriptParser::ClassNode::Member::CLASS:
+ _traverse_class(m.m_class);
+ break;
+ case GDScriptParser::ClassNode::Member::FUNCTION:
+ _traverse_function(m.function);
+ break;
+ case GDScriptParser::ClassNode::Member::VARIABLE:
+ _read_variable(m.variable);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void GDScriptEditorTranslationParserPlugin::_traverse_function(const GDScriptParser::FunctionNode *p_func) {
+ _traverse_block(p_func->body);
+}
+
+void GDScriptEditorTranslationParserPlugin::_read_variable(const GDScriptParser::VariableNode *p_var) {
+ _assess_expression(p_var->initializer);
+}
+
+void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser::SuiteNode *p_suite) {
+ if (!p_suite) {
+ return;
+ }
+
+ const Vector<GDScriptParser::Node *> &statements = p_suite->statements;
+ for (int i = 0; i < statements.size(); i++) {
+ GDScriptParser::Node *statement = statements[i];
+
+ // Statements with Node type constant, break, continue, pass, breakpoint are skipped because they can't contain translatable strings.
+ switch (statement->type) {
+ case GDScriptParser::Node::VARIABLE:
+ _assess_expression(static_cast<GDScriptParser::VariableNode *>(statement)->initializer);
+ break;
+ case GDScriptParser::Node::IF: {
+ GDScriptParser::IfNode *if_node = static_cast<GDScriptParser::IfNode *>(statement);
+ _assess_expression(if_node->condition);
+ //FIXME : if the elif logic is changed in GDScriptParser, then this probably will have to change as well. See GDScriptParser::TreePrinter::print_if().
+ _traverse_block(if_node->true_block);
+ _traverse_block(if_node->false_block);
+ break;
+ }
+ case GDScriptParser::Node::FOR: {
+ GDScriptParser::ForNode *for_node = static_cast<GDScriptParser::ForNode *>(statement);
+ _assess_expression(for_node->list);
+ _traverse_block(for_node->loop);
+ break;
+ }
+ case GDScriptParser::Node::WHILE: {
+ GDScriptParser::WhileNode *while_node = static_cast<GDScriptParser::WhileNode *>(statement);
+ _assess_expression(while_node->condition);
+ _traverse_block(while_node->loop);
+ break;
+ }
+ case GDScriptParser::Node::MATCH: {
+ GDScriptParser::MatchNode *match_node = static_cast<GDScriptParser::MatchNode *>(statement);
+ _assess_expression(match_node->test);
+ for (int j = 0; j < match_node->branches.size(); j++) {
+ _traverse_block(match_node->branches[j]->block);
+ }
+ break;
+ }
+ case GDScriptParser::Node::RETURN:
+ _assess_expression(static_cast<GDScriptParser::ReturnNode *>(statement)->return_value);
+ break;
+ case GDScriptParser::Node::ASSERT:
+ _assess_expression((static_cast<GDScriptParser::AssertNode *>(statement))->condition);
+ break;
+ case GDScriptParser::Node::ASSIGNMENT:
+ _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(statement));
+ break;
+ default:
+ if (statement->is_expression()) {
+ _assess_expression(static_cast<GDScriptParser::ExpressionNode *>(statement));
+ }
+ break;
+ }
+ }
+}
+
+void GDScriptEditorTranslationParserPlugin::_assess_expression(GDScriptParser::ExpressionNode *p_expression) {
+ // Explore all ExpressionNodes to find CallNodes which contain translation strings, such as tr(), set_text() etc.
+ // tr() can be embedded quite deep within multiple ExpressionNodes so need to dig down to search through all ExpressionNodes.
+ if (!p_expression) {
+ return;
+ }
+
+ // ExpressionNode of type await, cast, get_node, identifier, literal, preload, self, subscript, unary are ignored as they can't be CallNode
+ // containing translation strings.
+ switch (p_expression->type) {
+ case GDScriptParser::Node::ARRAY: {
+ GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(p_expression);
+ for (int i = 0; i < array_node->elements.size(); i++) {
+ _assess_expression(array_node->elements[i]);
+ }
+ break;
+ }
+ case GDScriptParser::Node::ASSIGNMENT:
+ _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(p_expression));
+ break;
+ case GDScriptParser::Node::BINARY_OPERATOR: {
+ GDScriptParser::BinaryOpNode *binary_op_node = static_cast<GDScriptParser::BinaryOpNode *>(p_expression);
+ _assess_expression(binary_op_node->left_operand);
+ _assess_expression(binary_op_node->right_operand);
+ break;
+ }
+ case GDScriptParser::Node::CALL: {
+ GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_expression);
+ _extract_from_call(call_node);
+ for (int i = 0; i < call_node->arguments.size(); i++) {
+ _assess_expression(call_node->arguments[i]);
+ }
+ } break;
+ case GDScriptParser::Node::DICTIONARY: {
+ GDScriptParser::DictionaryNode *dict_node = static_cast<GDScriptParser::DictionaryNode *>(p_expression);
+ for (int i = 0; i < dict_node->elements.size(); i++) {
+ _assess_expression(dict_node->elements[i].key);
+ _assess_expression(dict_node->elements[i].value);
+ }
+ break;
+ }
+ case GDScriptParser::Node::TERNARY_OPERATOR: {
+ GDScriptParser::TernaryOpNode *ternary_op_node = static_cast<GDScriptParser::TernaryOpNode *>(p_expression);
+ _assess_expression(ternary_op_node->condition);
+ _assess_expression(ternary_op_node->true_expr);
+ _assess_expression(ternary_op_node->false_expr);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void GDScriptEditorTranslationParserPlugin::_assess_assignment(GDScriptParser::AssignmentNode *p_assignment) {
+ // Extract the translatable strings coming from assignments. For example, get_node("Label").text = "____"
+
+ StringName assignee_name;
+ if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
+ assignee_name = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name;
+ } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) {
+ assignee_name = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->attribute->name;
+ }
+
+ if (assignment_patterns.has(assignee_name) && p_assignment->assigned_value->type == GDScriptParser::Node::LITERAL) {
+ // If the assignment is towards one of the extract patterns (text, hint_tooltip etc.), and the value is a string literal, we collect the string.
+ ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_assignment->assigned_value)->value);
+ } else if (assignee_name == fd_filters && p_assignment->assigned_value->type == GDScriptParser::Node::CALL) {
+ // FileDialog.filters accepts assignment in the form of PackedStringArray. For example,
+ // get_node("FileDialog").filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]).
+
+ GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value);
+ if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
+ GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]);
+
+ // Extract the name in "extension ; name" of PackedStringArray.
+ for (int i = 0; i < array_node->elements.size(); i++) {
+ _extract_fd_literals(array_node->elements[i]);
+ }
+ }
+ } else {
+ // If the assignee is not in extract patterns or the assigned_value is not Literal type, try to see if the assigned_value contains tr().
+ _assess_expression(p_assignment->assigned_value);
}
}
-void GDScriptEditorTranslationParserPlugin::_get_captured_strings(const Array &p_results, Vector<String> *r_output) {
- Ref<RegExMatch> result;
- for (int i = 0; i < p_results.size(); i++) {
- result = p_results[i];
- for (int j = 0; j < result->get_group_count(); j++) {
- String s = result->get_string(j + 1);
- // Prevent reading text with only spaces.
- if (!s.strip_edges().empty()) {
- r_output->push_back(s);
+void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::CallNode *p_call) {
+ // Extract the translatable strings coming from function calls. For example:
+ // tr("___"), get_node("Label").set_text("____"), get_node("LineEdit").set_placeholder("____").
+
+ StringName function_name = p_call->function_name;
+
+ // Variables for extracting tr() and tr_n().
+ Vector<String> id_ctx_plural;
+ id_ctx_plural.resize(3);
+ bool extract_id_ctx_plural = true;
+
+ if (function_name == tr_func) {
+ // Extract from tr(id, ctx).
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ if (p_call->arguments[i]->type == GDScriptParser::Node::LITERAL) {
+ id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[i])->value;
+ } else {
+ // Avoid adding something like tr("Flying dragon", var_context_level_1). We want to extract both id and context together.
+ extract_id_ctx_plural = false;
+ }
+ }
+ if (extract_id_ctx_plural) {
+ ids_ctx_plural->push_back(id_ctx_plural);
+ }
+ } else if (function_name == trn_func) {
+ // Extract from tr_n(id, plural, n, ctx).
+ Vector<int> indices;
+ indices.push_back(0);
+ indices.push_back(3);
+ indices.push_back(1);
+ for (int i = 0; i < indices.size(); i++) {
+ if (indices[i] >= p_call->arguments.size()) {
+ continue;
+ }
+
+ if (p_call->arguments[indices[i]]->type == GDScriptParser::Node::LITERAL) {
+ id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[indices[i]])->value;
+ } else {
+ extract_id_ctx_plural = false;
+ }
+ }
+ if (extract_id_ctx_plural) {
+ ids_ctx_plural->push_back(id_ctx_plural);
+ }
+ } else if (first_arg_patterns.has(function_name)) {
+ // Extracting argument with only string literals. In other words, not extracting something like set_text("hello " + some_var).
+ if (p_call->arguments[0]->type == GDScriptParser::Node::LITERAL) {
+ ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[0])->value);
+ }
+ } else if (second_arg_patterns.has(function_name)) {
+ if (p_call->arguments[1]->type == GDScriptParser::Node::LITERAL) {
+ ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[1])->value);
+ }
+ } else if (function_name == fd_add_filter) {
+ // Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images").
+ _extract_fd_literals(p_call->arguments[0]);
+
+ } else if (function_name == fd_set_filter && p_call->arguments[0]->type == GDScriptParser::Node::CALL) {
+ // FileDialog.set_filters() accepts assignment in the form of PackedStringArray. For example,
+ // get_node("FileDialog").set_filters( PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])).
+
+ GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_call->arguments[0]);
+ if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
+ GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]);
+ for (int i = 0; i < array_node->elements.size(); i++) {
+ _extract_fd_literals(array_node->elements[i]);
}
}
}
}
+void GDScriptEditorTranslationParserPlugin::_extract_fd_literals(GDScriptParser::ExpressionNode *p_expression) {
+ // Extract the name in "extension ; name".
+
+ if (p_expression->type == GDScriptParser::Node::LITERAL) {
+ String arg_val = String(static_cast<GDScriptParser::LiteralNode *>(p_expression)->value);
+ PackedStringArray arr = arg_val.split(";", true);
+ if (arr.size() != 2) {
+ ERR_PRINT("Argument for setting FileDialog has bad format.");
+ return;
+ }
+ ids->push_back(arr[1].strip_edges());
+ }
+}
+
GDScriptEditorTranslationParserPlugin::GDScriptEditorTranslationParserPlugin() {
- // Regex search pattern templates.
- // The extra complication in the regex pattern is to ensure that the matching works when users write over multiple lines, use tabs etc.
- const String dot = "\\.[\\s\\\\]*";
- const String str_assign_template = "[\\s\\\\]*=[\\s\\\\]*\"" + text + "\"";
- const String first_arg_template = "[\\s\\\\]*\\([\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)";
- const String second_arg_template = "[\\s\\\\]*\\([\\s\\S]+?,[\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)";
-
- // Common patterns.
- patterns.push_back("tr" + first_arg_template);
- patterns.push_back(dot + "text" + str_assign_template);
- patterns.push_back(dot + "placeholder_text" + str_assign_template);
- patterns.push_back(dot + "hint_tooltip" + str_assign_template);
- patterns.push_back(dot + "set_text" + first_arg_template);
- patterns.push_back(dot + "set_tooltip" + first_arg_template);
- patterns.push_back(dot + "set_placeholder" + first_arg_template);
-
- // Tabs and TabContainer API.
- patterns.push_back(dot + "set_tab_title" + second_arg_template);
- patterns.push_back(dot + "add_tab" + first_arg_template);
-
- // PopupMenu API.
- patterns.push_back(dot + "add_check_item" + first_arg_template);
- patterns.push_back(dot + "add_icon_check_item" + second_arg_template);
- patterns.push_back(dot + "add_icon_item" + second_arg_template);
- patterns.push_back(dot + "add_icon_radio_check_item" + second_arg_template);
- patterns.push_back(dot + "add_item" + first_arg_template);
- patterns.push_back(dot + "add_multistate_item" + first_arg_template);
- patterns.push_back(dot + "add_radio_check_item" + first_arg_template);
- patterns.push_back(dot + "add_separator" + first_arg_template);
- patterns.push_back(dot + "add_submenu_item" + first_arg_template);
- patterns.push_back(dot + "set_item_text" + second_arg_template);
- //patterns.push_back(dot + "set_item_tooltip" + second_arg_template); //no tr() behind this function. might be bug.
-
- // FileDialog API - special case.
- const String fd_text = "((?:[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*\"[\\s\\\\]*,?)*)";
- const String packed_string_array = "[\\s\\\\]*PackedStringArray[\\s\\\\]*\\([\\s\\\\]*\\[" + fd_text + "\\][\\s\\\\]*\\)";
- file_dialog_patterns.push_back(dot + "add_filter[\\s\\\\]*\\(" + fd_text + "[\\s\\\\]*\\)");
- file_dialog_patterns.push_back(dot + "filters[\\s\\\\]*=" + packed_string_array);
- file_dialog_patterns.push_back(dot + "set_filters[\\s\\\\]*\\(" + packed_string_array + "[\\s\\\\]*\\)");
+ assignment_patterns.insert("text");
+ assignment_patterns.insert("placeholder_text");
+ assignment_patterns.insert("hint_tooltip");
+
+ first_arg_patterns.insert("set_text");
+ first_arg_patterns.insert("set_tooltip");
+ first_arg_patterns.insert("set_placeholder");
+ first_arg_patterns.insert("add_tab");
+ first_arg_patterns.insert("add_check_item");
+ first_arg_patterns.insert("add_item");
+ first_arg_patterns.insert("add_multistate_item");
+ first_arg_patterns.insert("add_radio_check_item");
+ first_arg_patterns.insert("add_separator");
+ first_arg_patterns.insert("add_submenu_item");
+
+ second_arg_patterns.insert("set_tab_title");
+ second_arg_patterns.insert("add_icon_check_item");
+ second_arg_patterns.insert("add_icon_item");
+ second_arg_patterns.insert("add_icon_radio_check_item");
+ second_arg_patterns.insert("set_item_text");
}
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
index 9fa4b69f01..d763df01f5 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
@@ -31,23 +31,40 @@
#ifndef GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
#define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
+#include "core/templates/set.h"
#include "editor/editor_translation_parser.h"
+#include "modules/gdscript/gdscript_parser.h"
#include "modules/regex/regex.h"
class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin {
GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin);
- // Regex and search patterns that are used to match translation strings.
- const String text = "((?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*)";
- RegEx regex;
- Vector<String> patterns;
- Vector<String> file_dialog_patterns;
+ Vector<String> *ids;
+ Vector<Vector<String>> *ids_ctx_plural;
- void _parse_file_dialog(const String &p_source_code, Vector<String> *r_output);
- void _get_captured_strings(const Array &p_results, Vector<String> *r_output);
+ // List of patterns used for extracting translation strings.
+ StringName tr_func = "tr";
+ StringName trn_func = "tr_n";
+ Set<StringName> assignment_patterns;
+ Set<StringName> first_arg_patterns;
+ Set<StringName> second_arg_patterns;
+ // FileDialog patterns.
+ StringName fd_add_filter = "add_filter";
+ StringName fd_set_filter = "set_filters";
+ StringName fd_filters = "filters";
+
+ void _traverse_class(const GDScriptParser::ClassNode *p_class);
+ void _traverse_function(const GDScriptParser::FunctionNode *p_func);
+ void _traverse_block(const GDScriptParser::SuiteNode *p_suite);
+
+ void _read_variable(const GDScriptParser::VariableNode *p_var);
+ void _assess_expression(GDScriptParser::ExpressionNode *p_expression);
+ void _assess_assignment(GDScriptParser::AssignmentNode *p_assignment);
+ void _extract_from_call(GDScriptParser::CallNode *p_call);
+ void _extract_fd_literals(GDScriptParser::ExpressionNode *p_expression);
public:
- virtual Error parse_file(const String &p_path, Vector<String> *r_extracted_strings) override;
+ virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override;
virtual void get_recognized_extensions(List<String> *r_extensions) const override;
GDScriptEditorTranslationParserPlugin();
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 9170255c02..4425b59d62 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -32,13 +32,13 @@
#include <stdint.h>
+#include "core/config/engine.h"
+#include "core/config/project_settings.h"
+#include "core/core_constants.h"
#include "core/core_string_names.h"
-#include "core/engine.h"
-#include "core/global_constants.h"
#include "core/io/file_access_encrypted.h"
#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"
@@ -231,7 +231,7 @@ void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) {
}
#endif
-void GDScript::get_script_method_list(List<MethodInfo> *p_list) const {
+void GDScript::_get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const {
const GDScript *current = this;
while (current) {
for (const Map<StringName, GDScriptFunction *>::Element *E = current->member_functions.front(); E; E = E->next()) {
@@ -239,18 +239,29 @@ void GDScript::get_script_method_list(List<MethodInfo> *p_list) const {
MethodInfo mi;
mi.name = E->key();
for (int i = 0; i < func->get_argument_count(); i++) {
- mi.arguments.push_back(func->get_argument_type(i));
+ PropertyInfo arginfo = func->get_argument_type(i);
+#ifdef TOOLS_ENABLED
+ arginfo.name = func->get_argument_name(i);
+#endif
+ mi.arguments.push_back(arginfo);
}
mi.return_val = func->get_return_type();
- p_list->push_back(mi);
+ r_list->push_back(mi);
+ }
+ if (!p_include_base) {
+ return;
}
current = current->_base;
}
}
-void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const {
+void GDScript::get_script_method_list(List<MethodInfo> *r_list) const {
+ _get_script_method_list(r_list, true);
+}
+
+void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const {
const GDScript *sptr = this;
List<PropertyInfo> props;
@@ -269,15 +280,22 @@ void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const {
for (int i = 0; i < msort.size(); i++) {
props.push_front(sptr->member_info[msort[i].name]);
}
+ if (!p_include_base) {
+ break;
+ }
sptr = sptr->_base;
}
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- p_list->push_back(E->get());
+ r_list->push_back(E->get());
}
}
+void GDScript::get_script_property_list(List<PropertyInfo> *r_list) const {
+ _get_script_property_list(r_list, true);
+}
+
bool GDScript::has_method(const StringName &p_method) const {
return member_functions.has(p_method);
}
@@ -383,6 +401,183 @@ void GDScript::_update_exports_values(Map<StringName, Variant> &values, List<Pro
propnames.push_back(E->get());
}
}
+
+void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) {
+ if (_owner) {
+ _owner->_add_doc(p_inner_class);
+ } else {
+ for (int i = 0; i < docs.size(); i++) {
+ if (docs[i].name == p_inner_class.name) {
+ docs.remove(i);
+ break;
+ }
+ }
+ docs.append(p_inner_class);
+ }
+}
+
+void GDScript::_clear_doc() {
+ docs.clear();
+ doc = DocData::ClassDoc();
+}
+
+void GDScript::_update_doc() {
+ _clear_doc();
+
+ doc.script_path = "\"" + get_path().get_slice("://", 1) + "\"";
+ if (!name.empty()) {
+ doc.name = name;
+ } else {
+ doc.name = doc.script_path;
+ }
+
+ if (_owner) {
+ doc.name = _owner->doc.name + "." + doc.name;
+ doc.script_path = doc.script_path + "." + doc.name;
+ }
+
+ doc.is_script_doc = true;
+
+ if (base.is_valid() && base->is_valid()) {
+ if (base->doc.name != String()) {
+ doc.inherits = base->doc.name;
+ } else {
+ doc.inherits = base->get_instance_base_type();
+ }
+ } else if (native.is_valid()) {
+ doc.inherits = native->get_name();
+ }
+
+ doc.brief_description = doc_brief_description;
+ doc.description = doc_description;
+ doc.tutorials = doc_tutorials;
+
+ for (Map<String, DocData::EnumDoc>::Element *E = doc_enums.front(); E; E = E->next()) {
+ if (E->value().description != "") {
+ doc.enums[E->key()] = E->value().description;
+ }
+ }
+
+ List<MethodInfo> methods;
+ _get_script_method_list(&methods, false);
+ for (int i = 0; i < methods.size(); i++) {
+ // Ignore internal methods.
+ if (methods[i].name[0] == '@') {
+ continue;
+ }
+
+ DocData::MethodDoc method_doc;
+ const String &class_name = methods[i].name;
+ if (member_functions.has(class_name)) {
+ GDScriptFunction *fn = member_functions[class_name];
+
+ // Change class name if return type is script reference.
+ GDScriptDataType return_type = fn->get_return_type();
+ if (return_type.kind == GDScriptDataType::GDSCRIPT) {
+ methods[i].return_val.class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(return_type.script_type));
+ }
+
+ // Change class name if argumetn is script reference.
+ for (int j = 0; j < fn->get_argument_count(); j++) {
+ GDScriptDataType arg_type = fn->get_argument_type(j);
+ if (arg_type.kind == GDScriptDataType::GDSCRIPT) {
+ methods[i].arguments[j].class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(arg_type.script_type));
+ }
+ }
+ }
+ if (doc_functions.has(methods[i].name)) {
+ DocData::method_doc_from_methodinfo(method_doc, methods[i], doc_functions[methods[i].name]);
+ } else {
+ DocData::method_doc_from_methodinfo(method_doc, methods[i], String());
+ }
+ doc.methods.push_back(method_doc);
+ }
+
+ List<PropertyInfo> props;
+ _get_script_property_list(&props, false);
+ for (int i = 0; i < props.size(); i++) {
+ ScriptMemberInfo scr_member_info;
+ scr_member_info.propinfo = props[i];
+ scr_member_info.propinfo.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ if (member_indices.has(props[i].name)) {
+ const MemberInfo &mi = member_indices[props[i].name];
+ scr_member_info.setter = mi.setter;
+ scr_member_info.getter = mi.getter;
+ if (mi.data_type.kind == GDScriptDataType::GDSCRIPT) {
+ scr_member_info.propinfo.class_name = _get_gdscript_reference_class_name(
+ Object::cast_to<GDScript>(mi.data_type.script_type));
+ }
+ }
+ if (member_default_values.has(props[i].name)) {
+ scr_member_info.has_default_value = true;
+ scr_member_info.default_value = member_default_values[props[i].name];
+ }
+ if (doc_variables.has(props[i].name)) {
+ scr_member_info.doc_string = doc_variables[props[i].name];
+ }
+
+ DocData::PropertyDoc prop_doc;
+ DocData::property_doc_from_scriptmemberinfo(prop_doc, scr_member_info);
+ doc.properties.push_back(prop_doc);
+ }
+
+ List<MethodInfo> signals;
+ _get_script_signal_list(&signals, false);
+ for (int i = 0; i < signals.size(); i++) {
+ DocData::MethodDoc signal_doc;
+ if (doc_signals.has(signals[i].name)) {
+ DocData::signal_doc_from_methodinfo(signal_doc, signals[i], signals[i].name);
+ } else {
+ DocData::signal_doc_from_methodinfo(signal_doc, signals[i], String());
+ }
+ doc.signals.push_back(signal_doc);
+ }
+
+ for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) {
+ if (subclasses.has(E->key())) {
+ continue;
+ }
+
+ // Enums.
+ bool is_enum = false;
+ if (E->value().get_type() == Variant::DICTIONARY) {
+ if (doc_enums.has(E->key())) {
+ is_enum = true;
+ for (int i = 0; i < doc_enums[E->key()].values.size(); i++) {
+ doc_enums[E->key()].values.write[i].enumeration = E->key();
+ doc.constants.push_back(doc_enums[E->key()].values[i]);
+ }
+ }
+ }
+ if (!is_enum && doc_enums.has("@unnamed_enums")) {
+ for (int i = 0; i < doc_enums["@unnamed_enums"].values.size(); i++) {
+ if (E->key() == doc_enums["@unnamed_enums"].values[i].name) {
+ is_enum = true;
+ DocData::ConstantDoc constant_doc;
+ constant_doc.enumeration = "@unnamed_enums";
+ DocData::constant_doc_from_variant(constant_doc, E->key(), E->value(), doc_enums["@unnamed_enums"].values[i].description);
+ doc.constants.push_back(constant_doc);
+ break;
+ }
+ }
+ }
+ if (!is_enum) {
+ DocData::ConstantDoc constant_doc;
+ String doc_description;
+ if (doc_constants.has(E->key())) {
+ doc_description = doc_constants[E->key()];
+ }
+ DocData::constant_doc_from_variant(constant_doc, E->key(), E->value(), doc_description);
+ doc.constants.push_back(constant_doc);
+ }
+ }
+
+ for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) {
+ E->get()->_update_doc();
+ }
+
+ _add_doc(doc);
+}
#endif
bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
@@ -596,6 +791,19 @@ Error GDScript::reload(bool p_keep_state) {
return OK;
}
+ {
+ String source_path = path;
+ if (source_path.empty()) {
+ source_path = get_path();
+ }
+ if (!source_path.empty()) {
+ MutexLock lock(GDScriptCache::singleton->lock);
+ if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) {
+ GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this;
+ }
+ }
+ }
+
valid = false;
GDScriptParser parser;
Error err = parser.parse(source, path, false);
@@ -615,8 +823,12 @@ Error GDScript::reload(bool p_keep_state) {
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);
+
+ const List<GDScriptParser::ParserError>::Element *e = parser.get_errors().front();
+ while (e != nullptr) {
+ _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT);
+ e = e->next();
+ }
ERR_FAIL_V(ERR_PARSE_ERROR);
}
@@ -625,6 +837,10 @@ Error GDScript::reload(bool p_keep_state) {
GDScriptCompiler compiler;
err = compiler.compile(&parser, this, p_keep_state);
+#ifdef TOOLS_ENABLED
+ _update_doc();
+#endif
+
if (err) {
if (can_run) {
if (EngineDebugger::is_active()) {
@@ -898,7 +1114,7 @@ bool GDScript::has_script_signal(const StringName &p_signal) const {
return false;
}
-void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
+void GDScript::_get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const {
for (const Map<StringName, Vector<StringName>>::Element *E = _signals.front(); E; E = E->next()) {
MethodInfo mi;
mi.name = E->key();
@@ -907,20 +1123,43 @@ void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
arg.name = E->get()[i];
mi.arguments.push_back(arg);
}
- r_signals->push_back(mi);
+ r_list->push_back(mi);
+ }
+
+ if (!p_include_base) {
+ return;
}
if (base.is_valid()) {
- base->get_script_signal_list(r_signals);
+ base->get_script_signal_list(r_list);
}
#ifdef TOOLS_ENABLED
else if (base_cache.is_valid()) {
- base_cache->get_script_signal_list(r_signals);
+ base_cache->get_script_signal_list(r_list);
}
#endif
}
+void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
+ _get_script_signal_list(r_signals, true);
+}
+
+String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript) {
+ ERR_FAIL_NULL_V(p_gdscript, String());
+
+ String class_name;
+ while (p_gdscript) {
+ if (class_name == "") {
+ class_name = p_gdscript->get_script_class_name();
+ } else {
+ class_name = p_gdscript->get_script_class_name() + "." + class_name;
+ }
+ p_gdscript = p_gdscript->_owner;
+ }
+ return class_name;
+}
+
GDScript::GDScript() :
script_list(this) {
valid = false;
@@ -1031,8 +1270,10 @@ GDScript::~GDScript() {
MutexLock lock(GDScriptLanguage::get_singleton()->lock);
while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
- E->self()->_clear_stack();
+ // Order matters since clearing the stack may already cause
+ // the GDSCriptFunctionState to be destroyed and thus removed from the list.
pending_func_states.remove(E);
+ E->self()->_clear_stack();
}
}
@@ -1040,10 +1281,19 @@ GDScript::~GDScript() {
memdelete(E->get());
}
- GDScriptCache::remove_script(get_path());
+ if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown.
+ GDScriptCache::remove_script(get_path());
+ }
_save_orphaned_subclasses();
+#ifdef TOOLS_ENABLED
+ // Clearing inner class doc, script doc only cleared when the script source deleted.
+ if (_owner) {
+ _clear_doc();
+ }
+#endif
+
#ifdef DEBUG_ENABLED
{
MutexLock lock(GDScriptLanguage::get_singleton()->lock);
@@ -1075,7 +1325,8 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
// Try conversion
Callable::CallError ce;
const Variant *value = &p_value;
- Variant converted = Variant::construct(member->data_type.builtin_type, &value, 1, ce);
+ Variant converted;
+ Variant::construct(member->data_type.builtin_type, converted, &value, 1, ce);
if (ce.error == Callable::CallError::CALL_OK) {
members.write[member->index] = converted;
return true;
@@ -1436,8 +1687,10 @@ GDScriptInstance::~GDScriptInstance() {
MutexLock lock(GDScriptLanguage::get_singleton()->lock);
while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
- E->self()->_clear_stack();
+ // Order matters since clearing the stack may already cause
+ // the GDSCriptFunctionState to be destroyed and thus removed from the list.
pending_func_states.remove(E);
+ E->self()->_clear_stack();
}
if (script.is_valid() && owner) {
@@ -1481,9 +1734,9 @@ void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) {
void GDScriptLanguage::init() {
//populate global constants
- int gcc = GlobalConstants::get_global_constant_count();
+ int gcc = CoreConstants::get_global_constant_count();
for (int i = 0; i < gcc; i++) {
- _add_global(StaticCString::create(GlobalConstants::get_global_constant_name(i)), GlobalConstants::get_global_constant_value(i));
+ _add_global(StaticCString::create(CoreConstants::get_global_constant_name(i)), CoreConstants::get_global_constant_value(i));
}
_add_global(StaticCString::create("PI"), Math_PI);
@@ -2022,7 +2275,33 @@ GDScriptLanguage::~GDScriptLanguage() {
if (_call_stack) {
memdelete_arr(_call_stack);
}
- singleton = nullptr;
+
+ // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit).
+ SelfList<GDScript> *s = script_list.first();
+ while (s) {
+ GDScript *script = s->self();
+ // This ensures the current script is not released before we can check what's the next one
+ // in the list (we can't get the next upfront because we don't know if the reference breaking
+ // will cause it -or any other after it, for that matter- to be released so the next one
+ // is not the same as before).
+ script->reference();
+
+ for (Map<StringName, GDScriptFunction *>::Element *E = script->member_functions.front(); E; E = E->next()) {
+ GDScriptFunction *func = E->get();
+ for (int i = 0; i < func->argument_types.size(); i++) {
+ func->argument_types.write[i].script_type_ref = Ref<Script>();
+ }
+ func->return_type.script_type_ref = Ref<Script>();
+ }
+ for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) {
+ E->get().data_type.script_type_ref = Ref<Script>();
+ }
+
+ s = s->next();
+ script->unreference();
+ }
+
+ singleton = NULL;
}
void GDScriptLanguage::add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass) {
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 9906b4014d..f949d26664 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -33,9 +33,10 @@
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
+#include "core/doc_data.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
-#include "core/script_language.h"
+#include "core/object/script_language.h"
#include "gdscript_function.h"
class GDScriptNativeClass : public Reference {
@@ -69,6 +70,7 @@ class GDScript : public Script {
friend class GDScriptInstance;
friend class GDScriptFunction;
+ friend class GDScriptAnalyzer;
friend class GDScriptCompiler;
friend class GDScriptFunctions;
friend class GDScriptLanguage;
@@ -90,9 +92,7 @@ class GDScript : public Script {
#ifdef TOOLS_ENABLED
Map<StringName, int> member_lines;
-
Map<StringName, Variant> member_default_values;
-
List<PropertyInfo> members_cache;
Map<StringName, Variant> member_default_values_cache;
Ref<GDScript> base_cache;
@@ -101,6 +101,20 @@ class GDScript : public Script {
bool placeholder_fallback_enabled;
void _update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames);
+ DocData::ClassDoc doc;
+ Vector<DocData::ClassDoc> docs;
+ String doc_brief_description;
+ String doc_description;
+ Vector<DocData::TutorialDoc> doc_tutorials;
+ Map<String, String> doc_functions;
+ Map<String, String> doc_variables;
+ Map<String, String> doc_constants;
+ Map<String, String> doc_signals;
+ Map<String, DocData::EnumDoc> doc_enums;
+ void _clear_doc();
+ void _update_doc();
+ void _add_doc(const DocData::ClassDoc &p_inner_class);
+
#endif
Map<StringName, PropertyInfo> member_info;
@@ -140,6 +154,13 @@ class GDScript : public Script {
void _save_orphaned_subclasses();
void _init_rpc_methods_properties();
+ void _get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const;
+ void _get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const;
+ void _get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const;
+
+ // This method will map the class name from "Reference" to "MyClass.InnerClass".
+ static String _get_gdscript_reference_class_name(const GDScript *p_gdscript);
+
protected:
bool _get(const StringName &p_name, Variant &r_ret) const;
bool _set(const StringName &p_name, const Variant &p_value);
@@ -190,6 +211,12 @@ public:
virtual void set_source_code(const String &p_code) override;
virtual void update_exports() override;
+#ifdef TOOLS_ENABLED
+ virtual const Vector<DocData::ClassDoc> &get_documentation() const override {
+ return docs;
+ }
+#endif // TOOLS_ENABLED
+
virtual Error reload(bool p_keep_state = false) override;
void set_script_path(const String &p_path) { path = p_path; } //because subclasses need a path too...
@@ -443,7 +470,8 @@ public:
virtual Script *create_script() const;
virtual bool has_named_classes() const;
virtual bool supports_builtin_mode() const;
- virtual bool can_inherit_from_file() { return true; }
+ virtual bool supports_documentation() const;
+ virtual bool can_inherit_from_file() const { return true; }
virtual int find_function(const String &p_function, const String &p_code) const;
virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const;
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint);
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 1e72216ad2..1b76c7f967 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -30,11 +30,12 @@
#include "gdscript_analyzer.h"
-#include "core/class_db.h"
-#include "core/hash_map.h"
+#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
-#include "core/project_settings.h"
-#include "core/script_language.h"
+#include "core/object/class_db.h"
+#include "core/object/script_language.h"
+#include "core/os/file_access.h"
+#include "core/templates/hash_map.h"
#include "gdscript.h"
// TODO: Move this to a central location (maybe core?).
@@ -181,7 +182,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class);
return err;
@@ -207,11 +208,12 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
return err;
}
+ base = parser->get_parser()->head->get_datatype();
}
} 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);
@@ -226,7 +228,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
+ Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
return err;
@@ -301,6 +303,16 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
+ // Check for cyclic inheritance.
+ const GDScriptParser::ClassNode *base_class = result.class_type;
+ while (base_class) {
+ if (base_class->fqcn == p_class->fqcn) {
+ push_error("Cyclic inheritance.", p_class);
+ return ERR_PARSE_ERROR;
+ }
+ base_class = base_class->base_type.class_type;
+ }
+
p_class->base_type = result;
class_type.native_type = result.native_type;
p_class->set_datatype(class_type);
@@ -308,7 +320,10 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
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);
+ Error err = resolve_inheritance(p_class->members[i].m_class, true);
+ if (err) {
+ return err;
+ }
}
}
}
@@ -490,6 +505,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
member.variable->set_datatype(datatype); // Allow recursive usage.
reduce_expression(member.variable->initializer);
datatype = member.variable->initializer->get_datatype();
+ if (datatype.type_source != GDScriptParser::DataType::UNDETECTED) {
+ datatype.type_source = GDScriptParser::DataType::INFERRED;
+ }
}
if (member.variable->datatype_specifier != nullptr) {
@@ -498,7 +516,13 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
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 a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
+ // Try reverse test since it can be a masked subtype.
+ if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) {
+ push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
+ } else {
+ // TODO: Add warning.
+ mark_node_unsafe(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);
@@ -519,6 +543,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
} 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.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
}
datatype.is_constant = false;
@@ -533,6 +558,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
break;
case GDScriptParser::DataType::NATIVE:
if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) {
+ member.variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
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);
@@ -551,6 +577,12 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
GDScriptParser::DataType datatype = member.constant->get_datatype();
if (member.constant->initializer) {
+ if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) {
+ const_fold_array(static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer));
+ } else if (member.constant->initializer->type == GDScriptParser::Node::DICTIONARY) {
+ const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(member.constant->initializer));
+ }
+
if (!member.constant->initializer->is_constant) {
push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer);
}
@@ -596,17 +628,67 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
enum_type.is_meta_type = true;
enum_type.is_constant = true;
+ // Enums can't be nested, so we can safely override this.
+ current_enum = member.m_enum;
+
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;
+ GDScriptParser::EnumNode::Value &element = member.m_enum->values.write[j];
+
+ if (element.custom_value) {
+ reduce_expression(element.custom_value);
+ if (!element.custom_value->is_constant) {
+ push_error(R"(Enum values must be constant.)", element.custom_value);
+ } else if (element.custom_value->reduced_value.get_type() != Variant::INT) {
+ push_error(R"(Enum values must be integers.)", element.custom_value);
+ } else {
+ element.value = element.custom_value->reduced_value;
+ element.resolved = true;
+ }
+ } else {
+ if (element.index > 0) {
+ element.value = element.parent_enum->values[element.index - 1].value + 1;
+ } else {
+ element.value = 0;
+ }
+ element.resolved = true;
+ }
+
+ enum_type.enum_values[element.identifier->name] = element.value;
}
+ current_enum = nullptr;
+
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::ENUM_VALUE: {
+ if (member.enum_value.custom_value) {
+ current_enum = member.enum_value.parent_enum;
+ reduce_expression(member.enum_value.custom_value);
+ current_enum = nullptr;
+
+ if (!member.enum_value.custom_value->is_constant) {
+ push_error(R"(Enum values must be constant.)", member.enum_value.custom_value);
+ } else if (member.enum_value.custom_value->reduced_value.get_type() != Variant::INT) {
+ push_error(R"(Enum values must be integers.)", member.enum_value.custom_value);
+ } else {
+ member.enum_value.value = member.enum_value.custom_value->reduced_value;
+ member.enum_value.resolved = true;
+ }
+ } else {
+ if (member.enum_value.index > 0) {
+ member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1;
+ } else {
+ member.enum_value.value = 0;
+ }
+ member.enum_value.resolved = true;
+ }
+ // Also update the original references.
+ member.enum_value.parent_enum->values.write[member.enum_value.index] = member.enum_value;
+ p_class->members.write[i].enum_value = member.enum_value;
+ } break;
case GDScriptParser::ClassNode::Member::CLASS:
break; // Done later.
case GDScriptParser::ClassNode::Member::UNDEFINED:
@@ -718,7 +800,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) {
resolve_match_branch(static_cast<GDScriptParser::MatchBranchNode *>(p_node), nullptr);
break;
case GDScriptParser::Node::PARAMETER:
- resolve_pararameter(static_cast<GDScriptParser::ParameterNode *>(p_node));
+ resolve_parameter(static_cast<GDScriptParser::ParameterNode *>(p_node));
break;
case GDScriptParser::Node::PATTERN:
resolve_match_pattern(static_cast<GDScriptParser::PatternNode *>(p_node), nullptr);
@@ -772,13 +854,18 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
parser->current_function = p_function;
for (int i = 0; i < p_function->parameters.size(); i++) {
- resolve_pararameter(p_function->parameters[i]);
+ resolve_parameter(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
+#endif // DEBUG_ENABLED
+#ifdef TOOLS_ENABLED
+ if (p_function->parameters[i]->default_value && p_function->parameters[i]->default_value->is_constant) {
+ p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value);
+ }
+#endif // TOOLS_ENABLED
}
if (p_function->identifier->name == "_init") {
@@ -842,6 +929,7 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript
p_suite->datatype.type_source = GDScriptParser::DataType::UNDETECTED;
} else {
p_suite->set_datatype(p_statement->get_datatype());
+ p_suite->datatype.type_source = GDScriptParser::DataType::INFERRED;
}
break;
default:
@@ -876,7 +964,8 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
// 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::Node::Type callee_type = call->get_callee_type();
+ if (callee_type == GDScriptParser::Node::IDENTIFIER) {
GDScriptParser::IdentifierNode *callee = static_cast<GDScriptParser::IdentifierNode *>(call->callee);
if (callee->name == "range") {
list_resolved = true;
@@ -894,17 +983,19 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
if (!call->arguments[i]->is_constant) {
all_is_constant = false;
- } else {
+ } else if (all_is_constant) {
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]);
+ if (!arg_type.is_variant()) {
+ 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]);
+ }
}
}
@@ -927,11 +1018,15 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
}
}
- 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 (p_for->list->is_constant) {
+ p_for->list->set_datatype(type_from_variant(p_for->list->reduced_value, p_for->list));
+ } else {
+ 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);
+ }
}
}
}
@@ -993,7 +1088,13 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
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 a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
+ // Try reverse test since it can be a masked subtype.
+ if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) {
+ push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
+ } else {
+ // TODO: Add warning.
+ mark_node_unsafe(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);
@@ -1029,6 +1130,11 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant
GDScriptParser::DataType type;
reduce_expression(p_constant->initializer);
+ if (p_constant->initializer->type == GDScriptParser::Node::ARRAY) {
+ const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_constant->initializer));
+ } else if (p_constant->initializer->type == GDScriptParser::Node::DICTIONARY) {
+ const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(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);
@@ -1180,14 +1286,19 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc
p_match_pattern->set_datatype(result);
}
-void GDScriptAnalyzer::resolve_pararameter(GDScriptParser::ParameterNode *p_parameter) {
+void GDScriptAnalyzer::resolve_parameter(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->infer_datatype) {
+ result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
+ } else {
+ result.type_source = GDScriptParser::DataType::INFERRED;
+ }
+ result.is_constant = false;
}
if (p_parameter->datatype_specifier != nullptr) {
@@ -1197,7 +1308,7 @@ void GDScriptAnalyzer::resolve_pararameter(GDScriptParser::ParameterNode *p_para
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);
+ push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with parameter 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);
}
@@ -1333,22 +1444,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre
}
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.
@@ -1373,57 +1471,27 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
}
- Variant::Operator vop = Variant::Operator::OP_EQUAL;
- switch (p_assignment->operation) {
- case GDScriptParser::AssignmentNode::OP_NONE:
- vop = Variant::Operator::OP_EQUAL;
- break;
- case GDScriptParser::AssignmentNode::OP_ADDITION:
- vop = Variant::Operator::OP_ADD;
- break;
- case GDScriptParser::AssignmentNode::OP_SUBTRACTION:
- vop = Variant::Operator::OP_SUBTRACT;
- break;
- case GDScriptParser::AssignmentNode::OP_MULTIPLICATION:
- vop = Variant::Operator::OP_MULTIPLY;
- break;
- case GDScriptParser::AssignmentNode::OP_DIVISION:
- vop = Variant::Operator::OP_DIVIDE;
- break;
- case GDScriptParser::AssignmentNode::OP_MODULO:
- vop = Variant::Operator::OP_MODULE;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_LEFT:
- vop = Variant::Operator::OP_SHIFT_LEFT;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_RIGHT:
- vop = Variant::Operator::OP_SHIFT_RIGHT;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_AND:
- vop = Variant::Operator::OP_BIT_AND;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_OR:
- vop = Variant::Operator::OP_BIT_OR;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_XOR:
- vop = Variant::Operator::OP_BIT_XOR;
- break;
- }
-
if (!p_assignment->assignee->get_datatype().is_variant() && !p_assignment->assigned_value->get_datatype().is_variant()) {
bool compatible = true;
GDScriptParser::DataType op_type = p_assignment->assigned_value->get_datatype();
- if (vop != Variant::OP_EQUAL) {
- op_type = get_operation_type(vop, p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), compatible);
+ if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
+ op_type = get_operation_type(p_assignment->variant_op, p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), compatible, p_assignment->assigned_value);
}
if (compatible) {
compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true);
if (!compatible) {
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);
+ // Try reverse test since it can be a masked subtype.
+ if (!is_type_compatible(op_type, p_assignment->assignee->get_datatype(), true)) {
+ 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: Add warning.
+ mark_node_unsafe(p_assignment);
+ }
} else {
// TODO: Warning in this case.
+ mark_node_unsafe(p_assignment);
}
}
} else {
@@ -1548,7 +1616,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
if (p_binary_op->reduced_value.get_type() == Variant::STRING) {
push_error(vformat(R"(%s in operator %s.)", p_binary_op->reduced_value, Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
} else {
- push_error(vformat(R"(Invalid operands to operator %s, %s and %s.".)",
+ push_error(vformat(R"(Invalid operands to operator %s, %s and %s.)",
Variant::get_operator_name(p_binary_op->variant_op),
Variant::get_type_name(p_binary_op->left_operand->reduced_value.get_type()),
Variant::get_type_name(p_binary_op->right_operand->reduced_value.get_type())),
@@ -1570,7 +1638,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
ERR_PRINT("Parser bug: unknown binary operation.");
}
}
- p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value));
+ p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value, p_binary_op));
return;
}
@@ -1584,7 +1652,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
} 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);
+ result = get_operation_type(p_binary_op->variant_op, p_binary_op->left_operand->get_datatype(), right_type, valid, p_binary_op);
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);
@@ -1626,9 +1694,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
}
+ GDScriptParser::Node::Type callee_type = p_call->get_callee_type();
GDScriptParser::DataType call_type;
- if (!p_call->is_super && p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ if (!p_call->is_super && 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);
@@ -1645,7 +1714,27 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
call_type.native_type = function_name; // "Object".
}
- if (all_is_constant) {
+ bool safe_to_fold = true;
+ switch (builtin_type) {
+ // Those are stored by reference so not suited for compile-time construction.
+ // Because in this case they would be the same reference in all constructed values.
+ case Variant::OBJECT:
+ case Variant::PACKED_BYTE_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::PACKED_COLOR_ARRAY:
+ safe_to_fold = false;
+ break;
+ default:
+ break;
+ }
+
+ if (all_is_constant && safe_to_fold) {
// Construct here.
Vector<const Variant *> args;
for (int i = 0; i < p_call->arguments.size(); i++) {
@@ -1653,7 +1742,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
}
Callable::CallError err;
- Variant value = Variant::construct(builtin_type, (const Variant **)args.ptr(), args.size(), err);
+ Variant value;
+ Variant::construct(builtin_type, value, (const Variant **)args.ptr(), args.size(), err);
switch (err.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
@@ -1669,6 +1759,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
}
signature += p_call->arguments[i]->get_datatype().to_string();
}
+ signature += ")";
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:
@@ -1719,7 +1810,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
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())) {
+ if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) {
types_match = false;
break;
#ifdef DEBUG_ENABLED
@@ -1746,6 +1837,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
}
signature += p_call->arguments[i]->get_datatype().to_string();
}
+ signature += ")";
push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call);
}
}
@@ -1803,10 +1895,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
if (p_call->is_super) {
base_type = parser->current_class->base_type;
is_self = true;
- } else if (p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ } else if (callee_type == GDScriptParser::Node::IDENTIFIER) {
base_type = parser->current_class->get_datatype();
is_self = true;
- } else if (p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) {
+ } else if (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.
@@ -1843,9 +1935,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
} else {
// Check if the name exists as something else.
bool found = false;
- if (!p_call->is_super) {
+ if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) {
GDScriptParser::IdentifierNode *callee_id;
- if (p_call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ if (callee_type == GDScriptParser::Node::IDENTIFIER) {
callee_id = static_cast<GDScriptParser::IdentifierNode *>(p_call->callee);
} else {
// Can only be attribute.
@@ -1853,13 +1945,13 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
}
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()) {
+ GDScriptParser::DataType callee_datatype = callee_id->get_datatype();
+ if (callee_datatype.is_set() && !callee_datatype.is_variant()) {
found = true;
- if (callee_type.builtin_type == Variant::CALLABLE) {
+ if (callee_datatype.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);
+ push_error(vformat(R"*(Name "%s" called as a function but is a "%s".)*", p_call->function_name, callee_datatype.to_string()), p_call->callee);
}
#ifdef DEBUG_ENABLED
} else if (!is_self) {
@@ -1922,7 +2014,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
}
void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) {
- bool all_is_constant = true;
+ HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements;
for (int i = 0; i < p_dictionary->elements.size(); i++) {
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
@@ -1930,17 +2022,14 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti
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;
+ if (element.key->is_constant) {
+ if (elements.has(element.key->reduced_value)) {
+ push_error(vformat(R"(Key "%s" was already used in this dictionary (at line %d).)", element.key->reduced_value, elements[element.key->reduced_value]->start_line), element.key);
+ } else {
+ elements[element.key->reduced_value] = element.value;
+ }
}
- p_dictionary->is_constant = true;
- p_dictionary->reduced_value = dict;
}
// It's dictionary in any case.
@@ -1967,9 +2056,17 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node)
p_get_node->set_datatype(result);
}
-GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const StringName &p_class_name) {
+GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source) {
Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(p_class_name));
- ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+ Error err = ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+
+ if (err) {
+ push_error(vformat(R"(Could not resolve class "%s", because of a parser error.)", p_class_name), p_source);
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::UNDETECTED;
+ type.kind = GDScriptParser::DataType::VARIANT;
+ return type;
+ }
GDScriptParser::DataType type;
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
@@ -2000,23 +2097,38 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
if (valid) {
p_identifier->is_constant = true;
p_identifier->reduced_value = result;
- p_identifier->set_datatype(type_from_variant(result));
+ p_identifier->set_datatype(type_from_variant(result, p_identifier));
} 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));
+ switch (base.builtin_type) {
+ case Variant::NIL: {
+ push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier);
return;
}
+ case Variant::DICTIONARY: {
+ GDScriptParser::DataType dummy;
+ dummy.kind = GDScriptParser::DataType::VARIANT;
+ p_identifier->set_datatype(dummy);
+ return;
+ }
+ default: {
+ Callable::CallError temp;
+ Variant dummy;
+ Variant::construct(base.builtin_type, dummy, 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);
+ }
}
- push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
}
return;
}
@@ -2131,7 +2243,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
if (valid) {
p_identifier->is_constant = true;
p_identifier->reduced_value = int_constant;
- p_identifier->set_datatype(type_from_variant(int_constant));
+ p_identifier->set_datatype(type_from_variant(int_constant, p_identifier));
return;
}
}
@@ -2139,6 +2251,33 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) {
// TODO: This is opportunity to further infer types.
+
+ // Check if we are inside and enum. This allows enum values to access other elements of the same enum.
+ if (current_enum) {
+ for (int i = 0; i < current_enum->values.size(); i++) {
+ const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
+ if (element.identifier->name == p_identifier->name) {
+ GDScriptParser::DataType type;
+ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN;
+ type.builtin_type = Variant::INT;
+ type.is_constant = true;
+ if (element.parent_enum->identifier) {
+ type.enum_type = element.parent_enum->identifier->name;
+ }
+ p_identifier->set_datatype(type);
+
+ if (element.resolved) {
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = element.value;
+ } else {
+ push_error(R"(Cannot use another enum element before it was declared.)", p_identifier);
+ }
+ return; // Found anyway.
+ }
+ }
+ }
+
// Check if identifier is local.
// If that's the case, the declaration already was solved before.
switch (p_identifier->source) {
@@ -2181,10 +2320,12 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
StringName name = p_identifier->name;
p_identifier->source = GDScriptParser::IdentifierNode::UNDEFINED_SOURCE;
- // Check globals.
- if (GDScriptParser::get_builtin_type(name) < Variant::VARIANT_MAX) {
+ // Check globals. We make an exception for Variant::OBJECT because it's the base class for
+ // non-builtin types so we allow doing e.g. Object.new()
+ Variant::Type builtin_type = GDScriptParser::get_builtin_type(name);
+ if (builtin_type != Variant::OBJECT && builtin_type < Variant::VARIANT_MAX) {
if (can_be_builtin) {
- p_identifier->set_datatype(make_builtin_meta_type(GDScriptParser::get_builtin_type(name)));
+ p_identifier->set_datatype(make_builtin_meta_type(builtin_type));
return;
} else {
push_error(R"(Builtin type cannot be used as a name on its own.)", p_identifier);
@@ -2197,14 +2338,38 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
}
if (ScriptServer::is_global_class(name)) {
- p_identifier->set_datatype(make_global_class_meta_type(name));
+ p_identifier->set_datatype(make_global_class_meta_type(name, p_identifier));
return;
}
+ // Try singletons.
+ // Do this before globals because this might be a singleton loading another one before it's compiled.
+ if (ProjectSettings::get_singleton()->has_autoload(name)) {
+ const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(name);
+ if (autoload.is_singleton) {
+ // Singleton exists, so it's at least a Node.
+ GDScriptParser::DataType result;
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) {
+ Ref<GDScriptParserRef> parser = get_parser_for(autoload.path);
+ if (parser.is_valid()) {
+ Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+ if (err == OK) {
+ result = type_from_metatype(parser->get_parser()->head->get_datatype());
+ }
+ }
+ }
+ result.is_constant = true;
+ p_identifier->set_datatype(result);
+ 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->set_datatype(type_from_variant(constant, p_identifier));
p_identifier->is_constant = true;
p_identifier->reduced_value = constant;
return;
@@ -2212,7 +2377,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
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->set_datatype(type_from_variant(constant, p_identifier));
p_identifier->is_constant = true;
p_identifier->reduced_value = constant;
return;
@@ -2234,13 +2399,44 @@ 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));
+ p_literal->set_datatype(type_from_variant(p_literal->reduced_value, p_literal));
}
void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
+ if (!p_preload->path) {
+ return;
+ }
+
+ reduce_expression(p_preload->path);
+
+ if (!p_preload->path->is_constant) {
+ push_error("Preloaded path must be a constant string.", p_preload->path);
+ return;
+ }
+
+ if (p_preload->path->reduced_value.get_type() != Variant::STRING) {
+ push_error("Preloaded path must be a constant string.", p_preload->path);
+ } else {
+ p_preload->resolved_path = p_preload->path->reduced_value;
+ // TODO: Save this as script dependency.
+ if (p_preload->resolved_path.is_rel_path()) {
+ p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path);
+ }
+ p_preload->resolved_path = p_preload->resolved_path.simplify_path();
+ if (!FileAccess::exists(p_preload->resolved_path)) {
+ push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path);
+ } else {
+ // TODO: Don't load if validating: use completion cache.
+ p_preload->resource = ResourceLoader::load(p_preload->resolved_path);
+ if (p_preload->resource.is_null()) {
+ push_error(vformat(R"(Could not p_preload resource file "%s".)", p_preload->resolved_path), p_preload->path);
+ }
+ }
+ }
+
p_preload->is_constant = true;
p_preload->reduced_value = p_preload->resource;
- p_preload->set_datatype(type_from_variant(p_preload->reduced_value));
+ p_preload->set_datatype(type_from_variant(p_preload->reduced_value, p_preload));
}
void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) {
@@ -2283,13 +2479,13 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
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);
+ 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 = type_from_variant(value, p_subscript);
}
result_type.kind = GDScriptParser::DataType::VARIANT;
} else {
@@ -2329,7 +2525,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
} else {
p_subscript->is_constant = true;
p_subscript->reduced_value = value;
- result_type = type_from_variant(value);
+ result_type = type_from_variant(value, p_subscript);
}
result_type.kind = GDScriptParser::DataType::VARIANT;
} else {
@@ -2384,7 +2580,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
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::RID:
case Variant::BOOL:
case Variant::CALLABLE:
case Variant::FLOAT:
@@ -2413,11 +2609,11 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
// Check resulting type if possible.
result_type.builtin_type = Variant::NIL;
result_type.kind = GDScriptParser::DataType::BUILTIN;
- result_type.type_source = GDScriptParser::DataType::INFERRED;
+ result_type.type_source = base_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED;
switch (base_type.builtin_type) {
// Can't index at all.
- case Variant::_RID:
+ case Variant::RID:
case Variant::BOOL:
case Variant::CALLABLE:
case Variant::FLOAT:
@@ -2544,16 +2740,22 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op)
GDScriptParser::DataType result;
+ if (p_unary_op->operand == nullptr) {
+ result.kind = GDScriptParser::DataType::VARIANT;
+ p_unary_op->set_datatype(result);
+ return;
+ }
+
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);
+ result = type_from_variant(p_unary_op->reduced_value, p_unary_op);
} 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);
+ result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), valid, p_unary_op);
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);
@@ -2563,7 +2765,47 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op)
p_unary_op->set_datatype(result);
}
-GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value) {
+void GDScriptAnalyzer::const_fold_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];
+ all_is_constant = all_is_constant && element->is_constant;
+ if (!all_is_constant) {
+ return;
+ }
+ }
+
+ 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;
+}
+
+void GDScriptAnalyzer::const_fold_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];
+ all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant;
+ if (!all_is_constant) {
+ return;
+ }
+ }
+
+ 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;
+}
+
+GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) {
GDScriptParser::DataType result;
result.is_constant = true;
result.kind = GDScriptParser::DataType::BUILTIN;
@@ -2585,19 +2827,44 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
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;
+ 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;
+ // This might be an inner class, so we want to get the parser for the root.
+ // But still get the inner class from that tree.
+ GDScript *current = gds.ptr();
+ List<StringName> class_chain;
+ while (current->_owner) {
+ // Push to front so it's in reverse.
+ class_chain.push_front(current->name);
+ current = current->_owner;
+ }
+
+ Ref<GDScriptParserRef> ref = get_parser_for(current->path);
+ ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+
+ GDScriptParser::ClassNode *found = ref->get_parser()->head;
+
+ // It should be okay to assume this exists, since we have a complete script already.
+ for (const List<StringName>::Element *E = class_chain.front(); E; E = E->next()) {
+ found = found->get_member(E->get()).m_class;
+ }
+
+ result.class_type = found;
+ 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::SCRIPT;
+ push_error(vformat(R"(Constant value uses script from "%s" which is loaded but not compiled.)", scr->get_path()), p_source);
+ result.kind = GDScriptParser::DataType::VARIANT;
+ result.type_source = GDScriptParser::DataType::UNDETECTED;
+ result.is_meta_type = false;
}
- result.native_type = scr->get_instance_base_type();
} else {
result.kind = GDScriptParser::DataType::NATIVE;
if (result.native_type == GDScriptNativeClass::get_class_static()) {
@@ -2643,7 +2910,8 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD
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);
+ Variant dummy;
+ Variant::construct(p_base_type.builtin_type, dummy, nullptr, 0, err);
if (err.error != Callable::CallError::CALL_OK) {
ERR_FAIL_V_MSG(false, "Could not construct base Variant type.");
}
@@ -2788,7 +3056,7 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
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)) {
+ } else if (par_type.is_hard_type() && !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)) {
@@ -2853,81 +3121,31 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con
}
#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 GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source) {
+ // Unary version.
+ GDScriptParser::DataType nil_type;
+ nil_type.builtin_type = Variant::NIL;
+ return get_operation_type(p_operation, p_a, nil_type, r_valid, p_source);
+}
+GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source) {
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;
- }
+ Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type);
- // Avoid error in formatting operator (%) where it doesn't find a placeholder.
- if (a_type == Variant::STRING) {
- a = String("%s");
+ if (op_eval == nullptr) {
+ r_valid = false;
+ return result;
}
- Variant ret;
- Variant::evaluate(p_operation, a, b, ret, r_valid);
+ r_valid = true;
- if (r_valid) {
- return type_from_variant(ret);
- }
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type);
return result;
}
@@ -3124,9 +3342,11 @@ Error GDScriptAnalyzer::resolve_program() {
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()) {
+ if (depended_parsers[E->get()].is_null()) {
+ return ERR_PARSE_ERROR;
+ }
depended_parsers[E->get()]->raise_status(GDScriptParserRef::FULLY_SOLVED);
}
- depended_parsers.clear();
return parser->errors.empty() ? OK : ERR_PARSE_ERROR;
}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 06d3530cb6..9925167856 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -31,9 +31,9 @@
#ifndef GDSCRIPT_ANALYZER_H
#define GDSCRIPT_ANALYZER_H
-#include "core/object.h"
-#include "core/reference.h"
-#include "core/set.h"
+#include "core/object/object.h"
+#include "core/object/reference.h"
+#include "core/templates/set.h"
#include "gdscript_cache.h"
#include "gdscript_parser.h"
@@ -41,6 +41,8 @@ class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
+ const GDScriptParser::EnumNode *current_enum = nullptr;
+
Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
@@ -65,7 +67,7 @@ class GDScriptAnalyzer {
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_parameter(GDScriptParser::ParameterNode *p_parameter);
void resolve_return(GDScriptParser::ReturnNode *p_return);
// Reduction functions.
@@ -87,16 +89,20 @@ class GDScriptAnalyzer {
void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op);
void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
+ void const_fold_array(GDScriptParser::ArrayNode *p_array);
+ void const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary);
+
// Helpers.
- GDScriptParser::DataType type_from_variant(const Variant &p_value);
+ GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
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);
+ GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
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);
+ GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source);
+ GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source);
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);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
new file mode 100644
index 0000000000..d89b89c8b9
--- /dev/null
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -0,0 +1,1212 @@
+/*************************************************************************/
+/* gdscript_byte_codegen.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_byte_codegen.h"
+
+#include "core/debugger/engine_debugger.h"
+#include "gdscript.h"
+
+uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) {
+#ifdef TOOLS_ENABLED
+ function->arg_names.push_back(p_name);
+#endif
+ function->_argument_count++;
+ function->argument_types.push_back(p_type);
+ if (p_is_optional) {
+ function->_default_arg_count++;
+ }
+
+ return add_local(p_name, p_type);
+}
+
+uint32_t GDScriptByteCodeGenerator::add_local(const StringName &p_name, const GDScriptDataType &p_type) {
+ int stack_pos = increase_stack();
+ add_stack_identifier(p_name, stack_pos);
+ return stack_pos;
+}
+
+uint32_t GDScriptByteCodeGenerator::add_local_constant(const StringName &p_name, const Variant &p_constant) {
+ int index = add_or_get_constant(p_constant);
+ local_constants[p_name] = index;
+ return index;
+}
+
+uint32_t GDScriptByteCodeGenerator::add_or_get_constant(const Variant &p_constant) {
+ if (constant_map.has(p_constant)) {
+ return constant_map[p_constant];
+ }
+ int index = constant_map.size();
+ constant_map[p_constant] = index;
+ return index;
+}
+
+uint32_t GDScriptByteCodeGenerator::add_or_get_name(const StringName &p_name) {
+ return get_name_map_pos(p_name);
+}
+
+uint32_t GDScriptByteCodeGenerator::add_temporary() {
+ current_temporaries++;
+ int idx = increase_stack();
+#ifdef DEBUG_ENABLED
+ temp_stack.push_back(idx);
+#endif
+ return idx;
+}
+
+void GDScriptByteCodeGenerator::pop_temporary() {
+ ERR_FAIL_COND(current_temporaries == 0);
+ current_stack_size--;
+#ifdef DEBUG_ENABLED
+ if (temp_stack.back()->get() != current_stack_size) {
+ ERR_PRINT("Mismatched popping of temporary value");
+ }
+ temp_stack.pop_back();
+#endif
+ current_temporaries--;
+}
+
+void GDScriptByteCodeGenerator::start_parameters() {
+ if (function->_default_arg_count > 0) {
+ append(GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT);
+ function->default_arguments.push_back(opcodes.size());
+ }
+}
+
+void GDScriptByteCodeGenerator::end_parameters() {
+ function->default_arguments.invert();
+}
+
+void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) {
+ function = memnew(GDScriptFunction);
+ debug_stack = EngineDebugger::is_active();
+
+ function->name = p_function_name;
+ function->_script = p_script;
+ function->source = p_script->get_path();
+
+#ifdef DEBUG_ENABLED
+ function->func_cname = (String(function->source) + " - " + String(p_function_name)).utf8();
+ function->_func_cname = function->func_cname.get_data();
+#endif
+
+ function->_static = p_static;
+ function->return_type = p_return_type;
+ function->rpc_mode = p_rpc_mode;
+ function->_argument_count = 0;
+}
+
+GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
+#ifdef DEBUG_ENABLED
+ if (current_temporaries != 0) {
+ ERR_PRINT("Non-zero temporary variables at end of function: " + itos(current_temporaries));
+ }
+#endif
+ append(GDScriptFunction::OPCODE_END, 0);
+
+ if (constant_map.size()) {
+ function->_constant_count = constant_map.size();
+ function->constants.resize(constant_map.size());
+ function->_constants_ptr = function->constants.ptrw();
+ const Variant *K = nullptr;
+ while ((K = constant_map.next(K))) {
+ int idx = constant_map[*K];
+ function->constants.write[idx] = *K;
+ }
+ } else {
+ function->_constants_ptr = nullptr;
+ function->_constant_count = 0;
+ }
+
+ if (name_map.size()) {
+ function->global_names.resize(name_map.size());
+ function->_global_names_ptr = &function->global_names[0];
+ for (Map<StringName, int>::Element *E = name_map.front(); E; E = E->next()) {
+ function->global_names.write[E->get()] = E->key();
+ }
+ function->_global_names_count = function->global_names.size();
+
+ } else {
+ function->_global_names_ptr = nullptr;
+ function->_global_names_count = 0;
+ }
+
+ if (opcodes.size()) {
+ function->code = opcodes;
+ function->_code_ptr = &function->code[0];
+ function->_code_size = opcodes.size();
+
+ } else {
+ function->_code_ptr = nullptr;
+ function->_code_size = 0;
+ }
+
+ if (function->default_arguments.size()) {
+ function->_default_arg_count = function->default_arguments.size() - 1;
+ function->_default_arg_ptr = &function->default_arguments[0];
+ } else {
+ function->_default_arg_count = 0;
+ function->_default_arg_ptr = nullptr;
+ }
+
+ if (operator_func_map.size()) {
+ function->operator_funcs.resize(operator_func_map.size());
+ function->_operator_funcs_count = function->operator_funcs.size();
+ function->_operator_funcs_ptr = function->operator_funcs.ptr();
+ for (const Map<Variant::ValidatedOperatorEvaluator, int>::Element *E = operator_func_map.front(); E; E = E->next()) {
+ function->operator_funcs.write[E->get()] = E->key();
+ }
+ } else {
+ function->_operator_funcs_count = 0;
+ function->_operator_funcs_ptr = nullptr;
+ }
+
+ if (setters_map.size()) {
+ function->setters.resize(setters_map.size());
+ function->_setters_count = function->setters.size();
+ function->_setters_ptr = function->setters.ptr();
+ for (const Map<Variant::ValidatedSetter, int>::Element *E = setters_map.front(); E; E = E->next()) {
+ function->setters.write[E->get()] = E->key();
+ }
+ } else {
+ function->_setters_count = 0;
+ function->_setters_ptr = nullptr;
+ }
+
+ if (getters_map.size()) {
+ function->getters.resize(getters_map.size());
+ function->_getters_count = function->getters.size();
+ function->_getters_ptr = function->getters.ptr();
+ for (const Map<Variant::ValidatedGetter, int>::Element *E = getters_map.front(); E; E = E->next()) {
+ function->getters.write[E->get()] = E->key();
+ }
+ } else {
+ function->_getters_count = 0;
+ function->_getters_ptr = nullptr;
+ }
+
+ if (keyed_setters_map.size()) {
+ function->keyed_setters.resize(keyed_setters_map.size());
+ function->_keyed_setters_count = function->keyed_setters.size();
+ function->_keyed_setters_ptr = function->keyed_setters.ptr();
+ for (const Map<Variant::ValidatedKeyedSetter, int>::Element *E = keyed_setters_map.front(); E; E = E->next()) {
+ function->keyed_setters.write[E->get()] = E->key();
+ }
+ } else {
+ function->_keyed_setters_count = 0;
+ function->_keyed_setters_ptr = nullptr;
+ }
+
+ if (keyed_getters_map.size()) {
+ function->keyed_getters.resize(keyed_getters_map.size());
+ function->_keyed_getters_count = function->keyed_getters.size();
+ function->_keyed_getters_ptr = function->keyed_getters.ptr();
+ for (const Map<Variant::ValidatedKeyedGetter, int>::Element *E = keyed_getters_map.front(); E; E = E->next()) {
+ function->keyed_getters.write[E->get()] = E->key();
+ }
+ } else {
+ function->_keyed_getters_count = 0;
+ function->_keyed_getters_ptr = nullptr;
+ }
+
+ if (indexed_setters_map.size()) {
+ function->indexed_setters.resize(indexed_setters_map.size());
+ function->_indexed_setters_count = function->indexed_setters.size();
+ function->_indexed_setters_ptr = function->indexed_setters.ptr();
+ for (const Map<Variant::ValidatedIndexedSetter, int>::Element *E = indexed_setters_map.front(); E; E = E->next()) {
+ function->indexed_setters.write[E->get()] = E->key();
+ }
+ } else {
+ function->_indexed_setters_count = 0;
+ function->_indexed_setters_ptr = nullptr;
+ }
+
+ if (indexed_getters_map.size()) {
+ function->indexed_getters.resize(indexed_getters_map.size());
+ function->_indexed_getters_count = function->indexed_getters.size();
+ function->_indexed_getters_ptr = function->indexed_getters.ptr();
+ for (const Map<Variant::ValidatedIndexedGetter, int>::Element *E = indexed_getters_map.front(); E; E = E->next()) {
+ function->indexed_getters.write[E->get()] = E->key();
+ }
+ } else {
+ function->_indexed_getters_count = 0;
+ function->_indexed_getters_ptr = nullptr;
+ }
+
+ if (builtin_method_map.size()) {
+ function->builtin_methods.resize(builtin_method_map.size());
+ function->_builtin_methods_ptr = function->builtin_methods.ptr();
+ function->_builtin_methods_count = builtin_method_map.size();
+ for (const Map<Variant::ValidatedBuiltInMethod, int>::Element *E = builtin_method_map.front(); E; E = E->next()) {
+ function->builtin_methods.write[E->get()] = E->key();
+ }
+ } else {
+ function->_builtin_methods_ptr = nullptr;
+ function->_builtin_methods_count = 0;
+ }
+
+ if (constructors_map.size()) {
+ function->constructors.resize(constructors_map.size());
+ function->_constructors_ptr = function->constructors.ptr();
+ function->_constructors_count = constructors_map.size();
+ for (const Map<Variant::ValidatedConstructor, int>::Element *E = constructors_map.front(); E; E = E->next()) {
+ function->constructors.write[E->get()] = E->key();
+ }
+ } else {
+ function->_constructors_ptr = nullptr;
+ function->_constructors_count = 0;
+ }
+
+ if (method_bind_map.size()) {
+ function->methods.resize(method_bind_map.size());
+ function->_methods_ptr = function->methods.ptrw();
+ function->_methods_count = method_bind_map.size();
+ for (const Map<MethodBind *, int>::Element *E = method_bind_map.front(); E; E = E->next()) {
+ function->methods.write[E->get()] = E->key();
+ }
+ } else {
+ function->_methods_ptr = nullptr;
+ function->_methods_count = 0;
+ }
+
+ if (debug_stack) {
+ function->stack_debug = stack_debug;
+ }
+ function->_stack_size = stack_max;
+ function->_instruction_args_size = instr_args_max;
+ function->_ptrcall_args_size = ptrcall_max;
+
+ ended = true;
+ return function;
+}
+
+#ifdef DEBUG_ENABLED
+void GDScriptByteCodeGenerator::set_signature(const String &p_signature) {
+ function->profile.signature = p_signature;
+}
+#endif
+
+void GDScriptByteCodeGenerator::set_initial_line(int p_line) {
+ function->_initial_line = p_line;
+}
+
+#define HAS_BUILTIN_TYPE(m_var) \
+ (m_var.type.has_type && m_var.type.kind == GDScriptDataType::BUILTIN)
+
+#define IS_BUILTIN_TYPE(m_var, m_type) \
+ (m_var.type.has_type && m_var.type.kind == GDScriptDataType::BUILTIN && m_var.type.builtin_type == m_type)
+
+void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) {
+ if (HAS_BUILTIN_TYPE(p_left_operand)) {
+ // Gather specific operator.
+ Variant::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(p_operator, p_left_operand.type.builtin_type, Variant::NIL);
+
+ append(GDScriptFunction::OPCODE_OPERATOR_VALIDATED, 3);
+ append(p_left_operand);
+ append(Address());
+ append(p_target);
+ append(op_func);
+ return;
+ }
+
+ // No specific types, perform variant evaluation.
+ append(GDScriptFunction::OPCODE_OPERATOR, 3);
+ append(p_left_operand);
+ append(Address());
+ append(p_target);
+ append(p_operator);
+}
+
+void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) {
+ if (HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand)) {
+ // Gather specific operator.
+ Variant::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type);
+
+ append(GDScriptFunction::OPCODE_OPERATOR_VALIDATED, 3);
+ append(p_left_operand);
+ append(p_right_operand);
+ append(p_target);
+ append(op_func);
+ return;
+ }
+
+ // No specific types, perform variant evaluation.
+ append(GDScriptFunction::OPCODE_OPERATOR, 3);
+ append(p_left_operand);
+ append(p_right_operand);
+ append(p_target);
+ append(p_operator);
+}
+
+void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) {
+ append(GDScriptFunction::OPCODE_EXTENDS_TEST, 3);
+ append(p_source);
+ append(p_type);
+ append(p_target);
+}
+
+void GDScriptByteCodeGenerator::write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) {
+ append(GDScriptFunction::OPCODE_IS_BUILTIN, 3);
+ append(p_source);
+ append(p_target);
+ append(p_type);
+}
+
+void GDScriptByteCodeGenerator::write_and_left_operand(const Address &p_left_operand) {
+ append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1);
+ append(p_left_operand);
+ logic_op_jump_pos1.push_back(opcodes.size());
+ append(0); // Jump target, will be patched.
+}
+
+void GDScriptByteCodeGenerator::write_and_right_operand(const Address &p_right_operand) {
+ append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1);
+ append(p_right_operand);
+ logic_op_jump_pos2.push_back(opcodes.size());
+ append(0); // Jump target, will be patched.
+}
+
+void GDScriptByteCodeGenerator::write_end_and(const Address &p_target) {
+ // If here means both operands are true.
+ append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1);
+ append(p_target);
+ // Jump away from the fail condition.
+ append(GDScriptFunction::OPCODE_JUMP, 0);
+ append(opcodes.size() + 3);
+ // Here it means one of operands is false.
+ patch_jump(logic_op_jump_pos1.back()->get());
+ patch_jump(logic_op_jump_pos2.back()->get());
+ logic_op_jump_pos1.pop_back();
+ logic_op_jump_pos2.pop_back();
+ append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 0);
+ append(p_target);
+}
+
+void GDScriptByteCodeGenerator::write_or_left_operand(const Address &p_left_operand) {
+ append(GDScriptFunction::OPCODE_JUMP_IF, 1);
+ append(p_left_operand);
+ logic_op_jump_pos1.push_back(opcodes.size());
+ append(0); // Jump target, will be patched.
+}
+
+void GDScriptByteCodeGenerator::write_or_right_operand(const Address &p_right_operand) {
+ append(GDScriptFunction::OPCODE_JUMP_IF, 1);
+ append(p_right_operand);
+ logic_op_jump_pos2.push_back(opcodes.size());
+ append(0); // Jump target, will be patched.
+}
+
+void GDScriptByteCodeGenerator::write_end_or(const Address &p_target) {
+ // If here means both operands are false.
+ append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 1);
+ append(p_target);
+ // Jump away from the success condition.
+ append(GDScriptFunction::OPCODE_JUMP, 0);
+ append(opcodes.size() + 3);
+ // Here it means one of operands is false.
+ patch_jump(logic_op_jump_pos1.back()->get());
+ patch_jump(logic_op_jump_pos2.back()->get());
+ logic_op_jump_pos1.pop_back();
+ logic_op_jump_pos2.pop_back();
+ append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1);
+ append(p_target);
+}
+
+void GDScriptByteCodeGenerator::write_start_ternary(const Address &p_target) {
+ ternary_result.push_back(p_target);
+}
+
+void GDScriptByteCodeGenerator::write_ternary_condition(const Address &p_condition) {
+ append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1);
+ append(p_condition);
+ ternary_jump_fail_pos.push_back(opcodes.size());
+ append(0); // Jump target, will be patched.
+}
+
+void GDScriptByteCodeGenerator::write_ternary_true_expr(const Address &p_expr) {
+ append(GDScriptFunction::OPCODE_ASSIGN, 2);
+ append(ternary_result.back()->get());
+ append(p_expr);
+ // Jump away from the false path.
+ append(GDScriptFunction::OPCODE_JUMP, 0);
+ ternary_jump_skip_pos.push_back(opcodes.size());
+ append(0);
+ // Fail must jump here.
+ patch_jump(ternary_jump_fail_pos.back()->get());
+ ternary_jump_fail_pos.pop_back();
+}
+
+void GDScriptByteCodeGenerator::write_ternary_false_expr(const Address &p_expr) {
+ append(GDScriptFunction::OPCODE_ASSIGN, 2);
+ append(ternary_result.back()->get());
+ append(p_expr);
+}
+
+void GDScriptByteCodeGenerator::write_end_ternary() {
+ patch_jump(ternary_jump_skip_pos.back()->get());
+ ternary_jump_skip_pos.pop_back();
+}
+
+void GDScriptByteCodeGenerator::write_set(const Address &p_target, const Address &p_index, const Address &p_source) {
+ if (HAS_BUILTIN_TYPE(p_target)) {
+ if (IS_BUILTIN_TYPE(p_index, Variant::INT) && Variant::get_member_validated_indexed_setter(p_target.type.builtin_type)) {
+ // Use indexed setter instead.
+ Variant::ValidatedIndexedSetter setter = Variant::get_member_validated_indexed_setter(p_target.type.builtin_type);
+ append(GDScriptFunction::OPCODE_SET_INDEXED_VALIDATED, 3);
+ append(p_target);
+ append(p_index);
+ append(p_source);
+ append(setter);
+ return;
+ } else if (Variant::get_member_validated_keyed_setter(p_target.type.builtin_type)) {
+ Variant::ValidatedKeyedSetter setter = Variant::get_member_validated_keyed_setter(p_target.type.builtin_type);
+ append(GDScriptFunction::OPCODE_SET_KEYED_VALIDATED, 3);
+ append(p_target);
+ append(p_index);
+ append(p_source);
+ append(setter);
+ return;
+ }
+ }
+
+ append(GDScriptFunction::OPCODE_SET_KEYED, 3);
+ append(p_target);
+ append(p_index);
+ append(p_source);
+}
+
+void GDScriptByteCodeGenerator::write_get(const Address &p_target, const Address &p_index, const Address &p_source) {
+ if (HAS_BUILTIN_TYPE(p_source)) {
+ if (IS_BUILTIN_TYPE(p_index, Variant::INT) && Variant::get_member_validated_indexed_getter(p_source.type.builtin_type)) {
+ // Use indexed getter instead.
+ Variant::ValidatedIndexedGetter getter = Variant::get_member_validated_indexed_getter(p_source.type.builtin_type);
+ append(GDScriptFunction::OPCODE_GET_INDEXED_VALIDATED, 3);
+ append(p_source);
+ append(p_index);
+ append(p_target);
+ append(getter);
+ return;
+ } else if (Variant::get_member_validated_keyed_getter(p_source.type.builtin_type)) {
+ Variant::ValidatedKeyedGetter getter = Variant::get_member_validated_keyed_getter(p_source.type.builtin_type);
+ append(GDScriptFunction::OPCODE_GET_KEYED_VALIDATED, 3);
+ append(p_source);
+ append(p_index);
+ append(p_target);
+ append(getter);
+ return;
+ }
+ }
+ append(GDScriptFunction::OPCODE_GET_KEYED, 3);
+ append(p_source);
+ append(p_index);
+ append(p_target);
+}
+
+void GDScriptByteCodeGenerator::write_set_named(const Address &p_target, const StringName &p_name, const Address &p_source) {
+ if (HAS_BUILTIN_TYPE(p_target) && Variant::get_member_validated_setter(p_target.type.builtin_type, p_name)) {
+ Variant::ValidatedSetter setter = Variant::get_member_validated_setter(p_target.type.builtin_type, p_name);
+ append(GDScriptFunction::OPCODE_SET_NAMED_VALIDATED, 2);
+ append(p_target);
+ append(p_source);
+ append(setter);
+ return;
+ }
+ append(GDScriptFunction::OPCODE_SET_NAMED, 2);
+ append(p_target);
+ append(p_source);
+ append(p_name);
+}
+
+void GDScriptByteCodeGenerator::write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) {
+ if (HAS_BUILTIN_TYPE(p_source) && Variant::get_member_validated_getter(p_source.type.builtin_type, p_name)) {
+ Variant::ValidatedGetter getter = Variant::get_member_validated_getter(p_source.type.builtin_type, p_name);
+ append(GDScriptFunction::OPCODE_GET_NAMED_VALIDATED, 2);
+ append(p_source);
+ append(p_target);
+ append(getter);
+ return;
+ }
+ append(GDScriptFunction::OPCODE_GET_NAMED, 2);
+ append(p_source);
+ append(p_target);
+ append(p_name);
+}
+
+void GDScriptByteCodeGenerator::write_set_member(const Address &p_value, const StringName &p_name) {
+ append(GDScriptFunction::OPCODE_SET_MEMBER, 1);
+ append(p_value);
+ append(p_name);
+}
+
+void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const StringName &p_name) {
+ append(GDScriptFunction::OPCODE_GET_MEMBER, 1);
+ append(p_target);
+ append(p_name);
+}
+
+void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) {
+ if (p_target.type.has_type && !p_source.type.has_type) {
+ // Typed assignment.
+ switch (p_target.type.kind) {
+ case GDScriptDataType::BUILTIN: {
+ append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2);
+ append(p_target);
+ append(p_source);
+ append(p_target.type.builtin_type);
+ } break;
+ case GDScriptDataType::NATIVE: {
+ int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type];
+ class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS);
+ append(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE, 3);
+ append(p_target);
+ append(p_source);
+ append(class_idx);
+ } break;
+ case GDScriptDataType::SCRIPT:
+ case GDScriptDataType::GDSCRIPT: {
+ Variant script = p_target.type.script_type;
+ int idx = get_constant_pos(script);
+ idx |= (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS);
+
+ append(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT, 3);
+ append(p_target);
+ append(p_source);
+ append(idx);
+ } break;
+ default: {
+ ERR_PRINT("Compiler bug: unresolved assign.");
+
+ // Shouldn't get here, but fail-safe to a regular assignment
+ append(GDScriptFunction::OPCODE_ASSIGN, 2);
+ append(p_target);
+ append(p_source);
+ }
+ }
+ } else {
+ if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) {
+ // Need conversion..
+ append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2);
+ append(p_target);
+ append(p_source);
+ append(p_target.type.builtin_type);
+ } else {
+ // Either untyped assignment or already type-checked by the parser
+ append(GDScriptFunction::OPCODE_ASSIGN, 2);
+ append(p_target);
+ append(p_source);
+ }
+ }
+}
+
+void GDScriptByteCodeGenerator::write_assign_true(const Address &p_target) {
+ append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1);
+ append(p_target);
+}
+
+void GDScriptByteCodeGenerator::write_assign_false(const Address &p_target) {
+ append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 1);
+ append(p_target);
+}
+
+void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_dst, const Address &p_src) {
+ write_assign(p_dst, p_src);
+ function->default_arguments.push_back(opcodes.size());
+}
+
+void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) {
+ int index = 0;
+
+ switch (p_type.kind) {
+ case GDScriptDataType::BUILTIN: {
+ append(GDScriptFunction::OPCODE_CAST_TO_BUILTIN, 2);
+ index = p_type.builtin_type;
+ } break;
+ case GDScriptDataType::NATIVE: {
+ int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_type.native_type];
+ class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS);
+ append(GDScriptFunction::OPCODE_CAST_TO_NATIVE, 3);
+ index = class_idx;
+ } break;
+ case GDScriptDataType::SCRIPT:
+ case GDScriptDataType::GDSCRIPT: {
+ Variant script = p_type.script_type;
+ int idx = get_constant_pos(script);
+ idx |= (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS);
+
+ append(GDScriptFunction::OPCODE_CAST_TO_SCRIPT, 3);
+ index = idx;
+ } break;
+ default: {
+ return;
+ }
+ }
+
+ append(p_source);
+ append(p_target);
+ append(index);
+}
+
+void GDScriptByteCodeGenerator::write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) {
+ append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_base);
+ append(p_target);
+ append(p_arguments.size());
+ append(p_function_name);
+}
+
+void GDScriptByteCodeGenerator::write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) {
+ append(GDScriptFunction::OPCODE_CALL_SELF_BASE, 1 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_target);
+ append(p_arguments.size());
+ append(p_function_name);
+}
+
+void GDScriptByteCodeGenerator::write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) {
+ append(GDScriptFunction::OPCODE_CALL_ASYNC, 2 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_base);
+ append(p_target);
+ append(p_arguments.size());
+ append(p_function_name);
+}
+
+void GDScriptByteCodeGenerator::write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) {
+ append(GDScriptFunction::OPCODE_CALL_BUILT_IN, 1 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_target);
+ append(p_arguments.size());
+ append(p_function);
+}
+
+void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) {
+ bool is_validated = false;
+
+ // Check if all types are correct.
+ if (Variant::is_builtin_method_vararg(p_type, p_method)) {
+ is_validated = true; // Vararg works fine with any argument, since they can be any type.
+ } else if (p_arguments.size() == Variant::get_builtin_method_argument_count(p_type, p_method)) {
+ bool all_types_exact = true;
+ for (int i = 0; i < p_arguments.size(); i++) {
+ if (!IS_BUILTIN_TYPE(p_arguments[i], Variant::get_builtin_method_argument_type(p_type, p_method, i))) {
+ all_types_exact = false;
+ break;
+ }
+ }
+
+ is_validated = all_types_exact;
+ }
+
+ if (!is_validated) {
+ // Perform regular call.
+ write_call(p_target, p_base, p_method, p_arguments);
+ return;
+ }
+
+ append(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size());
+
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_base);
+ append(p_target);
+ append(p_arguments.size());
+ append(Variant::get_validated_builtin_method(p_type, p_method));
+}
+
+void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) {
+ append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_base);
+ append(p_target);
+ append(p_arguments.size());
+ append(p_method);
+}
+
+void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) {
+#define CASE_TYPE(m_type) \
+ case Variant::m_type: \
+ append(GDScriptFunction::OPCODE_CALL_PTRCALL_##m_type, 2 + p_arguments.size()); \
+ break
+
+ bool is_ptrcall = true;
+
+ if (p_method->has_return()) {
+ MethodInfo info;
+ ClassDB::get_method_info(p_method->get_instance_class(), p_method->get_name(), &info);
+ switch (info.return_val.type) {
+ CASE_TYPE(BOOL);
+ CASE_TYPE(INT);
+ CASE_TYPE(FLOAT);
+ CASE_TYPE(STRING);
+ CASE_TYPE(VECTOR2);
+ CASE_TYPE(VECTOR2I);
+ CASE_TYPE(RECT2);
+ CASE_TYPE(RECT2I);
+ CASE_TYPE(VECTOR3);
+ CASE_TYPE(VECTOR3I);
+ CASE_TYPE(TRANSFORM2D);
+ CASE_TYPE(PLANE);
+ CASE_TYPE(AABB);
+ CASE_TYPE(BASIS);
+ CASE_TYPE(TRANSFORM);
+ CASE_TYPE(COLOR);
+ CASE_TYPE(STRING_NAME);
+ CASE_TYPE(NODE_PATH);
+ CASE_TYPE(RID);
+ CASE_TYPE(QUAT);
+ CASE_TYPE(OBJECT);
+ CASE_TYPE(CALLABLE);
+ CASE_TYPE(SIGNAL);
+ CASE_TYPE(DICTIONARY);
+ CASE_TYPE(ARRAY);
+ CASE_TYPE(PACKED_BYTE_ARRAY);
+ CASE_TYPE(PACKED_INT32_ARRAY);
+ CASE_TYPE(PACKED_INT64_ARRAY);
+ CASE_TYPE(PACKED_FLOAT32_ARRAY);
+ CASE_TYPE(PACKED_FLOAT64_ARRAY);
+ CASE_TYPE(PACKED_STRING_ARRAY);
+ CASE_TYPE(PACKED_VECTOR2_ARRAY);
+ CASE_TYPE(PACKED_VECTOR3_ARRAY);
+ CASE_TYPE(PACKED_COLOR_ARRAY);
+ default:
+ append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size());
+ is_ptrcall = false;
+ break;
+ }
+ } else {
+ append(GDScriptFunction::OPCODE_CALL_PTRCALL_NO_RETURN, 2 + p_arguments.size());
+ }
+
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_base);
+ append(p_target);
+ append(p_arguments.size());
+ append(p_method);
+ if (is_ptrcall) {
+ alloc_ptrcall(p_arguments.size());
+ }
+
+#undef CASE_TYPE
+}
+
+void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) {
+ append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS);
+ append(p_target);
+ append(p_arguments.size());
+ append(p_function_name);
+}
+
+void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) {
+ append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_base);
+ append(p_target);
+ append(p_arguments.size());
+ append(p_function_name);
+}
+
+void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) {
+ // Try to find an appropriate constructor.
+ bool all_have_type = true;
+ Vector<Variant::Type> arg_types;
+ for (int i = 0; i < p_arguments.size(); i++) {
+ if (!HAS_BUILTIN_TYPE(p_arguments[i])) {
+ all_have_type = false;
+ break;
+ }
+ arg_types.push_back(p_arguments[i].type.builtin_type);
+ }
+ if (all_have_type) {
+ int valid_constructor = -1;
+ for (int i = 0; i < Variant::get_constructor_count(p_type); i++) {
+ if (Variant::get_constructor_argument_count(p_type, i) != p_arguments.size()) {
+ continue;
+ }
+ int types_correct = true;
+ for (int j = 0; j < arg_types.size(); j++) {
+ if (arg_types[j] != Variant::get_constructor_argument_type(p_type, i, j)) {
+ types_correct = false;
+ break;
+ }
+ }
+ if (types_correct) {
+ valid_constructor = i;
+ break;
+ }
+ }
+ if (valid_constructor >= 0) {
+ append(GDScriptFunction::OPCODE_CONSTRUCT_VALIDATED, 1 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_target);
+ append(p_arguments.size());
+ append(Variant::get_validated_constructor(p_type, valid_constructor));
+ return;
+ }
+ }
+
+ append(GDScriptFunction::OPCODE_CONSTRUCT, 1 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_target);
+ append(p_arguments.size());
+ append(p_type);
+}
+
+void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) {
+ append(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY, 1 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_target);
+ append(p_arguments.size());
+}
+
+void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) {
+ append(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY, 1 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ append(p_target);
+ append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments.
+}
+
+void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) {
+ append(GDScriptFunction::OPCODE_AWAIT, 1);
+ append(p_operand);
+ append(GDScriptFunction::OPCODE_AWAIT_RESUME, 1);
+ append(p_target);
+}
+
+void GDScriptByteCodeGenerator::write_if(const Address &p_condition) {
+ append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1);
+ append(p_condition);
+ if_jmp_addrs.push_back(opcodes.size());
+ append(0); // Jump destination, will be patched.
+}
+
+void GDScriptByteCodeGenerator::write_else() {
+ append(GDScriptFunction::OPCODE_JUMP, 0); // Jump from true if block;
+ int else_jmp_addr = opcodes.size();
+ append(0); // Jump destination, will be patched.
+
+ patch_jump(if_jmp_addrs.back()->get());
+ if_jmp_addrs.pop_back();
+ if_jmp_addrs.push_back(else_jmp_addr);
+}
+
+void GDScriptByteCodeGenerator::write_endif() {
+ patch_jump(if_jmp_addrs.back()->get());
+ if_jmp_addrs.pop_back();
+}
+
+void GDScriptByteCodeGenerator::start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) {
+ Address counter(Address::LOCAL_VARIABLE, add_local("@counter_pos", p_iterator_type), p_iterator_type);
+ Address container(Address::LOCAL_VARIABLE, add_local("@container_pos", p_list_type), p_list_type);
+
+ // Store state.
+ for_counter_variables.push_back(counter);
+ for_container_variables.push_back(container);
+}
+
+void GDScriptByteCodeGenerator::write_for_assignment(const Address &p_variable, const Address &p_list) {
+ const Address &container = for_container_variables.back()->get();
+
+ // Assign container.
+ append(GDScriptFunction::OPCODE_ASSIGN, 2);
+ append(container);
+ append(p_list);
+
+ for_iterator_variables.push_back(p_variable);
+}
+
+void GDScriptByteCodeGenerator::write_for() {
+ const Address &iterator = for_iterator_variables.back()->get();
+ const Address &counter = for_counter_variables.back()->get();
+ const Address &container = for_container_variables.back()->get();
+
+ current_breaks_to_patch.push_back(List<int>());
+
+ GDScriptFunction::Opcode begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN;
+ GDScriptFunction::Opcode iterate_opcode = GDScriptFunction::OPCODE_ITERATE;
+
+ if (container.type.has_type) {
+ if (container.type.kind == GDScriptDataType::BUILTIN) {
+ switch (container.type.builtin_type) {
+ case Variant::INT:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_INT;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_INT;
+ break;
+ case Variant::FLOAT:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_FLOAT;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_FLOAT;
+ break;
+ case Variant::VECTOR2:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR2;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR2;
+ break;
+ case Variant::VECTOR2I:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR2I;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR2I;
+ break;
+ case Variant::VECTOR3:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR3;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR3;
+ break;
+ case Variant::VECTOR3I:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR3I;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR3I;
+ break;
+ case Variant::STRING:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_STRING;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_STRING;
+ break;
+ case Variant::DICTIONARY:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_DICTIONARY;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_DICTIONARY;
+ break;
+ case Variant::ARRAY:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_ARRAY;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_ARRAY;
+ break;
+ case Variant::PACKED_BYTE_ARRAY:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_BYTE_ARRAY;
+ break;
+ case Variant::PACKED_INT32_ARRAY:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_INT32_ARRAY;
+ break;
+ case Variant::PACKED_INT64_ARRAY:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_INT64_ARRAY;
+ break;
+ case Variant::PACKED_FLOAT32_ARRAY:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_FLOAT32_ARRAY;
+ break;
+ case Variant::PACKED_FLOAT64_ARRAY:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_FLOAT64_ARRAY;
+ break;
+ case Variant::PACKED_STRING_ARRAY:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_STRING_ARRAY;
+ break;
+ case Variant::PACKED_VECTOR2_ARRAY:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_VECTOR2_ARRAY;
+ break;
+ case Variant::PACKED_VECTOR3_ARRAY:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_VECTOR3_ARRAY;
+ break;
+ case Variant::PACKED_COLOR_ARRAY:
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_COLOR_ARRAY;
+ break;
+ default:
+ break;
+ }
+ } else {
+ begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_OBJECT;
+ iterate_opcode = GDScriptFunction::OPCODE_ITERATE_OBJECT;
+ }
+ }
+
+ // Begin loop.
+ append(begin_opcode, 3);
+ append(counter);
+ append(container);
+ append(iterator);
+ for_jmp_addrs.push_back(opcodes.size());
+ append(0); // End of loop address, will be patched.
+ append(GDScriptFunction::OPCODE_JUMP, 0);
+ append(opcodes.size() + 6); // Skip over 'continue' code.
+
+ // Next iteration.
+ int continue_addr = opcodes.size();
+ continue_addrs.push_back(continue_addr);
+ append(iterate_opcode, 3);
+ append(counter);
+ append(container);
+ append(iterator);
+ for_jmp_addrs.push_back(opcodes.size());
+ append(0); // Jump destination, will be patched.
+}
+
+void GDScriptByteCodeGenerator::write_endfor() {
+ // Jump back to loop check.
+ append(GDScriptFunction::OPCODE_JUMP, 0);
+ append(continue_addrs.back()->get());
+ continue_addrs.pop_back();
+
+ // Patch end jumps (two of them).
+ for (int i = 0; i < 2; i++) {
+ patch_jump(for_jmp_addrs.back()->get());
+ for_jmp_addrs.pop_back();
+ }
+
+ // Patch break statements.
+ for (const List<int>::Element *E = current_breaks_to_patch.back()->get().front(); E; E = E->next()) {
+ patch_jump(E->get());
+ }
+ current_breaks_to_patch.pop_back();
+
+ // Pop state.
+ for_iterator_variables.pop_back();
+ for_counter_variables.pop_back();
+ for_container_variables.pop_back();
+}
+
+void GDScriptByteCodeGenerator::start_while_condition() {
+ current_breaks_to_patch.push_back(List<int>());
+ continue_addrs.push_back(opcodes.size());
+}
+
+void GDScriptByteCodeGenerator::write_while(const Address &p_condition) {
+ // Condition check.
+ append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1);
+ append(p_condition);
+ while_jmp_addrs.push_back(opcodes.size());
+ append(0); // End of loop address, will be patched.
+}
+
+void GDScriptByteCodeGenerator::write_endwhile() {
+ // Jump back to loop check.
+ append(GDScriptFunction::OPCODE_JUMP, 0);
+ append(continue_addrs.back()->get());
+ continue_addrs.pop_back();
+
+ // Patch end jump.
+ patch_jump(while_jmp_addrs.back()->get());
+ while_jmp_addrs.pop_back();
+
+ // Patch break statements.
+ for (const List<int>::Element *E = current_breaks_to_patch.back()->get().front(); E; E = E->next()) {
+ patch_jump(E->get());
+ }
+ current_breaks_to_patch.pop_back();
+}
+
+void GDScriptByteCodeGenerator::start_match() {
+ match_continues_to_patch.push_back(List<int>());
+}
+
+void GDScriptByteCodeGenerator::start_match_branch() {
+ // Patch continue statements.
+ for (const List<int>::Element *E = match_continues_to_patch.back()->get().front(); E; E = E->next()) {
+ patch_jump(E->get());
+ }
+ match_continues_to_patch.pop_back();
+ // Start a new list for next branch.
+ match_continues_to_patch.push_back(List<int>());
+}
+
+void GDScriptByteCodeGenerator::end_match() {
+ // Patch continue statements.
+ for (const List<int>::Element *E = match_continues_to_patch.back()->get().front(); E; E = E->next()) {
+ patch_jump(E->get());
+ }
+ match_continues_to_patch.pop_back();
+}
+
+void GDScriptByteCodeGenerator::write_break() {
+ append(GDScriptFunction::OPCODE_JUMP, 0);
+ current_breaks_to_patch.back()->get().push_back(opcodes.size());
+ append(0);
+}
+
+void GDScriptByteCodeGenerator::write_continue() {
+ append(GDScriptFunction::OPCODE_JUMP, 0);
+ append(continue_addrs.back()->get());
+}
+
+void GDScriptByteCodeGenerator::write_continue_match() {
+ append(GDScriptFunction::OPCODE_JUMP, 0);
+ match_continues_to_patch.back()->get().push_back(opcodes.size());
+ append(0);
+}
+
+void GDScriptByteCodeGenerator::write_breakpoint() {
+ append(GDScriptFunction::OPCODE_BREAKPOINT, 0);
+}
+
+void GDScriptByteCodeGenerator::write_newline(int p_line) {
+ append(GDScriptFunction::OPCODE_LINE, 0);
+ append(p_line);
+ current_line = p_line;
+}
+
+void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
+ append(GDScriptFunction::OPCODE_RETURN, 1);
+ append(p_return_value);
+}
+
+void GDScriptByteCodeGenerator::write_assert(const Address &p_test, const Address &p_message) {
+ append(GDScriptFunction::OPCODE_ASSERT, 2);
+ append(p_test);
+ append(p_message);
+}
+
+void GDScriptByteCodeGenerator::start_block() {
+ push_stack_identifiers();
+}
+
+void GDScriptByteCodeGenerator::end_block() {
+ pop_stack_identifiers();
+}
+
+GDScriptByteCodeGenerator::~GDScriptByteCodeGenerator() {
+ if (!ended && function != nullptr) {
+ memdelete(function);
+ }
+}
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
new file mode 100644
index 0000000000..5cbd12a0ba
--- /dev/null
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -0,0 +1,443 @@
+/*************************************************************************/
+/* gdscript_byte_codegen.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_BYTE_CODEGEN
+#define GDSCRIPT_BYTE_CODEGEN
+
+#include "gdscript_codegen.h"
+
+#include "gdscript_function.h"
+
+class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
+ bool ended = false;
+ GDScriptFunction *function = nullptr;
+ bool debug_stack = false;
+
+ Vector<int> opcodes;
+ List<Map<StringName, int>> stack_id_stack;
+ Map<StringName, int> stack_identifiers;
+ List<int> stack_identifiers_counts;
+ Map<StringName, int> local_constants;
+
+ List<GDScriptFunction::StackDebug> stack_debug;
+ List<Map<StringName, int>> block_identifier_stack;
+ Map<StringName, int> block_identifiers;
+
+ int current_stack_size = 0;
+ int current_temporaries = 0;
+ int current_locals = 0;
+ int current_line = 0;
+ int stack_max = 0;
+ int instr_args_max = 0;
+ int ptrcall_max = 0;
+
+#ifdef DEBUG_ENABLED
+ List<int> temp_stack;
+#endif
+
+ HashMap<Variant, int, VariantHasher, VariantComparator> constant_map;
+ Map<StringName, int> name_map;
+#ifdef TOOLS_ENABLED
+ Vector<StringName> named_globals;
+#endif
+ Map<Variant::ValidatedOperatorEvaluator, int> operator_func_map;
+ Map<Variant::ValidatedSetter, int> setters_map;
+ Map<Variant::ValidatedGetter, int> getters_map;
+ Map<Variant::ValidatedKeyedSetter, int> keyed_setters_map;
+ Map<Variant::ValidatedKeyedGetter, int> keyed_getters_map;
+ Map<Variant::ValidatedIndexedSetter, int> indexed_setters_map;
+ Map<Variant::ValidatedIndexedGetter, int> indexed_getters_map;
+ Map<Variant::ValidatedBuiltInMethod, int> builtin_method_map;
+ Map<Variant::ValidatedConstructor, int> constructors_map;
+ Map<MethodBind *, int> method_bind_map;
+
+ // Lists since these can be nested.
+ List<int> if_jmp_addrs;
+ List<int> for_jmp_addrs;
+ List<Address> for_iterator_variables;
+ List<Address> for_counter_variables;
+ List<Address> for_container_variables;
+ List<int> while_jmp_addrs;
+ List<int> continue_addrs;
+
+ // Used to patch jumps with `and` and `or` operators with short-circuit.
+ List<int> logic_op_jump_pos1;
+ List<int> logic_op_jump_pos2;
+
+ List<Address> ternary_result;
+ List<int> ternary_jump_fail_pos;
+ List<int> ternary_jump_skip_pos;
+
+ List<List<int>> current_breaks_to_patch;
+ List<List<int>> match_continues_to_patch;
+
+ void add_stack_identifier(const StringName &p_id, int p_stackpos) {
+ current_locals++;
+ stack_identifiers[p_id] = p_stackpos;
+ if (debug_stack) {
+ block_identifiers[p_id] = p_stackpos;
+ GDScriptFunction::StackDebug sd;
+ sd.added = true;
+ sd.line = current_line;
+ sd.identifier = p_id;
+ sd.pos = p_stackpos;
+ stack_debug.push_back(sd);
+ }
+ }
+
+ void push_stack_identifiers() {
+ stack_identifiers_counts.push_back(current_locals);
+ stack_id_stack.push_back(stack_identifiers);
+ if (debug_stack) {
+ Map<StringName, int> block_ids(block_identifiers);
+ block_identifier_stack.push_back(block_ids);
+ block_identifiers.clear();
+ }
+ }
+
+ void pop_stack_identifiers() {
+ current_locals = stack_identifiers_counts.back()->get();
+ stack_identifiers_counts.pop_back();
+ stack_identifiers = stack_id_stack.back()->get();
+ stack_id_stack.pop_back();
+#ifdef DEBUG_ENABLED
+ if (current_temporaries != 0) {
+ ERR_PRINT("Leaving block with non-zero temporary variables: " + itos(current_temporaries));
+ }
+#endif
+ current_stack_size = current_locals;
+
+ if (debug_stack) {
+ for (Map<StringName, int>::Element *E = block_identifiers.front(); E; E = E->next()) {
+ GDScriptFunction::StackDebug sd;
+ sd.added = false;
+ sd.identifier = E->key();
+ sd.line = current_line;
+ sd.pos = E->get();
+ stack_debug.push_back(sd);
+ }
+ block_identifiers = block_identifier_stack.back()->get();
+ block_identifier_stack.pop_back();
+ }
+ }
+
+ int get_name_map_pos(const StringName &p_identifier) {
+ int ret;
+ if (!name_map.has(p_identifier)) {
+ ret = name_map.size();
+ name_map[p_identifier] = ret;
+ } else {
+ ret = name_map[p_identifier];
+ }
+ return ret;
+ }
+
+ int get_constant_pos(const Variant &p_constant) {
+ if (constant_map.has(p_constant))
+ return constant_map[p_constant];
+ int pos = constant_map.size();
+ constant_map[p_constant] = pos;
+ return pos;
+ }
+
+ int get_operation_pos(const Variant::ValidatedOperatorEvaluator p_operation) {
+ if (operator_func_map.has(p_operation))
+ return operator_func_map[p_operation];
+ int pos = operator_func_map.size();
+ operator_func_map[p_operation] = pos;
+ return pos;
+ }
+
+ int get_setter_pos(const Variant::ValidatedSetter p_setter) {
+ if (setters_map.has(p_setter))
+ return setters_map[p_setter];
+ int pos = setters_map.size();
+ setters_map[p_setter] = pos;
+ return pos;
+ }
+
+ int get_getter_pos(const Variant::ValidatedGetter p_getter) {
+ if (getters_map.has(p_getter))
+ return getters_map[p_getter];
+ int pos = getters_map.size();
+ getters_map[p_getter] = pos;
+ return pos;
+ }
+
+ int get_keyed_setter_pos(const Variant::ValidatedKeyedSetter p_keyed_setter) {
+ if (keyed_setters_map.has(p_keyed_setter))
+ return keyed_setters_map[p_keyed_setter];
+ int pos = keyed_setters_map.size();
+ keyed_setters_map[p_keyed_setter] = pos;
+ return pos;
+ }
+
+ int get_keyed_getter_pos(const Variant::ValidatedKeyedGetter p_keyed_getter) {
+ if (keyed_getters_map.has(p_keyed_getter))
+ return keyed_getters_map[p_keyed_getter];
+ int pos = keyed_getters_map.size();
+ keyed_getters_map[p_keyed_getter] = pos;
+ return pos;
+ }
+
+ int get_indexed_setter_pos(const Variant::ValidatedIndexedSetter p_indexed_setter) {
+ if (indexed_setters_map.has(p_indexed_setter))
+ return indexed_setters_map[p_indexed_setter];
+ int pos = indexed_setters_map.size();
+ indexed_setters_map[p_indexed_setter] = pos;
+ return pos;
+ }
+
+ int get_indexed_getter_pos(const Variant::ValidatedIndexedGetter p_indexed_getter) {
+ if (indexed_getters_map.has(p_indexed_getter))
+ return indexed_getters_map[p_indexed_getter];
+ int pos = indexed_getters_map.size();
+ indexed_getters_map[p_indexed_getter] = pos;
+ return pos;
+ }
+
+ int get_builtin_method_pos(const Variant::ValidatedBuiltInMethod p_method) {
+ if (builtin_method_map.has(p_method)) {
+ return builtin_method_map[p_method];
+ }
+ int pos = builtin_method_map.size();
+ builtin_method_map[p_method] = pos;
+ return pos;
+ }
+
+ int get_constructor_pos(const Variant::ValidatedConstructor p_constructor) {
+ if (constructors_map.has(p_constructor)) {
+ return constructors_map[p_constructor];
+ }
+ int pos = constructors_map.size();
+ constructors_map[p_constructor] = pos;
+ return pos;
+ }
+
+ int get_method_bind_pos(MethodBind *p_method) {
+ if (method_bind_map.has(p_method)) {
+ return method_bind_map[p_method];
+ }
+ int pos = method_bind_map.size();
+ method_bind_map[p_method] = pos;
+ return pos;
+ }
+
+ void alloc_stack(int p_level) {
+ if (p_level >= stack_max)
+ stack_max = p_level + 1;
+ }
+
+ int increase_stack() {
+ int top = current_stack_size++;
+ alloc_stack(current_stack_size);
+ return top;
+ }
+
+ void alloc_ptrcall(int p_params) {
+ if (p_params >= ptrcall_max)
+ ptrcall_max = p_params;
+ }
+
+ int address_of(const Address &p_address) {
+ switch (p_address.mode) {
+ case Address::SELF:
+ return GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS;
+ case Address::CLASS:
+ return GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS;
+ case Address::MEMBER:
+ return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS);
+ case Address::CLASS_CONSTANT:
+ return p_address.address | (GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS);
+ case Address::LOCAL_CONSTANT:
+ case Address::CONSTANT:
+ return p_address.address | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS);
+ case Address::LOCAL_VARIABLE:
+ case Address::TEMPORARY:
+ case Address::FUNCTION_PARAMETER:
+ return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
+ case Address::GLOBAL:
+ return p_address.address | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS);
+ case Address::NAMED_GLOBAL:
+ return p_address.address | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS);
+ case Address::NIL:
+ return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS;
+ }
+ return -1; // Unreachable.
+ }
+
+ void append(GDScriptFunction::Opcode p_code, int p_argument_count) {
+ opcodes.push_back((p_code & GDScriptFunction::INSTR_MASK) | (p_argument_count << GDScriptFunction::INSTR_BITS));
+ instr_args_max = MAX(instr_args_max, p_argument_count);
+ }
+
+ void append(int p_code) {
+ opcodes.push_back(p_code);
+ }
+
+ void append(const Address &p_address) {
+ opcodes.push_back(address_of(p_address));
+ }
+
+ void append(const StringName &p_name) {
+ opcodes.push_back(get_name_map_pos(p_name));
+ }
+
+ void append(const Variant::ValidatedOperatorEvaluator p_operation) {
+ opcodes.push_back(get_operation_pos(p_operation));
+ }
+
+ void append(const Variant::ValidatedSetter p_setter) {
+ opcodes.push_back(get_setter_pos(p_setter));
+ }
+
+ void append(const Variant::ValidatedGetter p_getter) {
+ opcodes.push_back(get_getter_pos(p_getter));
+ }
+
+ void append(const Variant::ValidatedKeyedSetter p_keyed_setter) {
+ opcodes.push_back(get_keyed_setter_pos(p_keyed_setter));
+ }
+
+ void append(const Variant::ValidatedKeyedGetter p_keyed_getter) {
+ opcodes.push_back(get_keyed_getter_pos(p_keyed_getter));
+ }
+
+ void append(const Variant::ValidatedIndexedSetter p_indexed_setter) {
+ opcodes.push_back(get_indexed_setter_pos(p_indexed_setter));
+ }
+
+ void append(const Variant::ValidatedIndexedGetter p_indexed_getter) {
+ opcodes.push_back(get_indexed_getter_pos(p_indexed_getter));
+ }
+
+ void append(const Variant::ValidatedBuiltInMethod p_method) {
+ opcodes.push_back(get_builtin_method_pos(p_method));
+ }
+
+ void append(const Variant::ValidatedConstructor p_constructor) {
+ opcodes.push_back(get_constructor_pos(p_constructor));
+ }
+
+ void append(MethodBind *p_method) {
+ opcodes.push_back(get_method_bind_pos(p_method));
+ }
+
+ void patch_jump(int p_address) {
+ opcodes.write[p_address] = opcodes.size();
+ }
+
+public:
+ virtual uint32_t add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) override;
+ virtual uint32_t add_local(const StringName &p_name, const GDScriptDataType &p_type) override;
+ virtual uint32_t add_local_constant(const StringName &p_name, const Variant &p_constant) override;
+ virtual uint32_t add_or_get_constant(const Variant &p_constant) override;
+ virtual uint32_t add_or_get_name(const StringName &p_name) override;
+ virtual uint32_t add_temporary() override;
+ virtual void pop_temporary() override;
+
+ virtual void start_parameters() override;
+ virtual void end_parameters() override;
+
+ virtual void start_block() override;
+ virtual void end_block() override;
+
+ virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) override;
+ virtual GDScriptFunction *write_end() override;
+
+#ifdef DEBUG_ENABLED
+ virtual void set_signature(const String &p_signature) override;
+#endif
+ virtual void set_initial_line(int p_line) override;
+
+ virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) override;
+ virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) override;
+ virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) override;
+ virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) override;
+ virtual void write_and_left_operand(const Address &p_left_operand) override;
+ virtual void write_and_right_operand(const Address &p_right_operand) override;
+ virtual void write_end_and(const Address &p_target) override;
+ virtual void write_or_left_operand(const Address &p_left_operand) override;
+ virtual void write_or_right_operand(const Address &p_right_operand) override;
+ virtual void write_end_or(const Address &p_target) override;
+ virtual void write_start_ternary(const Address &p_target) override;
+ virtual void write_ternary_condition(const Address &p_condition) override;
+ virtual void write_ternary_true_expr(const Address &p_expr) override;
+ virtual void write_ternary_false_expr(const Address &p_expr) override;
+ virtual void write_end_ternary() override;
+ virtual void write_set(const Address &p_target, const Address &p_index, const Address &p_source) override;
+ virtual void write_get(const Address &p_target, const Address &p_index, const Address &p_source) override;
+ virtual void write_set_named(const Address &p_target, const StringName &p_name, const Address &p_source) override;
+ virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) override;
+ virtual void write_set_member(const Address &p_value, const StringName &p_name) override;
+ virtual void write_get_member(const Address &p_target, const StringName &p_name) override;
+ virtual void write_assign(const Address &p_target, const Address &p_source) override;
+ virtual void write_assign_true(const Address &p_target) override;
+ virtual void write_assign_false(const Address &p_target) override;
+ virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src) override;
+ virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override;
+ virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
+ virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
+ virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
+ virtual void write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) override;
+ virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override;
+ virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
+ virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
+ virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
+ virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
+ virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) override;
+ virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override;
+ virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override;
+ virtual void write_await(const Address &p_target, const Address &p_operand) override;
+ virtual void write_if(const Address &p_condition) override;
+ virtual void write_else() override;
+ virtual void write_endif() override;
+ virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) override;
+ virtual void write_for_assignment(const Address &p_variable, const Address &p_list) override;
+ virtual void write_for() override;
+ virtual void write_endfor() override;
+ virtual void start_while_condition() override;
+ virtual void write_while(const Address &p_condition) override;
+ virtual void write_endwhile() override;
+ virtual void start_match() override;
+ virtual void start_match_branch() override;
+ virtual void end_match() override;
+ virtual void write_break() override;
+ virtual void write_continue() override;
+ virtual void write_continue_match() override;
+ virtual void write_breakpoint() override;
+ virtual void write_newline(int p_line) override;
+ virtual void write_return(const Address &p_return_value) override;
+ virtual void write_assert(const Address &p_test, const Address &p_message) override;
+
+ virtual ~GDScriptByteCodeGenerator();
+};
+
+#endif // GDSCRIPT_BYTE_CODEGEN
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 583283ff46..95d24a8b08 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -31,7 +31,7 @@
#include "gdscript_cache.h"
#include "core/os/file_access.h"
-#include "core/vector.h"
+#include "core/templates/vector.h"
#include "gdscript.h"
#include "gdscript_analyzer.h"
#include "gdscript_parser.h"
@@ -85,6 +85,17 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
return result;
}
}
+ if (result != OK) {
+ if (parser != nullptr) {
+ memdelete(parser);
+ parser = nullptr;
+ }
+ if (analyzer != nullptr) {
+ memdelete(analyzer);
+ analyzer = nullptr;
+ }
+ return result;
+ }
}
return result;
@@ -97,33 +108,36 @@ GDScriptParserRef::~GDScriptParserRef() {
if (analyzer != nullptr) {
memdelete(analyzer);
}
- MutexLock(GDScriptCache::singleton->lock);
+ MutexLock lock(GDScriptCache::singleton->lock);
GDScriptCache::singleton->parser_map.erase(path);
}
GDScriptCache *GDScriptCache::singleton = nullptr;
void GDScriptCache::remove_script(const String &p_path) {
- MutexLock(singleton->lock);
+ MutexLock lock(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);
+ MutexLock lock(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];
+ ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]);
} else {
+ if (!FileAccess::exists(p_path)) {
+ r_error = ERR_FILE_NOT_FOUND;
+ return ref;
+ }
GDScriptParser *parser = memnew(GDScriptParser);
ref.instance();
ref->parser = parser;
ref->path = p_path;
- singleton->parser_map[p_path] = ref;
- ref->unreference();
+ singleton->parser_map[p_path] = ref.ptr();
}
r_error = ref->raise_status(p_status);
@@ -154,7 +168,7 @@ String GDScriptCache::get_source_code(const String &p_path) {
}
Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const String &p_owner) {
- MutexLock(singleton->lock);
+ MutexLock lock(singleton->lock);
if (p_owner != String()) {
singleton->dependencies[p_owner].insert(p_path);
}
@@ -171,15 +185,12 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri
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();
+ singleton->shallow_gdscript_cache[p_path] = script.ptr();
return script;
}
Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner) {
- MutexLock(singleton->lock);
+ MutexLock lock(singleton->lock);
if (p_owner != String()) {
singleton->dependencies[p_owner].insert(p_path);
@@ -202,7 +213,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
return script;
}
- singleton->full_gdscript_cache[p_path] = script;
+ singleton->full_gdscript_cache[p_path] = script.ptr();
singleton->shallow_gdscript_cache.erase(p_path);
return script;
@@ -211,7 +222,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
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->full_gdscript_cache[p_owner] = script.ptr();
singleton->shallow_gdscript_cache.erase(p_owner);
Set<String> depends = singleton->dependencies[p_owner];
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index 770704d6eb..90c5884985 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -31,10 +31,10 @@
#ifndef GDSCRIPT_CACHE_H
#define GDSCRIPT_CACHE_H
-#include "core/hash_map.h"
+#include "core/object/reference.h"
#include "core/os/mutex.h"
-#include "core/reference.h"
-#include "core/set.h"
+#include "core/templates/hash_map.h"
+#include "core/templates/set.h"
#include "gdscript.h"
class GDScriptAnalyzer;
@@ -70,9 +70,9 @@ public:
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, GDScriptParserRef *> parser_map;
+ HashMap<String, GDScript *> shallow_gdscript_cache;
+ HashMap<String, GDScript *> full_gdscript_cache;
HashMap<String, Set<String>> dependencies;
friend class GDScript;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
new file mode 100644
index 0000000000..559f9b8406
--- /dev/null
+++ b/modules/gdscript/gdscript_codegen.h
@@ -0,0 +1,165 @@
+/*************************************************************************/
+/* gdscript_codegen.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_CODEGEN
+#define GDSCRIPT_CODEGEN
+
+#include "core/io/multiplayer_api.h"
+#include "core/string/string_name.h"
+#include "core/variant/variant.h"
+#include "gdscript_function.h"
+#include "gdscript_functions.h"
+
+class GDScriptCodeGenerator {
+public:
+ struct Address {
+ enum AddressMode {
+ SELF,
+ CLASS,
+ MEMBER,
+ CONSTANT,
+ CLASS_CONSTANT,
+ LOCAL_CONSTANT,
+ LOCAL_VARIABLE,
+ FUNCTION_PARAMETER,
+ TEMPORARY,
+ GLOBAL,
+ NAMED_GLOBAL,
+ NIL,
+ };
+ AddressMode mode = NIL;
+ uint32_t address = 0;
+ GDScriptDataType type;
+
+ Address() {}
+ Address(AddressMode p_mode, const GDScriptDataType &p_type = GDScriptDataType()) {
+ mode = p_mode;
+ type = p_type;
+ }
+ Address(AddressMode p_mode, uint32_t p_address, const GDScriptDataType &p_type = GDScriptDataType()) {
+ mode = p_mode,
+ address = p_address;
+ type = p_type;
+ }
+ };
+
+ virtual uint32_t add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) = 0;
+ virtual uint32_t add_local(const StringName &p_name, const GDScriptDataType &p_type) = 0;
+ virtual uint32_t add_local_constant(const StringName &p_name, const Variant &p_constant) = 0;
+ virtual uint32_t add_or_get_constant(const Variant &p_constant) = 0;
+ virtual uint32_t add_or_get_name(const StringName &p_name) = 0;
+ virtual uint32_t add_temporary() = 0;
+ virtual void pop_temporary() = 0;
+
+ virtual void start_parameters() = 0;
+ virtual void end_parameters() = 0;
+
+ virtual void start_block() = 0;
+ virtual void end_block() = 0;
+
+ // virtual int get_max_stack_level() = 0;
+ // virtual int get_max_function_arguments() = 0;
+
+ virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) = 0;
+ virtual GDScriptFunction *write_end() = 0;
+
+#ifdef DEBUG_ENABLED
+ virtual void set_signature(const String &p_signature) = 0;
+#endif
+ virtual void set_initial_line(int p_line) = 0;
+
+ // virtual void alloc_stack(int p_level) = 0; // Is this needed?
+ // virtual void alloc_call(int p_arg_count) = 0; // This might be automatic from other functions.
+
+ virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) = 0;
+ virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) = 0;
+ virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) = 0;
+ virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) = 0;
+ virtual void write_and_left_operand(const Address &p_left_operand) = 0;
+ virtual void write_and_right_operand(const Address &p_right_operand) = 0;
+ virtual void write_end_and(const Address &p_target) = 0;
+ virtual void write_or_left_operand(const Address &p_left_operand) = 0;
+ virtual void write_or_right_operand(const Address &p_right_operand) = 0;
+ virtual void write_end_or(const Address &p_target) = 0;
+ virtual void write_start_ternary(const Address &p_target) = 0;
+ virtual void write_ternary_condition(const Address &p_condition) = 0;
+ virtual void write_ternary_true_expr(const Address &p_expr) = 0;
+ virtual void write_ternary_false_expr(const Address &p_expr) = 0;
+ virtual void write_end_ternary() = 0;
+ virtual void write_set(const Address &p_target, const Address &p_index, const Address &p_source) = 0;
+ virtual void write_get(const Address &p_target, const Address &p_index, const Address &p_source) = 0;
+ virtual void write_set_named(const Address &p_target, const StringName &p_name, const Address &p_source) = 0;
+ virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) = 0;
+ virtual void write_set_member(const Address &p_value, const StringName &p_name) = 0;
+ virtual void write_get_member(const Address &p_target, const StringName &p_name) = 0;
+ virtual void write_assign(const Address &p_target, const Address &p_source) = 0;
+ virtual void write_assign_true(const Address &p_target) = 0;
+ virtual void write_assign_false(const Address &p_target) = 0;
+ virtual void write_assign_default_parameter(const Address &dst, const Address &src) = 0;
+ virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0;
+ virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
+ virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
+ virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
+ virtual void write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) = 0;
+ virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
+ virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
+ virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
+ virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
+ virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
+ virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) = 0;
+ virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0;
+ virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0;
+ virtual void write_await(const Address &p_target, const Address &p_operand) = 0;
+ virtual void write_if(const Address &p_condition) = 0;
+ // virtual void write_elseif(const Address &p_condition) = 0; This kind of makes things more difficult for no real benefit.
+ virtual void write_else() = 0;
+ virtual void write_endif() = 0;
+ virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) = 0;
+ virtual void write_for_assignment(const Address &p_variable, const Address &p_list) = 0;
+ virtual void write_for() = 0;
+ virtual void write_endfor() = 0;
+ virtual void start_while_condition() = 0; // Used to allow a jump to the expression evaluation.
+ virtual void write_while(const Address &p_condition) = 0;
+ virtual void write_endwhile() = 0;
+ virtual void start_match() = 0;
+ virtual void start_match_branch() = 0;
+ virtual void end_match() = 0;
+ virtual void write_break() = 0;
+ virtual void write_continue() = 0;
+ virtual void write_continue_match() = 0;
+ virtual void write_breakpoint() = 0;
+ virtual void write_newline(int p_line) = 0;
+ virtual void write_return(const Address &p_return_value) = 0;
+ virtual void write_assert(const Address &p_test, const Address &p_message) = 0;
+
+ virtual ~GDScriptCodeGenerator() {}
+};
+
+#endif // GDSCRIPT_CODEGEN
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index e34d87f5cc..e3f058886f 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -31,6 +31,7 @@
#include "gdscript_compiler.h"
#include "gdscript.h"
+#include "gdscript_byte_codegen.h"
#include "gdscript_cache.h"
bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) {
@@ -38,7 +39,7 @@ bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringN
return false;
}
- if (codegen.stack_identifiers.has(p_name)) {
+ if (codegen.locals.has(p_name)) {
return false; //shadowed
}
@@ -75,46 +76,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N
}
}
-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;
- }
-
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); // perform operator
- codegen.opcodes.push_back(op); //which operator
- codegen.opcodes.push_back(src_address_a); // argument 1
- codegen.opcodes.push_back(src_address_a); // argument 2 (repeated)
- //codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_NIL); // argument 2 (unary only takes one parameter)
- return true;
-}
-
-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;
- }
- if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- p_stack_level++; //uses stack for return, increase stack
- }
-
- int src_address_b = _parse_expression(codegen, p_right_operand, p_stack_level, false, p_initializer);
- if (src_address_b < 0) {
- return false;
- }
-
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); // perform operator
- codegen.opcodes.push_back(op); //which operator
- codegen.opcodes.push_back(src_address_a); // argument 1
- codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
- 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 {
+GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) const {
if (!p_datatype.is_set() || !p_datatype.is_hard_type()) {
return GDScriptDataType();
}
@@ -136,7 +98,8 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
} break;
case GDScriptParser::DataType::SCRIPT: {
result.kind = GDScriptDataType::SCRIPT;
- result.script_type = p_datatype.script_type;
+ result.script_type_ref = Ref<Script>(p_datatype.script_type);
+ result.script_type = result.script_type_ref.ptr();
result.native_type = result.script_type->get_instance_base_type();
} break;
case GDScriptParser::DataType::CLASS: {
@@ -162,11 +125,13 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
names.pop_back();
}
result.kind = GDScriptDataType::GDSCRIPT;
- result.script_type = script;
+ result.script_type_ref = script;
+ result.script_type = result.script_type_ref.ptr();
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.script_type_ref = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path);
+ result.script_type = result.script_type_ref.ptr();
result.native_type = p_datatype.native_type;
}
}
@@ -187,202 +152,115 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
}
}
+ // Only hold strong reference to the script if it's not the owner of the
+ // element qualified with this type, to avoid cyclic references (leaks).
+ if (result.script_type && result.script_type == p_owner) {
+ result.script_type_ref = Ref<Script>();
+ }
+
return result;
}
-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_assignment->operation) {
- case GDScriptParser::AssignmentNode::OP_ADDITION:
- var_op = Variant::OP_ADD;
- break;
- case GDScriptParser::AssignmentNode::OP_SUBTRACTION:
- var_op = Variant::OP_SUBTRACT;
- break;
- case GDScriptParser::AssignmentNode::OP_MULTIPLICATION:
- var_op = Variant::OP_MULTIPLY;
- break;
- case GDScriptParser::AssignmentNode::OP_DIVISION:
- var_op = Variant::OP_DIVIDE;
- break;
- case GDScriptParser::AssignmentNode::OP_MODULO:
- var_op = Variant::OP_MODULE;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_LEFT:
- var_op = Variant::OP_SHIFT_LEFT;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_RIGHT:
- var_op = Variant::OP_SHIFT_RIGHT;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_AND:
- var_op = Variant::OP_BIT_AND;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_OR:
- var_op = Variant::OP_BIT_OR;
- break;
- case GDScriptParser::AssignmentNode::OP_BIT_XOR:
- var_op = Variant::OP_BIT_XOR;
- break;
- case GDScriptParser::AssignmentNode::OP_NONE: {
- //none
- } break;
- default: {
- ERR_FAIL_V(-1);
- }
+static bool _is_exact_type(const PropertyInfo &p_par_type, const GDScriptDataType &p_arg_type) {
+ if (!p_arg_type.has_type) {
+ return false;
}
-
- // bool initializer = p_expression->op == GDScriptParser::OperatorNode::OP_INIT_ASSIGN;
-
- if (var_op == Variant::OP_MAX) {
- return _parse_expression(codegen, p_assignment->assigned_value, p_stack_level, false, false);
+ if (p_par_type.type == Variant::NIL) {
+ return false;
}
-
- if (!_create_binary_operator(codegen, p_assignment->assignee, p_assignment->assigned_value, var_op, p_stack_level, false, p_index_addr)) {
- return -1;
+ if (p_par_type.type == Variant::OBJECT) {
+ if (p_arg_type.kind == GDScriptDataType::BUILTIN) {
+ return false;
+ }
+ StringName class_name;
+ if (p_arg_type.kind == GDScriptDataType::NATIVE) {
+ class_name = p_arg_type.native_type;
+ } else {
+ class_name = p_arg_type.native_type == StringName() ? p_arg_type.script_type->get_instance_base_type() : p_arg_type.native_type;
+ }
+ return p_par_type.class_name == class_name || ClassDB::is_parent_class(class_name, p_par_type.class_name);
+ } else {
+ if (p_arg_type.kind != GDScriptDataType::BUILTIN) {
+ return false;
+ }
+ return p_par_type.type == p_arg_type.builtin_type;
}
-
- 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;
}
-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)
+static bool _have_exact_arguments(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) {
+ if (p_method->get_argument_count() != p_arguments.size()) {
+ // ptrcall won't work with default arguments.
+ return false;
+ }
+ MethodInfo info;
+ ClassDB::get_method_info(p_method->get_instance_class(), p_method->get_name(), &info);
+ for (int i = 0; i < p_arguments.size(); i++) {
+ const PropertyInfo &prop = info.arguments[i];
+ if (!_is_exact_type(prop, p_arguments[i].type)) {
+ return false;
}
}
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) {
+GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) {
if (p_expression->is_constant) {
- return codegen.get_constant_pos(p_expression->reduced_value);
+ return codegen.add_constant(p_expression->reduced_value);
}
+ GDScriptCodeGenerator *gen = codegen.generator;
+
switch (p_expression->type) {
- //should parse variable declaration and adjust stack accordingly...
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
-
- //This could be made much simpler by just indexing "self", but done this way (with custom self-addressing modes) increases performance a lot.
-
+ // Look for identifiers in current scope.
const GDScriptParser::IdentifierNode *in = static_cast<const GDScriptParser::IdentifierNode *>(p_expression);
StringName identifier = in->name;
- // TRY STACK!
- if (!p_initializer && codegen.stack_identifiers.has(identifier)) {
- int pos = codegen.stack_identifiers[identifier];
- return pos | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS);
+ // Try function parameters.
+ if (codegen.parameters.has(identifier)) {
+ return codegen.parameters[identifier];
}
- // 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 local variables and constants.
+ if (!p_initializer && codegen.locals.has(identifier)) {
+ return codegen.locals[identifier];
}
- // TRY CLASS MEMBER
+ // Try class members.
if (_is_class_member_property(codegen, identifier)) {
- //get property
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET_MEMBER); // perform operator
- 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;
+ // Get property.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Could get the type of the class member here.
+ gen->write_get_member(temp, identifier);
+ return temp;
}
- //TRY MEMBERS!
+ // Try members.
if (!codegen.function_node || !codegen.function_node->is_static) {
- // TRY MEMBER VARIABLES!
- //static function
+ // Try member variables.
if (codegen.script->member_indices.has(identifier)) {
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;
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary();
+ Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
+ gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args);
+ return temp;
} else {
- // No getter or inside getter: direct member access.
+ // 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)
+ return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier));
}
}
}
- //TRY CLASS CONSTANTS
-
+ // Try class constants.
GDScript *owner = codegen.script;
while (owner) {
GDScript *scr = owner;
GDScriptNativeClass *nc = nullptr;
while (scr) {
if (scr->constants.has(identifier)) {
- //int idx=scr->constants[identifier];
- int idx = codegen.get_name_map_pos(identifier);
- return idx | (GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root)
+ return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS_CONSTANT, gen->add_or_get_name(identifier)); // TODO: Get type here.
}
if (scr->native.is_valid()) {
nc = scr->native.ptr();
@@ -390,52 +268,37 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
scr = scr->_base;
}
- // CLASS C++ Integer Constant
-
+ // Class C++ integer constant.
if (nc) {
bool success = false;
int constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success);
if (success) {
- Variant key = constant;
- int idx;
-
- if (!codegen.constant_map.has(key)) {
- idx = codegen.constant_map.size();
- codegen.constant_map[key] = idx;
-
- } else {
- idx = codegen.constant_map[key];
- }
-
- return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access)
+ return codegen.add_constant(constant);
}
}
owner = owner->_owner;
}
- // TRY SIGNALS AND METHODS (can be made callables)
+ // 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;
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here.
+ GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF);
+
+ gen->write_get_named(temp, identifier, self);
+ return temp;
}
}
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)
+ return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::GLOBAL, idx); // TODO: Get type.
}
- /* TRY GLOBAL CLASSES */
-
+ // Try global classes.
if (ScriptServer::is_global_class(identifier)) {
const GDScriptParser::ClassNode *class_node = codegen.class_node;
while (class_node->outer) {
@@ -450,356 +313,244 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
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;
+ r_error = ERR_COMPILATION_FAILED;
+ return GDScriptCodeGenerator::Address();
}
}
- Variant key = res;
- int idx;
-
- if (!codegen.constant_map.has(key)) {
- idx = codegen.constant_map.size();
- codegen.constant_map[key] = idx;
-
- } else {
- idx = codegen.constant_map[key];
- }
-
- return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access)
+ return codegen.add_constant(res);
}
#ifdef TOOLS_ENABLED
if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) {
- int idx = codegen.named_globals.find(identifier);
- if (idx == -1) {
- idx = codegen.named_globals.size();
- codegen.named_globals.push_back(identifier);
- }
- return idx | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS);
+ return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NAMED_GLOBAL, gen->add_or_get_name(identifier)); // TODO: Get type.
}
#endif
- //not found, error
-
+ // Not found, error.
_set_error("Identifier not found: " + String(identifier), p_expression);
-
- return -1;
-
+ r_error = ERR_COMPILATION_FAILED;
+ return GDScriptCodeGenerator::Address();
} break;
case GDScriptParser::Node::LITERAL: {
- //return constant
+ // Return constant.
const GDScriptParser::LiteralNode *cn = static_cast<const GDScriptParser::LiteralNode *>(p_expression);
- int idx;
-
- if (!codegen.constant_map.has(cn->value)) {
- idx = codegen.constant_map.size();
- codegen.constant_map[cn->value] = idx;
-
- } else {
- idx = codegen.constant_map[cn->value];
- }
-
- return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root)
-
+ return codegen.add_constant(cn->value);
} break;
case GDScriptParser::Node::SELF: {
//return constant
if (codegen.function_node && codegen.function_node->is_static) {
_set_error("'self' not present in static function!", p_expression);
- return -1;
+ r_error = ERR_COMPILATION_FAILED;
+ return GDScriptCodeGenerator::Address();
}
- return (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS);
+ return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF);
} break;
case GDScriptParser::Node::ARRAY: {
const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression);
- Vector<int> values;
+ Vector<GDScriptCodeGenerator::Address> values;
- int slevel = p_stack_level;
+ // Create the result temporary first since it's the last to be killed.
+ GDScriptDataType array_type;
+ array_type.has_type = true;
+ array_type.kind = GDScriptDataType::BUILTIN;
+ array_type.builtin_type = Variant::ARRAY;
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type);
for (int i = 0; i < an->elements.size(); i++) {
- int ret = _parse_expression(codegen, an->elements[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);
+ GDScriptCodeGenerator::Address val = _parse_expression(codegen, r_error, an->elements[i]);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
-
- values.push_back(ret);
+ values.push_back(val);
}
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY);
- codegen.opcodes.push_back(values.size());
+ gen->write_construct_array(result, values);
+
for (int i = 0; i < values.size(); i++) {
- codegen.opcodes.push_back(values[i]);
+ if (values[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
}
- 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;
-
+ return result;
} break;
case GDScriptParser::Node::DICTIONARY: {
const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression);
- Vector<int> elements;
+ Vector<GDScriptCodeGenerator::Address> elements;
- int slevel = p_stack_level;
+ // Create the result temporary first since it's the last to be killed.
+ GDScriptDataType dict_type;
+ dict_type.has_type = true;
+ dict_type.kind = GDScriptDataType::BUILTIN;
+ dict_type.builtin_type = Variant::DICTIONARY;
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type);
for (int i = 0; i < dn->elements.size(); i++) {
// Key.
- int ret = -1;
+ GDScriptCodeGenerator::Address element;
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);
+ element = _parse_expression(codegen, r_error, dn->elements[i].key);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
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);
+ element = codegen.add_constant(key);
break;
}
- elements.push_back(ret);
+ elements.push_back(element);
- ret = _parse_expression(codegen, dn->elements[i].value, slevel);
- if (ret < 0) {
- return ret;
- }
- if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
- slevel++;
- codegen.alloc_stack(slevel);
+ element = _parse_expression(codegen, r_error, dn->elements[i].value);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
- elements.push_back(ret);
+ elements.push_back(element);
}
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY);
- codegen.opcodes.push_back(dn->elements.size());
+ gen->write_construct_dictionary(result, elements);
+
for (int i = 0; i < elements.size(); i++) {
- codegen.opcodes.push_back(elements[i]);
+ if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
}
- 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;
-
+ return result;
} break;
case GDScriptParser::Node::CAST: {
const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
+ GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype());
- int slevel = p_stack_level;
- int src_addr = _parse_expression(codegen, cn->operand, slevel);
- if (src_addr < 0) {
- return src_addr;
- }
- if (src_addr & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
+ // Create temporary for result first since it will be deleted last.
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type);
- GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype());
+ GDScriptCodeGenerator::Address source = _parse_expression(codegen, r_error, cn->operand);
- switch (cast_type.kind) {
- case GDScriptDataType::BUILTIN: {
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_BUILTIN);
- codegen.opcodes.push_back(cast_type.builtin_type);
- } break;
- case GDScriptDataType::NATIVE: {
- int class_idx;
- if (GDScriptLanguage::get_singleton()->get_global_map().has(cast_type.native_type)) {
- class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cast_type.native_type];
- class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
- } else {
- _set_error("Invalid native class type '" + String(cast_type.native_type) + "'.", cn);
- return -1;
- }
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_NATIVE); // perform operator
- codegen.opcodes.push_back(class_idx); // variable type
- } break;
- case GDScriptDataType::SCRIPT:
- case GDScriptDataType::GDSCRIPT: {
- Variant script = cast_type.script_type;
- int idx = codegen.get_constant_pos(script); //make it a local constant (faster access)
+ gen->write_cast(result, source, cast_type);
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator
- codegen.opcodes.push_back(idx); // variable type
- } break;
- default: {
- _set_error("Parser bug: unresolved data type.", cn);
- return -1;
- }
+ if (source.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
- codegen.opcodes.push_back(src_addr); // source address
- 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;
-
+ return result;
} break;
- //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
+ GDScriptDataType type = _gdtype_from_datatype(call->get_datatype());
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(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);
+ Vector<GDScriptCodeGenerator::Address> arguments;
+ for (int i = 0; i < call->arguments.size(); i++) {
+ GDScriptCodeGenerator::Address arg = _parse_expression(codegen, r_error, call->arguments[i]);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
+ arguments.push_back(arg);
+ }
- //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
- }
+ 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 built-in type.
+ Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name);
+ gen->write_construct(result, vtype, arguments);
} 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 = 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);
- }
-
- 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]);
- }
-
+ // Built-in function.
+ GDScriptFunctions::Function func = GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name);
+ gen->write_call_builtin(result, func, arguments);
} else {
- //regular function
-
+ // Regular function.
const GDScriptParser::ExpressionNode *callee = call->callee;
- Vector<int> arguments;
- int slevel = p_stack_level;
-
- // 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);
- }
+ gen->write_super_call(result, call->function_name, arguments);
} else {
if (callee->type == GDScriptParser::Node::IDENTIFIER) {
// Self function call.
- if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
- ret = (GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS);
+ if (ClassDB::has_method(codegen.script->native->get_name(), call->function_name)) {
+ // Native method, use faster path.
+ GDScriptCodeGenerator::Address self;
+ self.mode = GDScriptCodeGenerator::Address::SELF;
+ MethodBind *method = ClassDB::get_method(codegen.script->native->get_name(), call->function_name);
+
+ if (_have_exact_arguments(method, arguments)) {
+ // Exact arguments, use ptrcall.
+ gen->write_call_ptrcall(result, self, method, arguments);
+ } else {
+ // Not exact arguments, but still can use method bind call.
+ gen->write_call_method_bind(result, self, method, arguments);
+ }
+ } else if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
+ GDScriptCodeGenerator::Address self;
+ self.mode = GDScriptCodeGenerator::Address::CLASS;
+ gen->write_call(result, self, call->function_name, arguments);
} else {
- ret = (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS);
+ gen->write_call_self(result, call->function_name, arguments);
}
- 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;
+ GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
- if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
- slevel++;
- codegen.alloc_stack(slevel);
+ if (within_await) {
+ gen->write_call_async(result, base, call->function_name, arguments);
+ } else if (base.type.has_type && base.type.kind != GDScriptDataType::BUILTIN) {
+ // Native method, use faster path.
+ StringName class_name;
+ if (base.type.kind == GDScriptDataType::NATIVE) {
+ class_name = base.type.native_type;
+ } else {
+ class_name = base.type.native_type == StringName() ? base.type.script_type->get_instance_base_type() : base.type.native_type;
+ }
+ if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) {
+ MethodBind *method = ClassDB::get_method(class_name, call->function_name);
+ if (_have_exact_arguments(method, arguments)) {
+ // Exact arguments, use ptrcall.
+ gen->write_call_ptrcall(result, base, method, arguments);
+ } else {
+ // Not exact arguments, but still can use method bind call.
+ gen->write_call_method_bind(result, base, method, arguments);
+ }
+ } else {
+ gen->write_call(result, base, call->function_name, arguments);
+ }
+ } else if (base.type.has_type && base.type.kind == GDScriptDataType::BUILTIN) {
+ gen->write_call_builtin_type(result, base, base.type.builtin_type, call->function_name, arguments);
+ } else {
+ gen->write_call(result, base, call->function_name, arguments);
+ }
+ if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
- 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;
+ r_error = ERR_COMPILATION_FAILED;
+ return GDScriptCodeGenerator::Address();
}
} else {
- _set_error("Cannot call something that isn't a function.", call->callee);
- return -1;
- }
- }
-
- for (int i = 0; i < call->arguments.size(); i++) {
- ret = _parse_expression(codegen, call->arguments[i], slevel);
- if (ret < 0) {
- return ret;
+ r_error = ERR_COMPILATION_FAILED;
+ return GDScriptCodeGenerator::Address();
}
- if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
- arguments.push_back(ret);
- }
-
- 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;
}
+ }
- 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]);
+ for (int i = 0; i < arguments.size(); i++) {
+ if (arguments[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
}
- OPERATOR_RETURN;
+ return result;
} break;
case GDScriptParser::Node::GET_NODE: {
const GDScriptParser::GetNodeNode *get_node = static_cast<const GDScriptParser::GetNodeNode *>(p_expression);
@@ -816,59 +567,55 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
}
}
- int arg_address = codegen.get_constant_pos(NodePath(node_name));
+ Vector<GDScriptCodeGenerator::Address> args;
+ args.push_back(codegen.add_constant(NodePath(node_name)));
+
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype()));
+
+ MethodBind *get_node_method = ClassDB::get_method("Node", "get_node");
+ gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args);
- 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;
+ return result;
} break;
case GDScriptParser::Node::PRELOAD: {
const GDScriptParser::PreloadNode *preload = static_cast<const GDScriptParser::PreloadNode *>(p_expression);
// Add resource as constant.
- return codegen.get_constant_pos(preload->resource);
+ return codegen.add_constant(preload->resource);
} break;
case GDScriptParser::Node::AWAIT: {
const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression);
- int slevel = p_stack_level;
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype()));
within_await = true;
- int argument = _parse_expression(codegen, await->to_await, slevel);
+ GDScriptCodeGenerator::Address argument = _parse_expression(codegen, r_error, await->to_await);
within_await = false;
- if (argument < 0) {
- return argument;
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
- if ((argument >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
- slevel++;
- codegen.alloc_stack(slevel);
+
+ gen->write_await(result, argument);
+
+ if (argument.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
- //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 :)
- OPERATOR_RETURN;
+ return result;
} break;
-
- //indexing operator
+ // Indexing operator.
case GDScriptParser::Node::SUBSCRIPT: {
- int slevel = p_stack_level;
-
const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression);
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype()));
- int from = _parse_expression(codegen, subscript->base, slevel);
- if (from < 0) {
- return from;
+ GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
bool named = subscript->is_attribute;
- int index;
- if (p_index_addr != 0) {
+ StringName name;
+ GDScriptCodeGenerator::Address index;
+ if (p_index_addr.mode != GDScriptCodeGenerator::Address::NIL) {
index = p_index_addr;
} else if (subscript->is_attribute) {
if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) {
@@ -879,306 +626,179 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
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;
+ r_error = ERR_COMPILATION_FAILED;
+ return GDScriptCodeGenerator::Address();
}
#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);
+ // Remove result temp as we don't need it.
+ gen->pop_temporary();
+ // Faster than indexing self (as if no self. had been used).
+ return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->get().index, _gdtype_from_datatype(subscript->get_datatype()));
}
}
- index = codegen.get_name_map_pos(subscript->attribute->name);
-
+ name = subscript->attribute->name;
+ named = true;
} 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);
+ // Also, somehow, named (speed up anyway).
+ name = static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value;
named = true;
-
} else {
- //regular indexing
- if (from & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
-
- index = _parse_expression(codegen, subscript->index, slevel);
- if (index < 0) {
- return index;
+ // Regular indexing.
+ index = _parse_expression(codegen, r_error, subscript->index);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
}
}
- 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;
+ if (named) {
+ gen->write_get_named(result, name, base);
+ } else {
+ gen->write_get(result, index, base);
+ }
+
+ if (index.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
+ if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
+
+ return result;
} 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::UnaryOpNode::OP_COMPLEMENT: {
- if (!_create_unary_operator(codegen, unary, Variant::OP_BIT_NEGATE, p_stack_level)) {
- return -1;
- }
- } break;
+
+ GDScriptCodeGenerator::Address result = codegen.add_temporary();
+
+ GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
+ }
+
+ gen->write_unary_operator(result, unary->variant_op, operand);
+
+ if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
- OPERATOR_RETURN;
+
+ return result;
}
case GDScriptParser::Node::BINARY_OPERATOR: {
- //binary operators (in precedence order)
const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression);
+ GDScriptCodeGenerator::Address result = codegen.add_temporary();
+
switch (binary->operation) {
case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: {
- // AND operator with early out on failure
+ // AND operator with early out on failure.
+ GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand);
+ gen->write_and_left_operand(left_operand);
+ GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand);
+ gen->write_and_right_operand(right_operand);
- int res = _parse_expression(codegen, binary->left_operand, p_stack_level);
- if (res < 0) {
- return res;
+ gen->write_end_and(result);
+
+ if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
- 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, binary->right_operand, p_stack_level);
- if (res < 0) {
- return res;
+ if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
-
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT);
- codegen.opcodes.push_back(res);
- int jump_fail_pos2 = codegen.opcodes.size();
- codegen.opcodes.push_back(0);
-
- codegen.alloc_stack(p_stack_level); //it will be used..
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE);
- codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(codegen.opcodes.size() + 3);
- codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size();
- codegen.opcodes.write[jump_fail_pos2] = codegen.opcodes.size();
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE);
- codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
-
} break;
case GDScriptParser::BinaryOpNode::OP_LOGIC_OR: {
- // OR operator with early out on success
+ // OR operator with early out on success.
+ GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand);
+ gen->write_or_left_operand(left_operand);
+ GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand);
+ gen->write_or_right_operand(right_operand);
+
+ gen->write_end_or(result);
- int res = _parse_expression(codegen, binary->left_operand, p_stack_level);
- if (res < 0) {
- return res;
+ if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF);
- codegen.opcodes.push_back(res);
- int jump_success_pos = codegen.opcodes.size();
- codegen.opcodes.push_back(0);
-
- res = _parse_expression(codegen, binary->right_operand, p_stack_level);
- if (res < 0) {
- return res;
+ if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
-
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF);
- codegen.opcodes.push_back(res);
- int jump_success_pos2 = codegen.opcodes.size();
- codegen.opcodes.push_back(0);
-
- codegen.alloc_stack(p_stack_level); //it will be used..
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE);
- codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(codegen.opcodes.size() + 3);
- codegen.opcodes.write[jump_success_pos] = codegen.opcodes.size();
- codegen.opcodes.write[jump_success_pos2] = codegen.opcodes.size();
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE);
- codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
- return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
-
} break;
case GDScriptParser::BinaryOpNode::OP_TYPE_TEST: {
- int slevel = p_stack_level;
+ GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, binary->left_operand);
- int src_address_a = _parse_expression(codegen, binary->left_operand, slevel);
- if (src_address_a < 0) {
- return -1;
- }
-
- if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++; //uses stack for return, increase stack
- }
-
- 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);
+ // `is` with builtin type)
+ Variant::Type type = GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name);
+ gen->write_type_test_builtin(result, operand, type);
} else {
- src_address_b = _parse_expression(codegen, binary->right_operand, slevel);
- if (src_address_b < 0) {
- return -1;
+ GDScriptCodeGenerator::Address type = _parse_expression(codegen, r_error, binary->right_operand);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
+ }
+ gen->write_type_test(result, operand, type);
+ if (type.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
}
+ } break;
+ default: {
+ GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand);
+ GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand);
- 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)
+ gen->write_binary_operator(result, binary->variant_op, left_operand, right_operand);
- } break;
- case GDScriptParser::BinaryOpNode::OP_CONTENT_TEST: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_IN, p_stack_level)) {
- return -1;
- }
- } break;
- case GDScriptParser::BinaryOpNode::OP_COMP_EQUAL: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_EQUAL, p_stack_level)) {
- return -1;
- }
- } break;
- 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::BinaryOpNode::OP_COMP_LESS: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_LESS, p_stack_level)) {
- return -1;
- }
- } break;
- 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::BinaryOpNode::OP_COMP_GREATER: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_GREATER, p_stack_level)) {
- return -1;
- }
- } break;
- 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::BinaryOpNode::OP_ADDITION: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_ADD, p_stack_level)) {
- return -1;
- }
- } break;
- case GDScriptParser::BinaryOpNode::OP_SUBTRACTION: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_SUBTRACT, p_stack_level)) {
- return -1;
- }
- } break;
- case GDScriptParser::BinaryOpNode::OP_MULTIPLICATION: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_MULTIPLY, p_stack_level)) {
- return -1;
- }
- } break;
- case GDScriptParser::BinaryOpNode::OP_DIVISION: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_DIVIDE, p_stack_level)) {
- return -1;
- }
- } break;
- case GDScriptParser::BinaryOpNode::OP_MODULO: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_MODULE, p_stack_level)) {
- return -1;
- }
- } break;
- case GDScriptParser::BinaryOpNode::OP_BIT_AND: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_AND, p_stack_level)) {
- return -1;
+ if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
- } break;
- case GDScriptParser::BinaryOpNode::OP_BIT_OR: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_OR, p_stack_level)) {
- return -1;
+ if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
- } break;
- 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::BinaryOpNode::OP_BIT_LEFT_SHIFT: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_LEFT, p_stack_level)) {
- return -1;
- }
- } break;
- case GDScriptParser::BinaryOpNode::OP_BIT_RIGHT_SHIFT: {
- if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_RIGHT, p_stack_level)) {
- return -1;
- }
- } break;
+ }
}
- OPERATOR_RETURN;
+ return result;
} break;
- // ternary operators
case GDScriptParser::Node::TERNARY_OPERATOR: {
- // x IF a ELSE y operator with early out on failure
-
+ // 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);
+ GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype()));
- res = _parse_expression(codegen, ternary->true_expr, p_stack_level);
- if (res < 0) {
- return res;
- }
+ gen->write_start_ternary(result);
- 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);
+ GDScriptCodeGenerator::Address condition = _parse_expression(codegen, r_error, ternary->condition);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
+ }
+ gen->write_ternary_condition(condition);
- codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size();
- res = _parse_expression(codegen, ternary->false_expr, p_stack_level);
- if (res < 0) {
- return res;
+ if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
- 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);
+ GDScriptCodeGenerator::Address true_expr = _parse_expression(codegen, r_error, ternary->true_expr);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
+ }
+ gen->write_ternary_true_expr(true_expr);
+ if (true_expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
- codegen.opcodes.write[jump_past_pos] = codegen.opcodes.size();
+ GDScriptCodeGenerator::Address false_expr = _parse_expression(codegen, r_error, ternary->false_expr);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
+ }
+ gen->write_ternary_false_expr(false_expr);
+ if (false_expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
- return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
+ gen->write_end_ternary();
+ return result;
} break;
- //assignment operators
case GDScriptParser::Node::ASSIGNMENT: {
const GDScriptParser::AssignmentNode *assignment = static_cast<const GDScriptParser::AssignmentNode *>(p_expression);
@@ -1186,20 +806,16 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
// 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;
- }
+ if (subscript->is_attribute && 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);
+ r_error = ERR_COMPILATION_FAILED;
+ return GDScriptCodeGenerator::Address();
}
}
#endif
-
- int slevel = p_stack_level;
-
/* Find chain of sets */
StringName assign_property;
@@ -1207,13 +823,12 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
List<const GDScriptParser::SubscriptNode *> chain;
{
- //create get/set chain
+ // Create get/set chain.
const GDScriptParser::SubscriptNode *n = subscript;
while (true) {
chain.push_back(n);
-
if (n->base->type != GDScriptParser::Node::SUBSCRIPT) {
- //check for a built-in property
+ // 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)) {
@@ -1228,366 +843,396 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
/* Chain of gets */
- //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;
+ // Get at (potential) root stack pos, so it can be returned.
+ GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, chain.back()->get()->base);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
- int retval = prev_pos;
- if (retval & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
+ GDScriptCodeGenerator::Address prev_base = base;
- Vector<int> setchain;
+ struct ChainInfo {
+ bool is_named = false;
+ GDScriptCodeGenerator::Address base;
+ GDScriptCodeGenerator::Address key;
+ StringName name;
+ };
- 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);
- }
+ List<ChainInfo> set_chain;
for (List<const GDScriptParser::SubscriptNode *>::Element *E = chain.back(); E; E = E->prev()) {
- if (E == chain.front()) { //ignore first
+ if (E == chain.front()) {
+ // Skip the main subscript, since we'll assign to that.
break;
}
-
const GDScriptParser::SubscriptNode *subscript_elem = E->get();
- int key_idx;
+ GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype()));
+ GDScriptCodeGenerator::Address key;
+ StringName name;
if (subscript_elem->is_attribute) {
- key_idx = codegen.get_name_map_pos(subscript_elem->attribute->name);
- //printf("named key %x\n",key_idx);
-
+ name = subscript_elem->attribute->name;
+ gen->write_get_named(value, name, prev_base);
} else {
- if (prev_pos & (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS)) {
- slevel++;
- codegen.alloc_stack(slevel);
+ key = _parse_expression(codegen, r_error, subscript_elem->index);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
-
- GDScriptParser::ExpressionNode *key = subscript_elem->index;
- key_idx = _parse_expression(codegen, key, slevel);
- //printf("expr key %x\n",key_idx);
-
- //stack was raised here if retval was stack but..
- }
-
- if (key_idx < 0) { //error
- return key_idx;
+ gen->write_get(value, key, prev_base);
}
- 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;
-
- codegen.opcodes.push_back(dst_pos);
-
- //add in reverse order, since it will be reverted
-
- 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);
-
- prev_pos = dst_pos;
+ // Store base and key for setting it back later.
+ set_chain.push_front({ subscript_elem->is_attribute, prev_base, key, name }); // Push to front to invert the list.
+ prev_base = value;
}
- setchain.invert();
-
- int set_index;
-
+ // Get value to assign.
+ GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
+ }
+ // Get the key if needed.
+ GDScriptCodeGenerator::Address key;
+ StringName name;
if (subscript->is_attribute) {
- set_index = codegen.get_name_map_pos(subscript->attribute->name);
+ name = subscript->attribute->name;
} else {
- set_index = _parse_expression(codegen, subscript->index, slevel + 1);
+ key = _parse_expression(codegen, r_error, subscript->index);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
+ }
}
- if (set_index < 0) { //error
- return set_index;
+ // Perform operator if any.
+ if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
+ GDScriptCodeGenerator::Address value = codegen.add_temporary();
+ if (subscript->is_attribute) {
+ gen->write_get_named(value, name, prev_base);
+ } else {
+ gen->write_get(value, key, prev_base);
+ }
+ gen->write_binary_operator(value, assignment->variant_op, value, assigned);
+ if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
+ assigned = value;
}
- if (set_index & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++;
- codegen.alloc_stack(slevel);
+ // Perform assignment.
+ if (subscript->is_attribute) {
+ gen->write_set_named(prev_base, name, assigned);
+ } else {
+ gen->write_set(prev_base, key, assigned);
}
-
- 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;
+ if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
}
- 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);
+ assigned = prev_base;
- for (int i = 0; i < setchain.size(); i++) {
- codegen.opcodes.push_back(setchain[i]);
+ // Set back the values into their bases.
+ for (List<ChainInfo>::Element *E = set_chain.front(); E; E = E->next()) {
+ const ChainInfo &info = E->get();
+ if (!info.is_named) {
+ gen->write_set(info.base, info.key, assigned);
+ if (info.key.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
+ } else {
+ gen->write_set_named(info.base, info.name, assigned);
+ }
+ if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
+ assigned = info.base;
}
- return retval;
+ // If this is a local member, also assign to it.
+ // This allow things like: position.x += 2.0
+ if (assign_property != StringName()) {
+ gen->write_set_member(assigned, assign_property);
+ }
+ if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
} 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 src_address = _parse_assign_right_expression(codegen, assignment, slevel);
- if (src_address < 0) {
- return -1;
+ // Assignment to member property.
+ GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
+ GDScriptCodeGenerator::Address assign_temp = assigned;
StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name;
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_SET_MEMBER);
- codegen.opcodes.push_back(codegen.get_name_map_pos(name));
- codegen.opcodes.push_back(src_address);
+ if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
+ GDScriptCodeGenerator::Address member = codegen.add_temporary();
+ gen->write_get_member(member, name);
+ gen->write_binary_operator(assigned, assignment->variant_op, member, assigned);
+ gen->pop_temporary();
+ }
- return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS;
- } else {
- //REGULAR ASSIGNMENT MODE!!
+ gen->write_set_member(assigned, name);
- int slevel = p_stack_level;
- int dst_address_a = -1;
+ if (assign_temp.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
+ } else {
+ // Regular assignment.
+ GDScriptCodeGenerator::Address target;
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)) {
+ if (!codegen.locals.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;
+ target.mode = GDScriptCodeGenerator::Address::MEMBER;
+ target.address = codegen.script->member_indices[var_name].index;
}
}
}
if (has_setter) {
- if (is_in_setter) {
- // Use direct member access.
- dst_address_a |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS;
- } else {
+ if (!is_in_setter) {
// 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;
+ target = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype()));
}
} else {
- dst_address_a = _parse_expression(codegen, assignment->assignee, slevel);
- if (dst_address_a < 0) {
- return -1;
+ target = _parse_expression(codegen, r_error, assignment->assignee);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
+ }
- if (dst_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) {
- slevel++;
- codegen.alloc_stack(slevel);
- }
+ GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value);
+ GDScriptCodeGenerator::Address op_result;
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
- int src_address_b = _parse_assign_right_expression(codegen, assignment, slevel);
- if (src_address_b < 0) {
- return -1;
+ if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
+ // Perform operation.
+ op_result = codegen.add_temporary();
+ gen->write_binary_operator(op_result, assignment->variant_op, target, assigned);
+ } else {
+ op_result = assigned;
+ assigned = GDScriptCodeGenerator::Address();
}
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;
+ Vector<GDScriptCodeGenerator::Address> args;
+ args.push_back(op_result);
+ gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args);
+ } else {
+ // Just assign.
+ gen->write_assign(target, op_result);
}
- return dst_address_a; //if anything, returns wathever was assigned or correct stack position
+ if (op_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
+ if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
+ if (target.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
}
+ return GDScriptCodeGenerator::Address(); // Assignment does not return a value.
} 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
+ ERR_FAIL_V_MSG(GDScriptCodeGenerator::Address(), "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); // Unreachable code.
} break;
}
}
-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...
+GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested) {
switch (p_pattern->pattern_type) {
case GDScriptParser::PatternNode::PT_LITERAL: {
+ if (p_is_nested) {
+ codegen.generator->write_and_left_operand(p_previous_test);
+ } else if (!p_is_first) {
+ codegen.generator->write_or_left_operand(p_previous_test);
+ }
+
// 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;
+ GDScriptCodeGenerator::Address literal_type_addr = codegen.add_constant((int)p_pattern->literal->value.get_type());
- } 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;
+ // Equality is always a boolean.
+ GDScriptDataType equality_type;
+ equality_type.has_type = true;
+ equality_type.kind = GDScriptDataType::BUILTIN;
+ equality_type.builtin_type = Variant::BOOL;
// 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.
+ GDScriptCodeGenerator::Address type_equality_addr = codegen.add_temporary(equality_type);
+ codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_EQUAL, p_type_addr, literal_type_addr);
+ codegen.generator->write_and_left_operand(type_equality_addr);
// Get literal.
- int literal_addr = _parse_expression(codegen, p_pattern->literal, p_stack_level);
+ GDScriptCodeGenerator::Address literal_addr = _parse_expression(codegen, r_error, p_pattern->literal);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
+ }
// 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.
+ GDScriptCodeGenerator::Address equality_addr = codegen.add_temporary(equality_type);
+ codegen.generator->write_binary_operator(equality_addr, Variant::OP_EQUAL, p_value_addr, literal_addr);
+ codegen.generator->write_and_right_operand(equality_addr);
+
+ // AND both together (reuse temporary location).
+ codegen.generator->write_end_and(type_equality_addr);
+
+ codegen.generator->pop_temporary(); // Remove equality_addr from stack.
+
+ if (literal_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
+ }
+
+ // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead.
+ if (p_is_nested) {
+ // Use the previous value as target, since we only need one temporary variable.
+ codegen.generator->write_and_right_operand(type_equality_addr);
+ codegen.generator->write_end_and(p_previous_test);
+ } else if (!p_is_first) {
+ // Use the previous value as target, since we only need one temporary variable.
+ codegen.generator->write_or_right_operand(type_equality_addr);
+ codegen.generator->write_end_or(p_previous_test);
+ } else {
+ // Just assign this value to the accumulator temporary.
+ codegen.generator->write_assign(p_previous_test, type_equality_addr);
+ }
+ codegen.generator->pop_temporary(); // Remove type_equality_addr.
+
+ return p_previous_test;
} break;
case GDScriptParser::PatternNode::PT_EXPRESSION: {
+ if (p_is_nested) {
+ codegen.generator->write_and_left_operand(p_previous_test);
+ } else if (!p_is_first) {
+ codegen.generator->write_or_left_operand(p_previous_test);
+ }
+ // Create the result temps first since it's the last to go away.
+ GDScriptCodeGenerator::Address result_addr = codegen.add_temporary();
+ GDScriptCodeGenerator::Address equality_test_addr = codegen.add_temporary();
+
// 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);
+ GDScriptCodeGenerator::Address expr_addr;
+ expr_addr = _parse_expression(codegen, r_error, p_pattern->expression);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
// 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.
+ Vector<GDScriptCodeGenerator::Address> typeof_args;
+ typeof_args.push_back(expr_addr);
+ codegen.generator->write_call_builtin(result_addr, GDScriptFunctions::TYPE_OF, typeof_args);
// 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.
+ codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, result_addr);
+ codegen.generator->write_and_left_operand(result_addr);
// 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.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_value_addr, expr_addr);
+ codegen.generator->write_and_right_operand(equality_test_addr);
- // 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.
+ // AND both type and value equality.
+ codegen.generator->write_end_and(result_addr);
+
+ // We don't need the expression temporary anymore.
+ if (expr_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
+ }
+ codegen.generator->pop_temporary(); // Remove type equality temporary.
+
+ // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead.
+ if (p_is_nested) {
+ // Use the previous value as target, since we only need one temporary variable.
+ codegen.generator->write_and_right_operand(result_addr);
+ codegen.generator->write_end_and(p_previous_test);
+ } else if (!p_is_first) {
+ // Use the previous value as target, since we only need one temporary variable.
+ codegen.generator->write_or_right_operand(result_addr);
+ codegen.generator->write_end_or(p_previous_test);
+ } else {
+ // Just assign this value to the accumulator temporary.
+ codegen.generator->write_assign(p_previous_test, result_addr);
+ }
+ codegen.generator->pop_temporary(); // Remove temp result addr.
+
+ return p_previous_test;
} break;
case GDScriptParser::PatternNode::PT_ARRAY: {
- int slevel = p_stack_level;
-
+ if (p_is_nested) {
+ codegen.generator->write_and_left_operand(p_previous_test);
+ } else if (!p_is_first) {
+ codegen.generator->write_or_left_operand(p_previous_test);
+ }
// Get array type into constant map.
- int array_type_addr = codegen.get_constant_pos(Variant::ARRAY);
+ GDScriptCodeGenerator::Address array_type_addr = codegen.add_constant((int)Variant::ARRAY);
+
+ // Equality is always a boolean.
+ GDScriptDataType temp_type;
+ temp_type.has_type = true;
+ temp_type.kind = GDScriptDataType::BUILTIN;
+ temp_type.builtin_type = Variant::BOOL;
// 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.
+ GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(temp_type);
+ codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, array_type_addr);
+ codegen.generator->write_and_left_operand(result_addr);
// 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());
+ GDScriptCodeGenerator::Address array_length_addr = codegen.add_constant(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.
+ temp_type.builtin_type = Variant::INT;
+ GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type);
+ Vector<GDScriptCodeGenerator::Address> len_args;
+ len_args.push_back(p_value_addr);
+ codegen.generator->write_call_builtin(value_length_addr, GDScriptFunctions::LEN, len_args);
// 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.
+ temp_type.builtin_type = Variant::BOOL;
+ GDScriptCodeGenerator::Address length_compat_addr = codegen.add_temporary(temp_type);
+ codegen.generator->write_binary_operator(length_compat_addr, p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL, value_length_addr, array_length_addr);
+ codegen.generator->write_and_right_operand(length_compat_addr);
+
+ // AND type and length check.
+ codegen.generator->write_end_and(result_addr);
+
+ // Remove length temporaries.
+ codegen.generator->pop_temporary();
+ codegen.generator->pop_temporary();
+
+ // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead.
+ if (p_is_nested) {
+ // Use the previous value as target, since we only need one temporary variable.
+ codegen.generator->write_and_right_operand(result_addr);
+ codegen.generator->write_end_and(p_previous_test);
+ } else if (!p_is_first) {
+ // Use the previous value as target, since we only need one temporary variable.
+ codegen.generator->write_or_right_operand(result_addr);
+ codegen.generator->write_end_or(p_previous_test);
+ } else {
+ // Just assign this value to the accumulator temporary.
+ codegen.generator->write_assign(p_previous_test, result_addr);
+ }
+ codegen.generator->pop_temporary(); // Remove temp result addr.
+
+ // Create temporaries outside the loop so they can be reused.
+ GDScriptCodeGenerator::Address element_addr = codegen.add_temporary();
+ GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary();
+ GDScriptCodeGenerator::Address test_addr = p_previous_test;
// Evaluate element by element.
for (int i = 0; i < p_pattern->array.size(); i++) {
@@ -1596,494 +1241,473 @@ Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptPar
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.
+ // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get).
+ codegen.generator->write_and_left_operand(test_addr);
+
// Add index to constant map.
- int index_addr = codegen.get_constant_pos(i);
+ GDScriptCodeGenerator::Address index_addr = codegen.add_constant(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.
+ codegen.generator->write_get(element_addr, index_addr, p_value_addr);
// 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.
+ Vector<GDScriptCodeGenerator::Address> typeof_args;
+ typeof_args.push_back(element_addr);
+ codegen.generator->write_call_builtin(element_type_addr, GDScriptFunctions::TYPE_OF, typeof_args);
// 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;
+ test_addr = _parse_match_pattern(codegen, r_error, p_pattern->array[i], element_addr, element_type_addr, p_previous_test, false, true);
+ if (r_error != OK) {
+ return GDScriptCodeGenerator::Address();
}
- // 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();
- }
+ codegen.generator->write_and_right_operand(test_addr);
+ codegen.generator->write_end_and(test_addr);
}
+ // Remove element temporaries.
+ codegen.generator->pop_temporary();
+ codegen.generator->pop_temporary();
- // 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.
+ return test_addr;
} break;
case GDScriptParser::PatternNode::PT_DICTIONARY: {
- int slevel = p_stack_level;
-
+ if (p_is_nested) {
+ codegen.generator->write_and_left_operand(p_previous_test);
+ } else if (!p_is_first) {
+ codegen.generator->write_or_left_operand(p_previous_test);
+ }
// Get dictionary type into constant map.
- int dict_type_addr = codegen.get_constant_pos(Variant::DICTIONARY);
+ GDScriptCodeGenerator::Address dict_type_addr = codegen.add_constant((int)Variant::DICTIONARY);
+
+ // Equality is always a boolean.
+ GDScriptDataType temp_type;
+ temp_type.has_type = true;
+ temp_type.kind = GDScriptDataType::BUILTIN;
+ temp_type.builtin_type = Variant::BOOL;
// 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.
+ GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(temp_type);
+ codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, dict_type_addr);
+ codegen.generator->write_and_left_operand(result_addr);
// 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());
+ GDScriptCodeGenerator::Address dict_length_addr = codegen.add_constant(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.
+ temp_type.builtin_type = Variant::INT;
+ GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type);
+ Vector<GDScriptCodeGenerator::Address> func_args;
+ func_args.push_back(p_value_addr);
+ codegen.generator->write_call_builtin(value_length_addr, GDScriptFunctions::LEN, func_args);
// 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.
+ temp_type.builtin_type = Variant::BOOL;
+ GDScriptCodeGenerator::Address length_compat_addr = codegen.add_temporary(temp_type);
+ codegen.generator->write_binary_operator(length_compat_addr, p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL, value_length_addr, dict_length_addr);
+ codegen.generator->write_and_right_operand(length_compat_addr);
+
+ // AND type and length check.
+ codegen.generator->write_end_and(result_addr);
+
+ // Remove length temporaries.
+ codegen.generator->pop_temporary();
+ codegen.generator->pop_temporary();
+
+ // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead.
+ if (p_is_nested) {
+ // Use the previous value as target, since we only need one temporary variable.
+ codegen.generator->write_and_right_operand(result_addr);
+ codegen.generator->write_end_and(p_previous_test);
+ } else if (!p_is_first) {
+ // Use the previous value as target, since we only need one temporary variable.
+ codegen.generator->write_or_right_operand(result_addr);
+ codegen.generator->write_end_or(p_previous_test);
+ } else {
+ // Just assign this value to the accumulator temporary.
+ codegen.generator->write_assign(p_previous_test, result_addr);
+ }
+ codegen.generator->pop_temporary(); // Remove temp result addr.
+
+ // Create temporaries outside the loop so they can be reused.
+ temp_type.builtin_type = Variant::BOOL;
+ GDScriptCodeGenerator::Address test_result = codegen.add_temporary(temp_type);
+ GDScriptCodeGenerator::Address element_addr = codegen.add_temporary();
+ GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary();
+ GDScriptCodeGenerator::Address test_addr = p_previous_test;
// 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;
+ 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.
+
+ // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get).
+ codegen.generator->write_and_left_operand(test_addr);
// Get the pattern key.
- int pattern_key_addr = _parse_expression(codegen, element.key, stlevel);
- if (pattern_key_addr < 0) {
- return ERR_PARSE_ERROR;
+ GDScriptCodeGenerator::Address pattern_key_addr = _parse_expression(codegen, r_error, element.key);
+ if (r_error) {
+ return GDScriptCodeGenerator::Address();
}
- 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.
+ // Check if pattern key exists in user's dictionary. This will be AND-ed with next result.
+ func_args.clear();
+ func_args.push_back(pattern_key_addr);
+ codegen.generator->write_call(test_result, p_value_addr, "has", func_args);
if (element.value_pattern != nullptr) {
+ // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get).
+ codegen.generator->write_and_left_operand(test_result);
+
// 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.
+ codegen.generator->write_get(element_addr, pattern_key_addr, p_value_addr);
// 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.
+ func_args.clear();
+ func_args.push_back(element_addr);
+ codegen.generator->write_call_builtin(element_type_addr, GDScriptFunctions::TYPE_OF, func_args);
// 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;
+ test_addr = _parse_match_pattern(codegen, r_error, element.value_pattern, element_addr, element_type_addr, test_addr, false, true);
+ if (r_error != OK) {
+ return GDScriptCodeGenerator::Address();
}
+ codegen.generator->write_and_right_operand(test_addr);
+ codegen.generator->write_end_and(test_addr);
}
- // 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();
+ codegen.generator->write_and_right_operand(test_addr);
+ codegen.generator->write_end_and(test_addr);
+
+ // Remove pattern key temporary.
+ if (pattern_key_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
}
}
- // 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.
+ // Remove element temporaries.
+ codegen.generator->pop_temporary();
+ codegen.generator->pop_temporary();
+ codegen.generator->pop_temporary();
+ return test_addr;
} break;
case GDScriptParser::PatternNode::PT_REST:
// Do nothing.
+ return p_previous_test;
break;
+ case GDScriptParser::PatternNode::PT_BIND: {
+ if (p_is_nested) {
+ codegen.generator->write_and_left_operand(p_previous_test);
+ } else if (!p_is_first) {
+ codegen.generator->write_or_left_operand(p_previous_test);
+ }
+ // Get the bind address.
+ GDScriptCodeGenerator::Address bind = codegen.locals[p_pattern->bind->name];
+
+ // Assign value to bound variable.
+ codegen.generator->write_assign(bind, p_value_addr);
+ }
+ [[fallthrough]]; // Act like matching anything too.
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.
+ // If this is a fall through we don't want to do this again.
+ if (p_pattern->pattern_type != GDScriptParser::PatternNode::PT_BIND) {
+ if (p_is_nested) {
+ codegen.generator->write_and_left_operand(p_previous_test);
+ } else if (!p_is_first) {
+ codegen.generator->write_or_left_operand(p_previous_test);
+ }
+ }
+ // This matches anything so just do the same as `if(true)`.
+ // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead.
+ if (p_is_nested) {
+ // Use the operator with the `true` constant so it works as always matching.
+ GDScriptCodeGenerator::Address constant = codegen.add_constant(true);
+ codegen.generator->write_and_right_operand(constant);
+ codegen.generator->write_end_and(p_previous_test);
+ } else if (!p_is_first) {
+ // Use the operator with the `true` constant so it works as always matching.
+ GDScriptCodeGenerator::Address constant = codegen.add_constant(true);
+ codegen.generator->write_or_right_operand(constant);
+ codegen.generator->write_end_or(p_previous_test);
+ } else {
+ // Just assign this value to the accumulator temporary.
+ codegen.generator->write_assign_true(p_previous_test);
+ }
+ return p_previous_test;
}
- return OK;
+ ERR_FAIL_V_MSG(p_previous_test, "Reaching the end of pattern compilation without matching a pattern.");
}
-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->start_line;
+void GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block) {
+ for (int i = 0; i < p_block->locals.size(); i++) {
+ if (p_block->locals[i].type == GDScriptParser::SuiteNode::Local::PARAMETER || p_block->locals[i].type == GDScriptParser::SuiteNode::Local::FOR_VARIABLE) {
+ // Parameters are added directly from function and loop variables are declared explicitly.
+ continue;
+ }
+ codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype()));
+ }
+}
+
+Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals) {
+ Error error = OK;
+ GDScriptCodeGenerator *gen = codegen.generator;
+
+ codegen.start_block();
+
+ if (p_add_locals) {
+ _add_locals_in_block(codegen, p_block);
+ }
for (int i = 0; i < p_block->statements.size(); i++) {
const GDScriptParser::Node *s = p_block->statements[i];
#ifdef DEBUG_ENABLED
// 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;
+ gen->write_newline(s->start_line);
#endif
switch (s->type) {
case GDScriptParser::Node::MATCH: {
const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s);
- int slevel = p_stack_level;
+ gen->start_match();
+ codegen.start_block();
- // 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;
+ // Evaluate the match expression.
+ GDScriptCodeGenerator::Address value_local = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype()));
+ GDScriptCodeGenerator::Address value = _parse_expression(codegen, error, match->test);
+ if (error) {
+ return error;
}
- if ((temp_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) {
- slevel++;
- codegen.alloc_stack(slevel);
+
+ // Assign to local.
+ // TODO: This can be improved by passing the target to parse_expression().
+ gen->write_assign(value_local, value);
+
+ if (value.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
}
// 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.
+ GDScriptDataType typeof_type;
+ typeof_type.has_type = true;
+ typeof_type.kind = GDScriptDataType::BUILTIN;
+ typeof_type.builtin_type = Variant::INT;
+ GDScriptCodeGenerator::Address type = codegen.add_local("@match_type", typeof_type);
- Vector<int> patch_match_end; // Will patch the jump to the end of match.
+ Vector<GDScriptCodeGenerator::Address> typeof_args;
+ typeof_args.push_back(value);
+ gen->write_call_builtin(type, GDScriptFunctions::TYPE_OF, typeof_args);
// Now we can actually start testing.
// For each branch.
for (int j = 0; j < match->branches.size(); j++) {
+ if (j > 0) {
+ // Use `else` to not check the next branch after matching.
+ gen->write_else();
+ }
+
const GDScriptParser::MatchBranchNode *branch = match->branches[j];
- int bound_variables = 0;
- codegen.push_stack_identifiers(); // Create an extra block around for binds.
+ gen->start_match_branch(); // Need so lower level code can patch 'continue' jumps.
+ codegen.start_block(); // Create an extra block around for binds.
+
+ // Add locals in block before patterns, so temporaries don't use the stack address for binds.
+ _add_locals_in_block(codegen, branch->block);
#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;
+ gen->write_newline(branch->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.
+ GDScriptCodeGenerator::Address pattern_result = codegen.add_temporary();
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();
- }
- 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;
+ pattern_result = _parse_match_pattern(codegen, error, branch->patterns[k], value, type, pattern_result, k == 0, false);
+ if (error != OK) {
+ return error;
}
}
- // 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();
- }
- // Leave space for bound variables.
- slevel += bound_variables;
- codegen.alloc_stack(slevel);
+ // Check if pattern did match.
+ gen->write_if(pattern_result);
- // Parse the branch block.
- _parse_block(codegen, branch->block, slevel, p_break_addr, p_continue_addr);
-
- // 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.
+ // Remove the result from stack.
+ gen->pop_temporary();
- // 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();
+ // Parse the branch block.
+ error = _parse_block(codegen, branch->block, false); // Don't add locals again.
+ if (error) {
+ return error;
}
- codegen.pop_stack_identifiers(); // Get out of extra block.
+ codegen.end_block(); // 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();
+
+ // End all nested `if`s.
+ for (int j = 0; j < match->branches.size(); j++) {
+ gen->write_endif();
}
- } break;
+ gen->end_match();
+ } break;
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;
+ GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, if_n->condition);
+ if (error) {
+ return error;
}
- 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
+ gen->write_if(condition);
+
+ if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
+ }
- Error err = _parse_block(codegen, if_n->true_block, p_stack_level, p_break_addr, p_continue_addr);
- if (err) {
- return err;
+ error = _parse_block(codegen, if_n->true_block);
+ if (error) {
+ return error;
}
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();
-
- Error err2 = _parse_block(codegen, if_n->false_block, p_stack_level, p_break_addr, p_continue_addr);
- if (err2) {
- return err2;
- }
+ gen->write_else();
- codegen.opcodes.write[end_addr] = codegen.opcodes.size();
- } else {
- //end without else
- codegen.opcodes.write[else_addr] = codegen.opcodes.size();
+ error = _parse_block(codegen, if_n->false_block);
+ if (error) {
+ return error;
+ }
}
+ gen->write_endif();
} 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);
-
- codegen.push_stack_identifiers();
- codegen.add_stack_identifier(for_n->variable->name, iter_stack_pos);
-
- int ret2 = _parse_expression(codegen, for_n->list, slevel, false);
- if (ret2 < 0) {
- return ERR_COMPILATION_FAILED;
- }
-
- //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, for_n->loop, slevel, break_pos, continue_pos);
- if (err) {
- return err;
- }
-
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(continue_pos);
- codegen.opcodes.write[break_pos + 1] = codegen.opcodes.size();
-
- codegen.pop_stack_identifiers();
+ codegen.start_block();
+ GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype()));
+
+ gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype()));
+
+ GDScriptCodeGenerator::Address list = _parse_expression(codegen, error, for_n->list);
+ if (error) {
+ return error;
+ }
+
+ gen->write_for_assignment(iterator, list);
+
+ if (list.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
+ }
+
+ gen->write_for();
+
+ error = _parse_block(codegen, for_n->loop);
+ if (error) {
+ return error;
+ }
+
+ gen->write_endfor();
+
+ codegen.end_block();
} 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;
+
+ gen->start_while_condition();
+
+ GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, while_n->condition);
+ if (error) {
+ return 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;
+
+ gen->write_while(condition);
+
+ if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
}
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(continue_addr);
- codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size();
+ error = _parse_block(codegen, while_n->loop);
+ if (error) {
+ return error;
+ }
+ gen->write_endwhile();
} 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);
-
+ gen->write_break();
} break;
case GDScriptParser::Node::CONTINUE: {
- if (p_continue_addr < 0) {
- _set_error("'continue' not within loop", s);
- return ERR_COMPILATION_FAILED;
+ const GDScriptParser::ContinueNode *cont = static_cast<const GDScriptParser::ContinueNode *>(s);
+ if (cont->is_for_match) {
+ gen->write_continue_match();
+ } else {
+ gen->write_continue();
}
-
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP);
- codegen.opcodes.push_back(p_continue_addr);
-
} break;
case GDScriptParser::Node::RETURN: {
const GDScriptParser::ReturnNode *return_n = static_cast<const GDScriptParser::ReturnNode *>(s);
- int ret2;
+
+ GDScriptCodeGenerator::Address return_value;
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;
+ return_value = _parse_expression(codegen, error, return_n->return_value);
+ if (error) {
+ return error;
}
-
- } else {
- ret2 = GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS;
}
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_RETURN);
- codegen.opcodes.push_back(ret2);
-
+ gen->write_return(return_value);
+ if (return_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
+ }
} break;
case GDScriptParser::Node::ASSERT: {
#ifdef DEBUG_ENABLED
- // try subblocks
-
const GDScriptParser::AssertNode *as = static_cast<const GDScriptParser::AssertNode *>(s);
- int ret2 = _parse_expression(codegen, as->condition, p_stack_level, false);
- if (ret2 < 0) {
- return ERR_PARSE_ERROR;
+ GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, as->condition);
+ if (error) {
+ return error;
}
- int message_ret = 0;
+ GDScriptCodeGenerator::Address message;
+
if (as->message) {
- message_ret = _parse_expression(codegen, as->message, p_stack_level + 1, false);
- if (message_ret < 0) {
- return ERR_PARSE_ERROR;
+ message = _parse_expression(codegen, error, as->message);
+ if (error) {
+ return error;
}
}
+ gen->write_assert(condition, message);
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSERT);
- codegen.opcodes.push_back(ret2);
- codegen.opcodes.push_back(message_ret);
+ if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
+ }
+ if (message.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
+ }
#endif
} break;
case GDScriptParser::Node::BREAKPOINT: {
#ifdef DEBUG_ENABLED
- // try subblocks
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_BREAKPOINT);
+ gen->write_breakpoint();
#endif
} break;
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.
- //
- //if (_is_class_member_property(codegen, lv->name)) {
- // _set_error("Name for local variable '" + String(lv->name) + "' can't shadow class property of the same name.", lv);
- // return ERR_ALREADY_EXISTS;
- //}
-
- codegen.add_stack_identifier(lv->identifier->name, p_stack_level++);
- codegen.alloc_stack(p_stack_level);
- new_identifiers++;
+ // Should be already in stack when the block began.
+ GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name];
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;
+ GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, lv->initializer);
+ if (error) {
+ return error;
}
- if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(lv->get_datatype()), lv->initializer->get_datatype())) {
- return ERR_PARSE_ERROR;
+ gen->write_assign(local, src_address);
+ if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
}
}
} break;
@@ -2094,281 +1718,159 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
_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);
+
+ codegen.add_local_constant(lc->identifier->name, lc->initializer->reduced_value);
} break;
case GDScriptParser::Node::PASS:
// Nothing to do.
break;
default: {
- //expression
+ // Expression.
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;
+ GDScriptCodeGenerator::Address expr = _parse_expression(codegen, error, static_cast<const GDScriptParser::ExpressionNode *>(s), true);
+ if (error) {
+ return error;
+ }
+ if (expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
}
} else {
- ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Bug in bytecode compiler, unexpected node in parse tree while parsing statement."); //unreachable code
+ 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();
+ codegen.end_block();
return OK;
}
Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready) {
- Vector<int> bytecode;
+ Error error = OK;
CodeGen codegen;
+ codegen.generator = memnew(GDScriptByteCodeGenerator);
codegen.class_node = p_class;
codegen.script = p_script;
codegen.function_node = p_func;
- 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;
+ StringName func_name;
+ bool is_static = false;
+ MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+ GDScriptDataType return_type;
+ return_type.has_type = true;
+ return_type.kind = GDScriptDataType::BUILTIN;
+ return_type.builtin_type = Variant::NIL;
+
+ if (p_func) {
+ func_name = p_func->identifier->name;
+ is_static = p_func->is_static;
+ rpc_mode = p_func->rpc_mode;
+ return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script);
+ } else {
+ if (p_for_ready) {
+ func_name = "_ready";
+ } else {
+ func_name = "@implicit_new";
+ }
+ }
+
+ codegen.function_name = func_name;
+ codegen.generator->write_start(p_script, func_name, is_static, rpc_mode, return_type);
+
int optional_parameters = 0;
if (p_func) {
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])) {
- // _set_error("Name for argument '" + String(p_func->arguments[i]) + "' can't shadow class property of the same name.", p_func);
- // return ERR_ALREADY_EXISTS;
- //}
-
- codegen.add_stack_identifier(p_func->parameters[i]->identifier->name, i);
-#ifdef TOOLS_ENABLED
- argnames.push_back(p_func->parameters[i]->identifier->name);
-#endif
+ const GDScriptParser::ParameterNode *parameter = p_func->parameters[i];
+ GDScriptDataType par_type = _gdtype_from_datatype(parameter->get_datatype(), p_script);
+ uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->default_value != nullptr, par_type);
+ codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type);
+
if (p_func->parameters[i]->default_value != nullptr) {
optional_parameters++;
}
}
- stack_level = p_func->parameters.size();
}
- codegen.alloc_stack(stack_level);
-
- /* Parse initializer -if applies- */
-
+ // Parse initializer if applies.
bool is_implicit_initializer = !p_for_ready && !p_func;
bool is_initializer = p_func && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init;
+ bool is_for_ready = p_for_ready || (p_func && String(p_func->identifier->name) == "_ready");
- if (is_implicit_initializer) {
+ if (is_implicit_initializer || is_for_ready) {
// 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) {
+ if (field->onready != is_for_ready) {
// 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);
+ codegen.generator->write_newline(field->initializer->start_line);
- int src_address = _parse_expression(codegen, field->initializer, stack_level, false, true);
- if (src_address < 0) {
- return ERR_PARSE_ERROR;
+ GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, field->initializer, false, true);
+ if (error) {
+ memdelete(codegen.generator);
+ return error;
}
- int dst_address = codegen.script->member_indices[field->identifier->name].index;
- dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS;
+ GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype()));
- if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) {
- return ERR_PARSE_ERROR;
+ codegen.generator->write_assign(dst_address, src_address);
+ if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
}
}
}
}
- 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;
- }
- }
- }
- }
-
- /* Parse default argument code -if applies- */
-
- Vector<int> defarg_addr;
- StringName func_name;
-
+ // Parse default argument code if applies.
if (p_func) {
if (optional_parameters > 0) {
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT);
- defarg_addr.push_back(codegen.opcodes.size());
+ codegen.generator->start_parameters();
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;
+ const GDScriptParser::ParameterNode *parameter = p_func->parameters[i];
+ GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, error, parameter->default_value, true);
+ if (error) {
+ memdelete(codegen.generator);
+ return 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;
+ GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name];
+ codegen.generator->write_assign_default_parameter(dst_addr, src_addr);
+ if (src_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ codegen.generator->pop_temporary();
}
- defarg_addr.push_back(codegen.opcodes.size());
}
- defarg_addr.invert();
+ codegen.generator->end_parameters();
}
- func_name = p_func->identifier->name;
- codegen.function_name = func_name;
- Error err = _parse_block(codegen, p_func->body, stack_level);
+ Error err = _parse_block(codegen, p_func->body);
if (err) {
+ memdelete(codegen.generator);
return err;
}
-
- } else {
- if (p_for_ready) {
- func_name = "_ready";
- } else {
- func_name = "@implicit_new";
- }
}
- codegen.function_name = func_name;
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_END);
-
- /*
- if (String(p_func->name)=="") { //initializer func
- gdfunc = &p_script->initializer;
- */
- //} else { //regular func
- p_script->member_functions[func_name] = memnew(GDScriptFunction);
- GDScriptFunction *gdfunc = p_script->member_functions[func_name];
- //}
-
- if (p_func) {
- gdfunc->_static = p_func->is_static;
- gdfunc->rpc_mode = p_func->rpc_mode;
- 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->get_datatype());
- } else {
- gdfunc->_static = false;
- gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
- gdfunc->return_type = GDScriptDataType();
- gdfunc->return_type.has_type = true;
- gdfunc->return_type.kind = GDScriptDataType::BUILTIN;
- gdfunc->return_type.builtin_type = Variant::NIL;
- }
-
-#ifdef TOOLS_ENABLED
- gdfunc->arg_names = argnames;
-#endif
- //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
-
- if (codegen.opcodes.size()) {
- gdfunc->code = codegen.opcodes;
- gdfunc->_code_ptr = &gdfunc->code[0];
- gdfunc->_code_size = codegen.opcodes.size();
-
- } else {
- gdfunc->_code_ptr = nullptr;
- gdfunc->_code_size = 0;
- }
-
- if (defarg_addr.size()) {
- gdfunc->default_arguments = defarg_addr;
- gdfunc->_default_arg_count = defarg_addr.size() - 1;
- gdfunc->_default_arg_ptr = &gdfunc->default_arguments[0];
- } else {
- gdfunc->_default_arg_count = 0;
- gdfunc->_default_arg_ptr = nullptr;
- }
-
- 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;
#ifdef DEBUG_ENABLED
if (EngineDebugger::is_active()) {
String signature;
- //path
+ // Path.
if (p_script->get_path() != String()) {
signature += p_script->get_path();
}
- //loc
+ // Location.
if (p_func) {
signature += "::" + itos(p_func->body->start_line);
} else {
signature += "::0";
}
- //function and class
+ // Function and class.
if (p_class->identifier) {
signature += "::" + String(p_class->identifier->name) + "." + String(func_name);
@@ -2376,65 +1878,57 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
signature += "::" + String(func_name);
}
- gdfunc->profile.signature = signature;
+ codegen.generator->set_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
if (p_func) {
- gdfunc->_initial_line = p_func->start_line;
+ codegen.generator->set_initial_line(p_func->start_line);
#ifdef TOOLS_ENABLED
-
p_script->member_lines[func_name] = p_func->start_line;
+ p_script->doc_functions[func_name] = p_func->doc_description;
#endif
} else {
- gdfunc->_initial_line = 0;
+ codegen.generator->set_initial_line(0);
}
- if (codegen.debug_stack) {
- gdfunc->stack_debug = codegen.stack_debug;
- }
+ GDScriptFunction *gd_function = codegen.generator->write_end();
if (is_initializer) {
- p_script->initializer = gdfunc;
+ p_script->initializer = gd_function;
+ } else if (is_implicit_initializer) {
+ p_script->implicit_initializer = gd_function;
}
- if (is_implicit_initializer) {
- p_script->implicit_initializer = gdfunc;
+
+ if (p_func) {
+ // if no return statement -> return type is void not unresolved Variant
+ if (p_func->body->has_return) {
+ gd_function->return_type = _gdtype_from_datatype(p_func->get_datatype());
+ } else {
+ gd_function->return_type = GDScriptDataType();
+ gd_function->return_type.has_type = true;
+ gd_function->return_type.kind = GDScriptDataType::BUILTIN;
+ gd_function->return_type.builtin_type = Variant::NIL;
+ }
+#ifdef TOOLS_ENABLED
+ gd_function->default_arg_values = p_func->default_arg_values;
+#endif
}
+ p_script->member_functions[func_name] = gd_function;
+
+ memdelete(codegen.generator);
+
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;
+ Error error = OK;
CodeGen codegen;
+ codegen.generator = memnew(GDScriptByteCodeGenerator);
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;
@@ -2443,76 +1937,33 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP
} 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;
+ GDScriptDataType return_type;
+ if (p_is_setter) {
+ return_type.has_type = true;
+ return_type.kind = GDScriptDataType::BUILTIN;
+ return_type.builtin_type = Variant::NIL;
+ } else {
+ return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script);
}
- codegen.opcodes.push_back(GDScriptFunction::OPCODE_END);
+ codegen.generator->write_start(p_script, func_name, false, p_variable->rpc_mode, return_type);
- 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;
+ if (p_is_setter) {
+ uint32_t par_addr = codegen.generator->add_parameter(p_variable->setter_parameter->name, false, _gdtype_from_datatype(p_variable->get_datatype()));
+ codegen.parameters[p_variable->setter_parameter->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, _gdtype_from_datatype(p_variable->get_datatype()));
}
- //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;
+ error = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter);
+ if (error) {
+ memdelete(codegen.generator);
+ return error;
}
-#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
+ GDScriptFunction *gd_function = codegen.generator->write_end();
+
+ p_script->member_functions[func_name] = gd_function;
- 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;
@@ -2531,29 +1982,15 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP
signature += "::" + String(func_name);
}
- gdfunc->profile.signature = signature;
+ codegen.generator->set_signature(signature);
}
#endif
- gdfunc->_script = p_script;
- gdfunc->source = source;
+ codegen.generator->set_initial_line(p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line);
-#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;
+ p_script->member_lines[func_name] = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line;
#endif
-
- if (codegen.debug_stack) {
- gdfunc->stack_debug = codegen.stack_debug;
- }
+ memdelete(codegen.generator);
return OK;
}
@@ -2575,6 +2012,24 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
}
}
+#ifdef TOOLS_ENABLED
+ p_script->doc_functions.clear();
+ p_script->doc_variables.clear();
+ p_script->doc_constants.clear();
+ p_script->doc_enums.clear();
+ p_script->doc_signals.clear();
+ p_script->doc_tutorials.clear();
+
+ p_script->doc_brief_description = p_class->doc_brief_description;
+ p_script->doc_description = p_class->doc_description;
+ for (int i = 0; i < p_class->doc_tutorials.size(); i++) {
+ DocData::TutorialDoc td;
+ td.title = p_class->doc_tutorials[i].first;
+ td.link = p_class->doc_tutorials[i].second;
+ p_script->doc_tutorials.append(td);
+ }
+#endif
+
p_script->native = Ref<GDScriptNativeClass>();
p_script->base = Ref<GDScript>();
p_script->_base = nullptr;
@@ -2604,25 +2059,38 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
p_script->native = native;
} break;
case GDScriptDataType::GDSCRIPT: {
- Ref<GDScript> base = base_type.script_type;
+ Ref<GDScript> base = Ref<GDScript>(base_type.script_type);
p_script->base = base;
p_script->_base = base.ptr();
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)) {
- 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;
+ if (p_class->base_type.script_path == main_script->path) {
+ if (!parsed_classes.has(p_script->_base)) {
+ if (parsing_classes.has(p_script->_base)) {
+ 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);
+ if (err) {
+ return err;
+ }
}
- Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state);
+ } else {
+ Error err = OK;
+ base = GDScriptCache::get_full_script(p_class->base_type.script_path, err, main_script->path);
if (err) {
return err;
}
+ if (base.is_null() && !base->is_valid()) {
+ return ERR_COMPILATION_FAILED;
+ }
}
}
p_script->member_indices = base->member_indices;
+ native = base->native;
+ p_script->native = native;
} break;
default: {
_set_error("Parser bug: invalid inheritance.", p_class);
@@ -2660,7 +2128,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
break;
}
minfo.rpc_mode = variable->rpc_mode;
- minfo.data_type = _gdtype_from_datatype(variable->get_datatype());
+ minfo.data_type = _gdtype_from_datatype(variable->get_datatype(), p_script);
PropertyInfo prop_info = minfo.data_type;
prop_info.name = name;
@@ -2674,20 +2142,23 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
prop_info.hint = export_info.hint;
prop_info.hint_string = export_info.hint_string;
prop_info.usage = export_info.usage;
-#ifdef TOOLS_ENABLED
- 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;
}
+#ifdef TOOLS_ENABLED
+ p_script->doc_variables[name] = variable->doc_description;
+#endif
p_script->member_info[name] = prop_info;
p_script->member_indices[name] = minfo;
p_script->members.insert(name);
#ifdef TOOLS_ENABLED
+ if (variable->initializer != nullptr && variable->initializer->is_constant) {
+ p_script->member_default_values[name] = variable->initializer->reduced_value;
+ } else {
+ p_script->member_default_values.erase(name);
+ }
p_script->member_lines[name] = variable->start_line;
#endif
} break;
@@ -2696,14 +2167,12 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
const GDScriptParser::ConstantNode *constant = member.constant;
StringName name = constant->identifier->name;
- ERR_CONTINUE(constant->initializer->type != GDScriptParser::Node::LITERAL);
-
- const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(constant->initializer);
-
- p_script->constants.insert(name, literal->value);
+ p_script->constants.insert(name, constant->initializer->reduced_value);
#ifdef TOOLS_ENABLED
-
p_script->member_lines[name] = constant->start_line;
+ if (constant->doc_description != String()) {
+ p_script->doc_constants[name] = constant->doc_description;
+ }
#endif
} break;
@@ -2714,6 +2183,15 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
p_script->constants.insert(name, enum_value.value);
#ifdef TOOLS_ENABLED
p_script->member_lines[name] = enum_value.identifier->start_line;
+ if (!p_script->doc_enums.has("@unnamed_enums")) {
+ p_script->doc_enums["@unnamed_enums"] = DocData::EnumDoc();
+ p_script->doc_enums["@unnamed_enums"].name = "@unnamed_enums";
+ }
+ DocData::ConstantDoc const_doc;
+ const_doc.name = enum_value.identifier->name;
+ const_doc.value = Variant(enum_value.value).operator String(); // TODO-DOC: enum value currently is int.
+ const_doc.description = enum_value.doc_description;
+ p_script->doc_enums["@unnamed_enums"].values.push_back(const_doc);
#endif
} break;
@@ -2749,6 +2227,11 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
parameters_names.write[j] = signal->parameters[j]->identifier->name;
}
p_script->_signals[name] = parameters_names;
+#ifdef TOOLS_ENABLED
+ if (!signal->doc_description.empty()) {
+ p_script->doc_signals[name] = signal->doc_description;
+ }
+#endif
} break;
case GDScriptParser::ClassNode::Member::ENUM: {
@@ -2765,6 +2248,16 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
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;
+ p_script->doc_enums[enum_n->identifier->name] = DocData::EnumDoc();
+ p_script->doc_enums[enum_n->identifier->name].name = enum_n->identifier->name;
+ p_script->doc_enums[enum_n->identifier->name].description = enum_n->doc_description;
+ for (int j = 0; j < enum_n->values.size(); j++) {
+ DocData::ConstantDoc const_doc;
+ const_doc.name = enum_n->values[j].identifier->name;
+ const_doc.value = Variant(enum_n->values[j].value).operator String();
+ const_doc.description = enum_n->values[j].doc_description;
+ p_script->doc_enums[enum_n->identifier->name].values.push_back(const_doc);
+ }
#endif
} break;
default:
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index e8601f69c7..157c801f56 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -31,111 +31,86 @@
#ifndef GDSCRIPT_COMPILER_H
#define GDSCRIPT_COMPILER_H
-#include "core/set.h"
+#include "core/templates/set.h"
#include "gdscript.h"
+#include "gdscript_codegen.h"
#include "gdscript_function.h"
#include "gdscript_parser.h"
class GDScriptCompiler {
- const GDScriptParser *parser;
+ const GDScriptParser *parser = nullptr;
Set<GDScript *> parsed_classes;
Set<GDScript *> parsing_classes;
- GDScript *main_script;
+ GDScript *main_script = nullptr;
+
struct CodeGen {
- GDScript *script;
- const GDScriptParser::ClassNode *class_node;
- const GDScriptParser::FunctionNode *function_node;
+ GDScript *script = nullptr;
+ const GDScriptParser::ClassNode *class_node = nullptr;
+ const GDScriptParser::FunctionNode *function_node = nullptr;
StringName function_name;
- bool debug_stack;
-
- List<Map<StringName, int>> stack_id_stack;
- Map<StringName, int> stack_identifiers;
-
- 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;
- if (debug_stack) {
- block_identifiers[p_id] = p_stackpos;
- GDScriptFunction::StackDebug sd;
- sd.added = true;
- sd.line = current_line;
- sd.identifier = p_id;
- sd.pos = p_stackpos;
- stack_debug.push_back(sd);
- }
+ GDScriptCodeGenerator *generator = nullptr;
+ Map<StringName, GDScriptCodeGenerator::Address> parameters;
+ Map<StringName, GDScriptCodeGenerator::Address> locals;
+ List<Map<StringName, GDScriptCodeGenerator::Address>> locals_stack;
+
+ GDScriptCodeGenerator::Address add_local(const StringName &p_name, const GDScriptDataType &p_type) {
+ uint32_t addr = generator->add_local(p_name, p_type);
+ locals[p_name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::LOCAL_VARIABLE, addr, p_type);
+ return locals[p_name];
}
- void push_stack_identifiers() {
- stack_id_stack.push_back(stack_identifiers);
- if (debug_stack) {
- block_identifier_stack.push_back(block_identifiers);
- block_identifiers.clear();
- }
+ GDScriptCodeGenerator::Address add_local_constant(const StringName &p_name, const Variant &p_value) {
+ uint32_t addr = generator->add_local_constant(p_name, p_value);
+ locals[p_name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::LOCAL_CONSTANT, addr);
+ return locals[p_name];
}
- void pop_stack_identifiers() {
- stack_identifiers = stack_id_stack.back()->get();
- stack_id_stack.pop_back();
-
- if (debug_stack) {
- for (Map<StringName, int>::Element *E = block_identifiers.front(); E; E = E->next()) {
- GDScriptFunction::StackDebug sd;
- sd.added = false;
- sd.identifier = E->key();
- sd.line = current_line;
- sd.pos = E->get();
- stack_debug.push_back(sd);
- }
- block_identifiers = block_identifier_stack.back()->get();
- block_identifier_stack.pop_back();
- }
+ GDScriptCodeGenerator::Address add_temporary(const GDScriptDataType &p_type = GDScriptDataType()) {
+ uint32_t addr = generator->add_temporary();
+ return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::TEMPORARY, addr, p_type);
}
- HashMap<Variant, int, VariantHasher, VariantComparator> constant_map;
- Map<StringName, int> name_map;
-#ifdef TOOLS_ENABLED
- Vector<StringName> named_globals;
-#endif
-
- int get_name_map_pos(const StringName &p_identifier) {
- int ret;
- if (!name_map.has(p_identifier)) {
- ret = name_map.size();
- name_map[p_identifier] = ret;
- } else {
- ret = name_map[p_identifier];
+ GDScriptCodeGenerator::Address add_constant(const Variant &p_constant) {
+ GDScriptDataType type;
+ type.has_type = true;
+ type.kind = GDScriptDataType::BUILTIN;
+ type.builtin_type = p_constant.get_type();
+ if (type.builtin_type == Variant::OBJECT) {
+ Object *obj = p_constant;
+ if (obj) {
+ type.kind = GDScriptDataType::NATIVE;
+ type.native_type = obj->get_class_name();
+
+ Ref<Script> script = obj->get_script();
+ if (script.is_valid()) {
+ type.script_type = script.ptr();
+ Ref<GDScript> gdscript = script;
+ if (gdscript.is_valid()) {
+ type.kind = GDScriptDataType::GDSCRIPT;
+ } else {
+ type.kind = GDScriptDataType::SCRIPT;
+ }
+ }
+ } else {
+ type.builtin_type = Variant::NIL;
+ }
}
- return ret;
- }
- int get_constant_pos(const Variant &p_constant) {
- if (constant_map.has(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 | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS);
+ uint32_t addr = generator->add_or_get_constant(p_constant);
+ return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CONSTANT, addr, type);
}
- Vector<int> opcodes;
- void alloc_stack(int p_level) {
- if (p_level >= stack_max) {
- stack_max = p_level + 1;
- }
- }
- void alloc_call(int p_params) {
- if (p_params >= call_max) {
- call_max = p_params;
- }
+ void start_block() {
+ Map<StringName, GDScriptCodeGenerator::Address> old_locals = locals;
+ locals_stack.push_back(old_locals);
+ generator->start_block();
}
- int current_line;
- int stack_max;
- int call_max;
+ void end_block() {
+ locals = locals_stack.back()->get();
+ locals_stack.pop_back();
+ generator->end_block();
+ }
};
bool _is_class_member_property(CodeGen &codegen, const StringName &p_name);
@@ -143,17 +118,16 @@ class GDScriptCompiler {
void _set_error(const String &p_error, const GDScriptParser::Node *p_node);
- 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);
+ Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
+ Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
- GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const;
+ GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner = nullptr) const;
- 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);
+ GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
+ GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
+ GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested);
+ void _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
+ Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true);
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);
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
new file mode 100644
index 0000000000..92a44c57f8
--- /dev/null
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -0,0 +1,810 @@
+/*************************************************************************/
+/* gdscript_disassembler.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifdef DEBUG_ENABLED
+
+#include "gdscript_function.h"
+
+#include "core/string/string_builder.h"
+#include "gdscript.h"
+#include "gdscript_functions.h"
+
+static String _get_variant_string(const Variant &p_variant) {
+ String txt;
+ if (p_variant.get_type() == Variant::STRING) {
+ txt = "\"" + String(p_variant) + "\"";
+ } else if (p_variant.get_type() == Variant::STRING_NAME) {
+ txt = "&\"" + String(p_variant) + "\"";
+ } else if (p_variant.get_type() == Variant::NODE_PATH) {
+ txt = "^\"" + String(p_variant) + "\"";
+ } else if (p_variant.get_type() == Variant::OBJECT) {
+ Object *obj = p_variant;
+ if (!obj) {
+ txt = "null";
+ } else {
+ GDScriptNativeClass *cls = Object::cast_to<GDScriptNativeClass>(obj);
+ if (cls) {
+ txt += cls->get_name();
+ txt += " (class)";
+ } else {
+ txt = obj->get_class();
+ if (obj->get_script_instance()) {
+ txt += "(" + obj->get_script_instance()->get_script()->get_path() + ")";
+ }
+ }
+ }
+ } else {
+ txt = p_variant;
+ }
+ return txt;
+}
+
+static String _disassemble_address(const GDScript *p_script, const GDScriptFunction &p_function, int p_address) {
+ int addr = p_address & GDScriptFunction::ADDR_MASK;
+
+ switch (p_address >> 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(" + p_function.get_global_name(addr) + ")";
+ } break;
+ case GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT: {
+ return "const(" + _get_variant_string(p_function.get_constant(addr)) + ")";
+ } 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(" + _get_variant_string(GDScriptLanguage::get_singleton()->get_global_array()[addr]) + ")";
+ } break;
+ case GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL: {
+ return "named_global(" + p_function.get_global_name(addr) + ")";
+ } break;
+ case GDScriptFunction::ADDR_TYPE_NIL: {
+ return "nil";
+ } break;
+ }
+
+ return "<err>";
+}
+
+void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
+#define DADDR(m_ip) (_disassemble_address(_script, *this, _code_ptr[ip + m_ip]))
+
+ for (int ip = 0; ip < _code_size;) {
+ StringBuilder text;
+ int incr = 0;
+
+ text += " ";
+ text += itos(ip);
+ text += ": ";
+
+ // This makes the compiler complain if some opcode is unchecked in the switch.
+ Opcode code = Opcode(_code_ptr[ip] & INSTR_MASK);
+ int instr_var_args = (_code_ptr[ip] & INSTR_ARGS_MASK) >> INSTR_BITS;
+
+ switch (code) {
+ case OPCODE_OPERATOR: {
+ int operation = _code_ptr[ip + 4];
+
+ text += "operator ";
+
+ text += DADDR(3);
+ text += " = ";
+ text += DADDR(1);
+ text += " ";
+ text += Variant::get_operator_name(Variant::Operator(operation));
+ text += " ";
+ text += DADDR(2);
+
+ incr += 5;
+ } break;
+ case OPCODE_OPERATOR_VALIDATED: {
+ text += "validated operator ";
+
+ text += DADDR(3);
+ text += " = ";
+ text += DADDR(1);
+ text += " <operator function> ";
+ text += DADDR(2);
+
+ incr += 5;
+ } break;
+ case OPCODE_EXTENDS_TEST: {
+ text += "is object ";
+ text += DADDR(3);
+ text += " = ";
+ text += DADDR(1);
+ text += " is ";
+ text += DADDR(2);
+
+ incr += 4;
+ } break;
+ case OPCODE_IS_BUILTIN: {
+ text += "is builtin ";
+ text += DADDR(2);
+ text += " = ";
+ text += DADDR(1);
+ text += " is ";
+ text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3]));
+
+ incr += 4;
+ } break;
+ case OPCODE_SET_KEYED: {
+ text += "set keyed ";
+ text += DADDR(1);
+ text += "[";
+ text += DADDR(2);
+ text += "] = ";
+ text += DADDR(3);
+
+ incr += 4;
+ } break;
+ case OPCODE_SET_KEYED_VALIDATED: {
+ text += "set keyed validated ";
+ text += DADDR(1);
+ text += "[";
+ text += DADDR(2);
+ text += "] = ";
+ text += DADDR(3);
+
+ incr += 5;
+ } break;
+ case OPCODE_SET_INDEXED_VALIDATED: {
+ text += "set indexed validated ";
+ text += DADDR(1);
+ text += "[";
+ text += DADDR(2);
+ text += "] = ";
+ text += DADDR(3);
+
+ incr += 5;
+ } break;
+ case OPCODE_GET_KEYED: {
+ text += "get keyed ";
+ text += DADDR(3);
+ text += " = ";
+ text += DADDR(1);
+ text += "[";
+ text += DADDR(2);
+ text += "]";
+
+ incr += 4;
+ } break;
+ case OPCODE_GET_KEYED_VALIDATED: {
+ text += "get keyed validated ";
+ text += DADDR(3);
+ text += " = ";
+ text += DADDR(1);
+ text += "[";
+ text += DADDR(2);
+ text += "]";
+
+ incr += 5;
+ } break;
+ case OPCODE_GET_INDEXED_VALIDATED: {
+ text += "get indexed validated ";
+ text += DADDR(3);
+ text += " = ";
+ text += DADDR(1);
+ text += "[";
+ text += DADDR(2);
+ text += "]";
+
+ incr += 5;
+ } break;
+ case OPCODE_SET_NAMED: {
+ text += "set_named ";
+ text += DADDR(1);
+ text += "[\"";
+ text += _global_names_ptr[_code_ptr[ip + 3]];
+ text += "\"] = ";
+ text += DADDR(2);
+
+ incr += 4;
+ } break;
+ case OPCODE_SET_NAMED_VALIDATED: {
+ text += "set_named validated ";
+ text += DADDR(1);
+ text += "[\"";
+ text += "<unknown name>";
+ text += "\"] = ";
+ text += DADDR(2);
+
+ incr += 4;
+ } break;
+ case OPCODE_GET_NAMED: {
+ text += "get_named ";
+ text += DADDR(2);
+ text += " = ";
+ text += DADDR(1);
+ text += "[\"";
+ text += _global_names_ptr[_code_ptr[ip + 3]];
+ text += "\"]";
+
+ incr += 4;
+ } break;
+ case OPCODE_GET_NAMED_VALIDATED: {
+ text += "get_named validated ";
+ text += DADDR(2);
+ text += " = ";
+ text += DADDR(1);
+ text += "[\"";
+ text += "<unknown name>";
+ text += "\"]";
+
+ incr += 4;
+ } break;
+ case OPCODE_SET_MEMBER: {
+ text += "set_member ";
+ text += "[\"";
+ text += _global_names_ptr[_code_ptr[ip + 2]];
+ text += "\"] = ";
+ text += DADDR(1);
+
+ incr += 3;
+ } break;
+ case OPCODE_GET_MEMBER: {
+ text += "get_member ";
+ text += DADDR(1);
+ text += " = ";
+ text += "[\"";
+ text += _global_names_ptr[_code_ptr[ip + 2]];
+ text += "\"]";
+
+ incr += 3;
+ } break;
+ case OPCODE_ASSIGN: {
+ text += "assign ";
+ text += DADDR(1);
+ text += " = ";
+ text += DADDR(2);
+
+ incr += 3;
+ } break;
+ case OPCODE_ASSIGN_TRUE: {
+ text += "assign ";
+ text += DADDR(1);
+ text += " = true";
+
+ incr += 2;
+ } break;
+ case OPCODE_ASSIGN_FALSE: {
+ text += "assign ";
+ text += DADDR(1);
+ text += " = false";
+
+ incr += 2;
+ } break;
+ case OPCODE_ASSIGN_TYPED_BUILTIN: {
+ text += "assign typed builtin (";
+ text += Variant::get_type_name((Variant::Type)_code_ptr[ip + 3]);
+ text += ") ";
+ text += DADDR(1);
+ text += " = ";
+ text += DADDR(2);
+
+ incr += 4;
+ } break;
+ case OPCODE_ASSIGN_TYPED_NATIVE: {
+ Variant class_name = _constants_ptr[_code_ptr[ip + 3]];
+ GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(class_name.operator Object *());
+
+ text += "assign typed native (";
+ text += nc->get_name().operator String();
+ text += ") ";
+ text += DADDR(1);
+ text += " = ";
+ text += DADDR(2);
+
+ incr += 4;
+ } break;
+ case OPCODE_ASSIGN_TYPED_SCRIPT: {
+ Variant script = _constants_ptr[_code_ptr[ip + 3]];
+ Script *sc = Object::cast_to<Script>(script.operator Object *());
+
+ text += "assign typed script (";
+ text += sc->get_path();
+ text += ") ";
+ text += DADDR(1);
+ text += " = ";
+ text += DADDR(2);
+
+ incr += 4;
+ } break;
+ case OPCODE_CAST_TO_BUILTIN: {
+ text += "cast builtin ";
+ text += DADDR(2);
+ text += " = ";
+ text += DADDR(1);
+ text += " as ";
+ text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 1]));
+
+ incr += 4;
+ } break;
+ case OPCODE_CAST_TO_NATIVE: {
+ text += "cast native ";
+ text += DADDR(2);
+ text += " = ";
+ text += DADDR(1);
+ text += " as ";
+ text += DADDR(3);
+
+ incr += 4;
+ } break;
+ case OPCODE_CAST_TO_SCRIPT: {
+ text += "cast ";
+ text += DADDR(2);
+ text += " = ";
+ text += DADDR(1);
+ text += " as ";
+ text += DADDR(3);
+
+ incr += 4;
+ } break;
+ case OPCODE_CONSTRUCT: {
+ Variant::Type t = Variant::Type(_code_ptr[ip + 3 + instr_var_args]);
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+
+ text += "construct ";
+ text += DADDR(1 + argc);
+ text += " = ";
+
+ text += Variant::get_type_name(t) + "(";
+ for (int i = 0; i < argc; i++) {
+ if (i > 0)
+ text += ", ";
+ text += DADDR(i + 1);
+ }
+ text += ")";
+
+ incr = 3 + instr_var_args;
+ } break;
+ case OPCODE_CONSTRUCT_VALIDATED: {
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+
+ text += "construct validated ";
+ text += DADDR(1 + argc);
+ text += " = ";
+
+ text += "<unkown type>(";
+ for (int i = 0; i < argc; i++) {
+ if (i > 0)
+ text += ", ";
+ text += DADDR(i + 1);
+ }
+ text += ")";
+
+ incr = 3 + instr_var_args;
+ } break;
+ case OPCODE_CONSTRUCT_ARRAY: {
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+ text += " make_array ";
+ text += DADDR(1 + argc);
+ text += " = [";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0)
+ text += ", ";
+ text += DADDR(1 + i);
+ }
+
+ text += "]";
+
+ incr += 3 + argc;
+ } break;
+ case OPCODE_CONSTRUCT_DICTIONARY: {
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+ text += "make_dict ";
+ text += DADDR(1 + argc * 2);
+ text += " = {";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0)
+ text += ", ";
+ text += DADDR(1 + i * 2 + 0);
+ text += ": ";
+ text += DADDR(1 + i * 2 + 1);
+ }
+
+ text += "}";
+
+ incr += 3 + argc * 2;
+ } break;
+ case OPCODE_CALL:
+ case OPCODE_CALL_RETURN:
+ case OPCODE_CALL_ASYNC: {
+ bool ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_RETURN;
+ bool async = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_ASYNC;
+
+ if (ret) {
+ text += "call-ret ";
+ } else if (async) {
+ text += "call-async ";
+ } else {
+ text += "call ";
+ }
+
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+ if (ret || async) {
+ text += DADDR(2 + argc) + " = ";
+ }
+
+ text += DADDR(1 + argc) + ".";
+ text += String(_global_names_ptr[_code_ptr[ip + 2 + instr_var_args]]);
+ text += "(";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0)
+ text += ", ";
+ text += DADDR(1 + i);
+ }
+ text += ")";
+
+ incr = 5 + argc;
+ } break;
+ case OPCODE_CALL_METHOD_BIND:
+ case OPCODE_CALL_METHOD_BIND_RET: {
+ bool ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_METHOD_BIND_RET;
+
+ if (ret) {
+ text += "call-method_bind-ret ";
+ } else {
+ text += "call-method_bind ";
+ }
+
+ MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]];
+
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+ if (ret) {
+ text += DADDR(2 + argc) + " = ";
+ }
+
+ text += DADDR(1 + argc) + ".";
+ text += method->get_name();
+ text += "(";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0)
+ text += ", ";
+ text += DADDR(1 + i);
+ }
+ text += ")";
+
+ incr = 5 + argc;
+ } break;
+ case OPCODE_CALL_PTRCALL_NO_RETURN: {
+ text += "call-ptrcall (no return) ";
+
+ MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]];
+
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+
+ text += DADDR(1 + argc) + ".";
+ text += method->get_name();
+ text += "(";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0)
+ text += ", ";
+ text += DADDR(1 + i);
+ }
+ text += ")";
+
+ incr = 5 + argc;
+ } break;
+
+#define DISASSEMBLE_PTRCALL(m_type) \
+ case OPCODE_CALL_PTRCALL_##m_type: { \
+ text += "call-ptrcall (return "; \
+ text += #m_type; \
+ text += ") "; \
+ MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; \
+ int argc = _code_ptr[ip + 1 + instr_var_args]; \
+ text += DADDR(2 + argc) + " = "; \
+ text += DADDR(1 + argc) + "."; \
+ text += method->get_name(); \
+ text += "("; \
+ for (int i = 0; i < argc; i++) { \
+ if (i > 0) \
+ text += ", "; \
+ text += DADDR(1 + i); \
+ } \
+ text += ")"; \
+ incr = 5 + argc; \
+ } break
+
+ DISASSEMBLE_PTRCALL(BOOL);
+ DISASSEMBLE_PTRCALL(INT);
+ DISASSEMBLE_PTRCALL(FLOAT);
+ DISASSEMBLE_PTRCALL(STRING);
+ DISASSEMBLE_PTRCALL(VECTOR2);
+ DISASSEMBLE_PTRCALL(VECTOR2I);
+ DISASSEMBLE_PTRCALL(RECT2);
+ DISASSEMBLE_PTRCALL(RECT2I);
+ DISASSEMBLE_PTRCALL(VECTOR3);
+ DISASSEMBLE_PTRCALL(VECTOR3I);
+ DISASSEMBLE_PTRCALL(TRANSFORM2D);
+ DISASSEMBLE_PTRCALL(PLANE);
+ DISASSEMBLE_PTRCALL(AABB);
+ DISASSEMBLE_PTRCALL(BASIS);
+ DISASSEMBLE_PTRCALL(TRANSFORM);
+ DISASSEMBLE_PTRCALL(COLOR);
+ DISASSEMBLE_PTRCALL(STRING_NAME);
+ DISASSEMBLE_PTRCALL(NODE_PATH);
+ DISASSEMBLE_PTRCALL(RID);
+ DISASSEMBLE_PTRCALL(QUAT);
+ DISASSEMBLE_PTRCALL(OBJECT);
+ DISASSEMBLE_PTRCALL(CALLABLE);
+ DISASSEMBLE_PTRCALL(SIGNAL);
+ DISASSEMBLE_PTRCALL(DICTIONARY);
+ DISASSEMBLE_PTRCALL(ARRAY);
+ DISASSEMBLE_PTRCALL(PACKED_BYTE_ARRAY);
+ DISASSEMBLE_PTRCALL(PACKED_INT32_ARRAY);
+ DISASSEMBLE_PTRCALL(PACKED_INT64_ARRAY);
+ DISASSEMBLE_PTRCALL(PACKED_FLOAT32_ARRAY);
+ DISASSEMBLE_PTRCALL(PACKED_FLOAT64_ARRAY);
+ DISASSEMBLE_PTRCALL(PACKED_STRING_ARRAY);
+ DISASSEMBLE_PTRCALL(PACKED_VECTOR2_ARRAY);
+ DISASSEMBLE_PTRCALL(PACKED_VECTOR3_ARRAY);
+ DISASSEMBLE_PTRCALL(PACKED_COLOR_ARRAY);
+
+ case OPCODE_CALL_BUILTIN_TYPE_VALIDATED: {
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+
+ text += "call-builtin-method validated ";
+
+ text += DADDR(2 + argc) + " = ";
+
+ text += DADDR(1) + ".";
+ text += "<unknown method>";
+
+ text += "(";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0)
+ text += ", ";
+ text += DADDR(1 + i);
+ }
+ text += ")";
+
+ incr = 5 + argc;
+ } break;
+ case OPCODE_CALL_BUILT_IN: {
+ text += "call-built-in ";
+
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+ text += DADDR(1 + argc) + " = ";
+
+ text += GDScriptFunctions::get_func_name(GDScriptFunctions::Function(_code_ptr[ip + 2 + instr_var_args]));
+ text += "(";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0)
+ text += ", ";
+ text += DADDR(1 + i);
+ }
+ text += ")";
+
+ incr = 4 + argc;
+ } break;
+ case OPCODE_CALL_SELF_BASE: {
+ text += "call-self-base ";
+
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+ text += DADDR(2 + argc) + " = ";
+
+ text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]];
+ text += "(";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0)
+ text += ", ";
+ text += DADDR(1 + i);
+ }
+ text += ")";
+
+ incr = 4 + argc;
+ } break;
+ case OPCODE_AWAIT: {
+ text += "await ";
+ text += DADDR(1);
+
+ incr += 2;
+ } break;
+ case OPCODE_AWAIT_RESUME: {
+ text += "await resume ";
+ text += DADDR(1);
+
+ incr = 2;
+ } break;
+ case OPCODE_JUMP: {
+ text += "jump ";
+ text += itos(_code_ptr[ip + 1]);
+
+ incr = 2;
+ } break;
+ case OPCODE_JUMP_IF: {
+ text += "jump-if ";
+ text += DADDR(1);
+ text += " to ";
+ text += itos(_code_ptr[ip + 2]);
+
+ incr = 3;
+ } break;
+ case OPCODE_JUMP_IF_NOT: {
+ text += "jump-if-not ";
+ text += DADDR(1);
+ text += " to ";
+ text += itos(_code_ptr[ip + 2]);
+
+ incr = 3;
+ } break;
+ case OPCODE_JUMP_TO_DEF_ARGUMENT: {
+ text += "jump-to-default-argument ";
+
+ incr = 1;
+ } break;
+ case OPCODE_RETURN: {
+ text += "return ";
+ text += DADDR(1);
+
+ incr = 2;
+ } break;
+
+#define DISASSEMBLE_ITERATE(m_type) \
+ case OPCODE_ITERATE_##m_type: { \
+ text += "for-loop (typed "; \
+ text += #m_type; \
+ text += ") "; \
+ text += DADDR(3); \
+ text += " in "; \
+ text += DADDR(2); \
+ text += " counter "; \
+ text += DADDR(1); \
+ text += " end "; \
+ text += itos(_code_ptr[ip + 4]); \
+ incr += 5; \
+ } break
+
+#define DISASSEMBLE_ITERATE_BEGIN(m_type) \
+ case OPCODE_ITERATE_BEGIN_##m_type: { \
+ text += "for-init (typed "; \
+ text += #m_type; \
+ text += ") "; \
+ text += DADDR(3); \
+ text += " in "; \
+ text += DADDR(2); \
+ text += " counter "; \
+ text += DADDR(1); \
+ text += " end "; \
+ text += itos(_code_ptr[ip + 4]); \
+ incr += 5; \
+ } break
+
+#define DISASSEMBLE_ITERATE_TYPES(m_macro) \
+ m_macro(INT); \
+ m_macro(FLOAT); \
+ m_macro(VECTOR2); \
+ m_macro(VECTOR2I); \
+ m_macro(VECTOR3); \
+ m_macro(VECTOR3I); \
+ m_macro(STRING); \
+ m_macro(DICTIONARY); \
+ m_macro(ARRAY); \
+ m_macro(PACKED_BYTE_ARRAY); \
+ m_macro(PACKED_INT32_ARRAY); \
+ m_macro(PACKED_INT64_ARRAY); \
+ m_macro(PACKED_FLOAT32_ARRAY); \
+ m_macro(PACKED_FLOAT64_ARRAY); \
+ m_macro(PACKED_STRING_ARRAY); \
+ m_macro(PACKED_VECTOR2_ARRAY); \
+ m_macro(PACKED_VECTOR3_ARRAY); \
+ m_macro(PACKED_COLOR_ARRAY); \
+ m_macro(OBJECT)
+
+ case OPCODE_ITERATE_BEGIN: {
+ text += "for-init ";
+ text += DADDR(3);
+ text += " in ";
+ text += DADDR(2);
+ text += " counter ";
+ text += DADDR(1);
+ text += " end ";
+ text += itos(_code_ptr[ip + 4]);
+
+ incr += 5;
+ } break;
+ DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE_BEGIN);
+ case OPCODE_ITERATE: {
+ text += "for-loop ";
+ text += DADDR(2);
+ text += " in ";
+ text += DADDR(2);
+ text += " counter ";
+ text += DADDR(1);
+ text += " end ";
+ text += itos(_code_ptr[ip + 4]);
+
+ incr += 5;
+ } break;
+ DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE);
+ case OPCODE_LINE: {
+ int line = _code_ptr[ip + 1] - 1;
+ if (line >= 0 && line < p_code_lines.size()) {
+ text += "line ";
+ text += itos(line + 1);
+ text += ": ";
+ text += p_code_lines[line];
+ } else {
+ text += "";
+ }
+
+ incr += 2;
+ } break;
+ case OPCODE_ASSERT: {
+ text += "assert (";
+ text += DADDR(1);
+ text += ", ";
+ text += DADDR(2);
+ text += ")";
+
+ incr += 3;
+ } break;
+ case OPCODE_BREAKPOINT: {
+ text += "breakpoint";
+
+ incr += 1;
+ } break;
+ case OPCODE_END: {
+ text += "== END ==";
+
+ incr += 1;
+ } break;
+ }
+
+ ip += incr;
+ if (text.get_string_length() > 0) {
+ print_line(text.as_string());
+ }
+ }
+}
+
+#endif
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 239015060e..af9673a9b8 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -30,8 +30,8 @@
#include "gdscript.h"
-#include "core/engine.h"
-#include "core/global_constants.h"
+#include "core/config/engine.h"
+#include "core/core_constants.h"
#include "core/os/file_access.h"
#include "gdscript_analyzer.h"
#include "gdscript_compiler.h"
@@ -39,7 +39,7 @@
#include "gdscript_tokenizer.h"
#ifdef TOOLS_ENABLED
-#include "core/project_settings.h"
+#include "core/config/project_settings.h"
#include "editor/editor_file_system.h"
#include "editor/editor_settings.h"
#endif
@@ -193,6 +193,10 @@ bool GDScriptLanguage::supports_builtin_mode() const {
return true;
}
+bool GDScriptLanguage::supports_documentation() const {
+ return true;
+}
+
int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const {
GDScriptTokenizer tokenizer;
tokenizer.set_source_code(p_code);
@@ -383,8 +387,8 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant>
}
bool skip = false;
- for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
- if (E->key() == GlobalConstants::get_global_constant_name(i)) {
+ for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) {
+ if (E->key() == CoreConstants::get_global_constant_name(i)) {
skip = true;
break;
}
@@ -483,6 +487,8 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na
#ifdef TOOLS_ENABLED
+#define COMPLETION_RECURSION_LIMIT 200
+
struct GDScriptCompletionIdentifier {
GDScriptParser::DataType type;
String enumeration;
@@ -766,9 +772,11 @@ static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite,
}
}
-static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result);
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth);
+
+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, int p_recursion_depth) {
+ ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
-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;
@@ -788,6 +796,9 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
continue;
}
option = ScriptCodeCompletionOption(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT);
+ if (member.constant->initializer) {
+ option.default_value = member.constant->initializer->reduced_value;
+ }
break;
case GDScriptParser::ClassNode::Member::CLASS:
if (p_only_functions) {
@@ -820,7 +831,6 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
break;
case GDScriptParser::ClassNode::Member::SIGNAL:
if (p_only_functions || outer) {
- clss = clss->outer;
continue;
}
option = ScriptCodeCompletionOption(member.signal->identifier->name, ScriptCodeCompletionOption::KIND_SIGNAL);
@@ -840,10 +850,12 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
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);
+ _find_identifiers_in_base(base_type, p_only_functions, r_result, p_recursion_depth + 1);
}
-static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) {
+ ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
+
GDScriptParser::DataType base_type = p_base.type;
bool _static = base_type.is_meta_type;
@@ -856,7 +868,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
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);
+ _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result, p_recursion_depth + 1);
// This already finds all parent identifiers, so we are done.
base_type = GDScriptParser::DataType();
} break;
@@ -965,7 +977,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
} break;
case GDScriptParser::DataType::BUILTIN: {
Callable::CallError err;
- Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
+ Variant tmp;
+ Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err);
if (err.error != Callable::CallError::CALL_OK) {
return;
}
@@ -1007,14 +1020,14 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
}
}
-static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
+static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) {
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);
+ _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1);
}
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
@@ -1045,10 +1058,8 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool
}
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",
+ "false", "PI", "TAU", "INF", "NAN", "self", "true", "breakpoint", "tool", "super",
+ "break", "continue", "pass", "return",
0
};
@@ -1059,6 +1070,33 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool
kw++;
}
+ static const char *_keywords_with_space[] = {
+ "and", "in", "not", "or", "as", "class", "extends", "is", "func", "signal", "await",
+ "const", "enum", "static", "var", "if", "elif", "else", "for", "match", "while",
+ 0
+ };
+
+ const char **kws = _keywords_with_space;
+ while (*kws) {
+ ScriptCodeCompletionOption option(*kws, ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ option.insert_text += " ";
+ r_result.insert(option.display, option);
+ kws++;
+ }
+
+ static const char *_keywords_with_args[] = {
+ "assert", "preload",
+ 0
+ };
+
+ const char **kwa = _keywords_with_args;
+ while (*kwa) {
+ ScriptCodeCompletionOption option(*kwa, ScriptCodeCompletionOption::KIND_FUNCTION);
+ option.insert_text += "(";
+ r_result.insert(option.display, option);
+ kwa++;
+ }
+
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) {
@@ -1259,8 +1297,10 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
GDScriptParser::CompletionContext c = p_context;
c.current_line = call->start_line;
+ GDScriptParser::Node::Type callee_type = call->get_callee_type();
+
GDScriptCompletionIdentifier base;
- if (call->callee->type == GDScriptParser::Node::IDENTIFIER || call->is_super) {
+ if (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;
@@ -1271,7 +1311,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
} else {
break;
}
- } else if (call->callee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) {
+ } else if (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;
@@ -1513,7 +1553,8 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
found = _guess_identifier_type_from_base(c, base, id, r_type);
} else if (!found && index.type.kind == GDScriptParser::DataType::BUILTIN) {
Callable::CallError err;
- Variant base_val = Variant::construct(base.type.builtin_type, nullptr, 0, err);
+ Variant base_val;
+ Variant::construct(base.type.builtin_type, base_val, nullptr, 0, err);
bool valid = false;
Variant res = base_val.get(index.value, &valid);
if (valid) {
@@ -1550,9 +1591,14 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
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);
+ Variant d1;
+ Variant::construct(p1.type.builtin_type, d1, nullptr, 0, ce);
+ Variant d2;
+ Variant::construct(p2.type.builtin_type, d2, nullptr, 0, ce);
+
+ Variant v1 = (v1_use_value) ? p1.value : d1;
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);
+ Variant v2 = (v2_use_value) ? p2.value : d2;
// avoid potential invalid ops
if ((op->variant_op == Variant::OP_DIVIDE || op->variant_op == Variant::OP_MODULE) && v2.get_type() == Variant::INT) {
v2 = 1;
@@ -1942,7 +1988,8 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
} break;
case GDScriptParser::DataType::BUILTIN: {
Callable::CallError err;
- Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
+ Variant tmp;
+ Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err);
if (err.error != Callable::CallError::CALL_OK) {
return false;
@@ -2092,7 +2139,8 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex
} break;
case GDScriptParser::DataType::BUILTIN: {
Callable::CallError err;
- Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err);
+ Variant tmp;
+ Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err);
if (err.error != Callable::CallError::CALL_OK) {
return false;
}
@@ -2129,9 +2177,9 @@ static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_co
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);
+ for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) {
+ if (CoreConstants::get_global_constant_enum(i) == current_enum) {
+ ScriptCodeCompletionOption option(CoreConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM);
r_result.insert(option.display, option);
}
}
@@ -2247,7 +2295,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
case GDScriptParser::DataType::BUILTIN: {
if (base.get_type() == Variant::NIL) {
Callable::CallError err;
- base = Variant::construct(base_type.builtin_type, nullptr, 0, err);
+ Variant::construct(base_type.builtin_type, base, nullptr, 0, err);
if (err.error != Callable::CallError::CALL_OK) {
return;
}
@@ -2290,16 +2338,12 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
GDScriptParser::DataType base_type;
bool _static = false;
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_call);
+ GDScriptParser::Node::Type callee_type = GDScriptParser::Node::NONE;
GDScriptCompletionIdentifier connect_base;
if (GDScriptParser::get_builtin_function(call->function_name) < GDScriptFunctions::FUNC_MAX) {
MethodInfo info = GDScriptFunctions::get_info(GDScriptParser::get_builtin_function(call->function_name));
-
- 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);
- }
-
r_arghint = _make_arguments_hint(info, p_argidx);
return;
} else if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) {
@@ -2319,14 +2363,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
i++;
}
return;
- } else if (call->is_super || call->callee->type == GDScriptParser::Node::IDENTIFIER) {
+ } else if (call->is_super || callee_type == GDScriptParser::Node::IDENTIFIER) {
base = p_context.base;
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) {
+ } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) {
const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee);
if (subscript->is_attribute) {
@@ -2396,6 +2440,11 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
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);
+ bool valid = false;
+ Variant default_value = Variant::get_constant_value(completion_context.builtin_type, E->get(), &valid);
+ if (valid) {
+ option.default_value = default_value;
+ }
options.insert(option.display, option);
}
} break;
@@ -2450,7 +2499,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
break;
}
if (!_guess_expression_type(completion_context, static_cast<const GDScriptParser::AssignmentNode *>(completion_context.node)->assignee, type)) {
- _find_identifiers(completion_context, false, options);
+ _find_identifiers(completion_context, false, options, 0);
r_forced = true;
break;
}
@@ -2459,7 +2508,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
_find_enumeration_candidates(completion_context, type.enumeration, options);
r_forced = options.size() > 0;
} else {
- _find_identifiers(completion_context, false, options);
+ _find_identifiers(completion_context, false, options, 0);
r_forced = true;
}
} break;
@@ -2467,7 +2516,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
is_function = true;
[[fallthrough]];
case GDScriptParser::COMPLETION_IDENTIFIER: {
- _find_identifiers(completion_context, is_function, options);
+ _find_identifiers(completion_context, is_function, options, 0);
} break;
case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD:
is_function = true;
@@ -2481,7 +2530,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
break;
}
- _find_identifiers_in_base(base, is_function, options);
+ _find_identifiers_in_base(base, is_function, options, 0);
}
} break;
case GDScriptParser::COMPLETION_SUBSCRIPT: {
@@ -2501,7 +2550,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
c.current_class = nullptr;
}
- _find_identifiers_in_base(base, false, options);
+ _find_identifiers_in_base(base, false, options, 0);
} break;
case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: {
if (!completion_context.current_class) {
@@ -2527,7 +2576,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
// TODO: Improve this to only list types.
if (found) {
- _find_identifiers_in_base(base, false, options);
+ _find_identifiers_in_base(base, false, options, 0);
}
r_forced = true;
} break;
@@ -2648,7 +2697,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
if (!completion_context.current_class) {
break;
}
- _find_identifiers_in_class(completion_context.current_class, true, false, true, options);
+ _find_identifiers_in_class(completion_context.current_class, true, false, true, options, 0);
} break;
}
@@ -2762,6 +2811,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
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();
+ return OK;
}
base_type = base_type.class_type->base_type;
}
@@ -2866,7 +2916,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, NULL, 0, err);
+ Variant::construct(base_type.builtin_type, v, NULL, 0, err);
if (err.error != Callable::CallError::CALL_OK) {
break;
}
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index a4e37a79f8..32372439c5 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -30,1567 +30,7 @@
#include "gdscript_function.h"
-#include "core/os/os.h"
#include "gdscript.h"
-#include "gdscript_functions.h"
-
-Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const {
- int address = p_address & ADDR_MASK;
-
- //sequential table (jump table generated by compiler)
- switch ((p_address & ADDR_TYPE_MASK) >> ADDR_BITS) {
- case ADDR_TYPE_SELF: {
-#ifdef DEBUG_ENABLED
- if (unlikely(!p_instance)) {
- r_error = "Cannot access self without instance.";
- return nullptr;
- }
-#endif
- return &self;
- } break;
- case ADDR_TYPE_CLASS: {
- return &static_ref;
- } break;
- case ADDR_TYPE_MEMBER: {
-#ifdef DEBUG_ENABLED
- if (unlikely(!p_instance)) {
- r_error = "Cannot access member without instance.";
- return nullptr;
- }
-#endif
- //member indexing is O(1)
- return &p_instance->members.write[address];
- } break;
- case ADDR_TYPE_CLASS_CONSTANT: {
- //todo change to index!
- GDScript *s = p_script;
-#ifdef DEBUG_ENABLED
- ERR_FAIL_INDEX_V(address, _global_names_count, nullptr);
-#endif
- const StringName *sn = &_global_names_ptr[address];
-
- while (s) {
- GDScript *o = s;
- while (o) {
- Map<StringName, Variant>::Element *E = o->constants.find(*sn);
- if (E) {
- return &E->get();
- }
- o = o->_owner;
- }
- s = s->_base;
- }
-
- ERR_FAIL_V_MSG(nullptr, "GDScriptCompiler bug.");
- } break;
- case ADDR_TYPE_LOCAL_CONSTANT: {
-#ifdef DEBUG_ENABLED
- ERR_FAIL_INDEX_V(address, _constant_count, nullptr);
-#endif
- return &_constants_ptr[address];
- } break;
- case ADDR_TYPE_STACK:
- case ADDR_TYPE_STACK_VARIABLE: {
-#ifdef DEBUG_ENABLED
- ERR_FAIL_INDEX_V(address, _stack_size, nullptr);
-#endif
- return &p_stack[address];
- } break;
- case ADDR_TYPE_GLOBAL: {
-#ifdef DEBUG_ENABLED
- ERR_FAIL_INDEX_V(address, GDScriptLanguage::get_singleton()->get_global_array_size(), nullptr);
-#endif
- return &GDScriptLanguage::get_singleton()->get_global_array()[address];
- } break;
-#ifdef TOOLS_ENABLED
- case ADDR_TYPE_NAMED_GLOBAL: {
-#ifdef DEBUG_ENABLED
- ERR_FAIL_INDEX_V(address, _named_globals_count, nullptr);
-#endif
- StringName id = _named_globals_ptr[address];
-
- if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) {
- return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id];
- } else {
- r_error = "Autoload singleton '" + String(id) + "' has been removed.";
- return nullptr;
- }
- } break;
-#endif
- case ADDR_TYPE_NIL: {
- return &nil;
- } break;
- }
-
- ERR_FAIL_V_MSG(nullptr, "Bad code! (unknown addressing mode).");
- return nullptr;
-}
-
-#ifdef DEBUG_ENABLED
-static String _get_var_type(const Variant *p_var) {
- String basestr;
-
- if (p_var->get_type() == Variant::OBJECT) {
- bool was_freed;
- Object *bobj = p_var->get_validated_object_with_check(was_freed);
- if (!bobj) {
- if (was_freed) {
- basestr = "null instance";
- } else {
- basestr = "previously freed";
- }
- } else {
- if (bobj->get_script_instance()) {
- basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")";
- } else {
- basestr = bobj->get_class();
- }
- }
-
- } else {
- basestr = Variant::get_type_name(p_var->get_type());
- }
-
- return basestr;
-}
-#endif // DEBUG_ENABLED
-
-String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const {
- String err_text;
-
- if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
- int errorarg = p_err.argument;
- // Handle the Object to Object case separately as we don't have further class details.
-#ifdef DEBUG_ENABLED
- if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) {
- err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class.";
- } else
-#endif // DEBUG_ENABLED
- {
- err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + ".";
- }
- } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
- err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
- } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
- err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
- } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
- err_text = "Invalid call. Nonexistent " + p_where + ".";
- } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) {
- err_text = "Attempt to call " + p_where + " on a null instance.";
- } else {
- err_text = "Bug, call error: #" + itos(p_err.error);
- }
-
- return err_text;
-}
-
-#if defined(__GNUC__)
-#define OPCODES_TABLE \
- static const void *switch_table_ops[] = { \
- &&OPCODE_OPERATOR, \
- &&OPCODE_EXTENDS_TEST, \
- &&OPCODE_IS_BUILTIN, \
- &&OPCODE_SET, \
- &&OPCODE_GET, \
- &&OPCODE_SET_NAMED, \
- &&OPCODE_GET_NAMED, \
- &&OPCODE_SET_MEMBER, \
- &&OPCODE_GET_MEMBER, \
- &&OPCODE_ASSIGN, \
- &&OPCODE_ASSIGN_TRUE, \
- &&OPCODE_ASSIGN_FALSE, \
- &&OPCODE_ASSIGN_TYPED_BUILTIN, \
- &&OPCODE_ASSIGN_TYPED_NATIVE, \
- &&OPCODE_ASSIGN_TYPED_SCRIPT, \
- &&OPCODE_CAST_TO_BUILTIN, \
- &&OPCODE_CAST_TO_NATIVE, \
- &&OPCODE_CAST_TO_SCRIPT, \
- &&OPCODE_CONSTRUCT, \
- &&OPCODE_CONSTRUCT_ARRAY, \
- &&OPCODE_CONSTRUCT_DICTIONARY, \
- &&OPCODE_CALL, \
- &&OPCODE_CALL_RETURN, \
- &&OPCODE_CALL_ASYNC, \
- &&OPCODE_CALL_BUILT_IN, \
- &&OPCODE_CALL_SELF, \
- &&OPCODE_CALL_SELF_BASE, \
- &&OPCODE_AWAIT, \
- &&OPCODE_AWAIT_RESUME, \
- &&OPCODE_JUMP, \
- &&OPCODE_JUMP_IF, \
- &&OPCODE_JUMP_IF_NOT, \
- &&OPCODE_JUMP_TO_DEF_ARGUMENT, \
- &&OPCODE_RETURN, \
- &&OPCODE_ITERATE_BEGIN, \
- &&OPCODE_ITERATE, \
- &&OPCODE_ASSERT, \
- &&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:
-#define OPCODE_WHILE(m_test)
-#define OPCODES_END \
- OPSEXIT:
-#define OPCODES_OUT \
- OPSOUT:
-#define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]]
-#define OPCODE_SWITCH(m_test) DISPATCH_OPCODE;
-#define OPCODE_BREAK goto OPSEXIT
-#define OPCODE_OUT goto OPSOUT
-#else
-#define OPCODES_TABLE
-#define OPCODE(m_op) case m_op:
-#define OPCODE_WHILE(m_test) while (m_test)
-#define OPCODES_END
-#define OPCODES_OUT
-#define DISPATCH_OPCODE continue
-#define OPCODE_SWITCH(m_test) switch (m_test)
-#define OPCODE_BREAK break
-#define OPCODE_OUT break
-#endif
-
-Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) {
- OPCODES_TABLE;
-
- if (!_code_ptr) {
- return Variant();
- }
-
- r_err.error = Callable::CallError::CALL_OK;
-
- Variant self;
- Variant static_ref;
- Variant retvalue;
- Variant *stack = nullptr;
- Variant **call_args;
- int defarg = 0;
-
-#ifdef DEBUG_ENABLED
-
- //GDScriptLanguage::get_singleton()->calls++;
-
-#endif
-
- uint32_t alloca_size = 0;
- GDScript *script;
- int ip = 0;
- int line = _initial_line;
-
- if (p_state) {
- //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;
- ip = p_state->ip;
- alloca_size = p_state->stack.size();
- script = p_state->script;
- p_instance = p_state->instance;
- defarg = p_state->defarg;
- self = p_state->self;
-
- } else {
- if (p_argcount != _argument_count) {
- if (p_argcount > _argument_count) {
- r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_err.argument = _argument_count;
-
- return Variant();
- } else if (p_argcount < _argument_count - _default_arg_count) {
- r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_err.argument = _argument_count - _default_arg_count;
- return Variant();
- } else {
- defarg = _argument_count - p_argcount;
- }
- }
-
- alloca_size = sizeof(Variant *) * _call_size + sizeof(Variant) * _stack_size;
-
- if (alloca_size) {
- uint8_t *aptr = (uint8_t *)alloca(alloca_size);
-
- if (_stack_size) {
- stack = (Variant *)aptr;
- for (int i = 0; i < p_argcount; i++) {
- if (!argument_types[i].has_type) {
- memnew_placement(&stack[i], Variant(*p_args[i]));
- continue;
- }
-
- if (!argument_types[i].is_type(*p_args[i], true)) {
- r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_err.argument = i;
- r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT;
- return Variant();
- }
- if (argument_types[i].kind == GDScriptDataType::BUILTIN) {
- Variant arg = Variant::construct(argument_types[i].builtin_type, &p_args[i], 1, r_err);
- memnew_placement(&stack[i], Variant(arg));
- } else {
- memnew_placement(&stack[i], Variant(*p_args[i]));
- }
- }
- for (int i = p_argcount; i < _stack_size; i++) {
- memnew_placement(&stack[i], Variant);
- }
- } else {
- stack = nullptr;
- }
-
- if (_call_size) {
- call_args = (Variant **)&aptr[sizeof(Variant) * _stack_size];
- } else {
- call_args = nullptr;
- }
-
- } else {
- stack = nullptr;
- call_args = nullptr;
- }
-
- if (p_instance) {
- if (p_instance->base_ref && static_cast<Reference *>(p_instance->owner)->is_referenced()) {
- self = REF(static_cast<Reference *>(p_instance->owner));
- } else {
- self = p_instance->owner;
- }
- script = p_instance->script.ptr();
- } else {
- script = _script;
- }
- }
-
- static_ref = script;
-
- String err_text;
-
-#ifdef DEBUG_ENABLED
-
- if (EngineDebugger::is_active()) {
- GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line);
- }
-
-#define GD_ERR_BREAK(m_cond) \
- { \
- if (unlikely(m_cond)) { \
- _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition ' " _STR(m_cond) " ' is true. Breaking..:"); \
- OPCODE_BREAK; \
- } \
- }
-
-#define CHECK_SPACE(m_space) \
- GD_ERR_BREAK((ip + m_space) > _code_size)
-
-#define GET_VARIANT_PTR(m_v, m_code_ofs) \
- Variant *m_v; \
- m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); \
- if (unlikely(!m_v)) \
- OPCODE_BREAK;
-
-#else
-#define GD_ERR_BREAK(m_cond)
-#define CHECK_SPACE(m_space)
-#define GET_VARIANT_PTR(m_v, m_code_ofs) \
- Variant *m_v; \
- m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text);
-
-#endif
-
-#ifdef DEBUG_ENABLED
-
- uint64_t function_start_time = 0;
- uint64_t function_call_time = 0;
-
- if (GDScriptLanguage::get_singleton()->profiling) {
- function_start_time = OS::get_singleton()->get_ticks_usec();
- function_call_time = 0;
- profile.call_count++;
- profile.frame_call_count++;
- }
- bool exit_ok = false;
- bool awaited = false;
-#endif
-
-#ifdef DEBUG_ENABLED
- OPCODE_WHILE(ip < _code_size) {
- int last_opcode = _code_ptr[ip];
-#else
- OPCODE_WHILE(true) {
-#endif
-
- OPCODE_SWITCH(_code_ptr[ip]) {
- OPCODE(OPCODE_OPERATOR) {
- CHECK_SPACE(5);
-
- bool valid;
- Variant::Operator op = (Variant::Operator)_code_ptr[ip + 1];
- GD_ERR_BREAK(op >= Variant::OP_MAX);
-
- GET_VARIANT_PTR(a, 2);
- GET_VARIANT_PTR(b, 3);
- GET_VARIANT_PTR(dst, 4);
-
-#ifdef DEBUG_ENABLED
-
- Variant ret;
- Variant::evaluate(op, *a, *b, ret, valid);
-#else
- Variant::evaluate(op, *a, *b, *dst, valid);
-#endif
-#ifdef DEBUG_ENABLED
- if (!valid) {
- if (ret.get_type() == Variant::STRING) {
- //return a string when invalid with the error
- err_text = ret;
- err_text += " in operator '" + Variant::get_operator_name(op) + "'.";
- } else {
- err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'.";
- }
- OPCODE_BREAK;
- }
- *dst = ret;
-#endif
- ip += 5;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_EXTENDS_TEST) {
- CHECK_SPACE(4);
-
- GET_VARIANT_PTR(a, 1);
- GET_VARIANT_PTR(b, 2);
- GET_VARIANT_PTR(dst, 3);
-
-#ifdef DEBUG_ENABLED
- if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) {
- err_text = "Right operand of 'is' is not a class.";
- OPCODE_BREAK;
- }
-#endif
-
- bool extends_ok = false;
- if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) {
-#ifdef DEBUG_ENABLED
- bool was_freed;
- Object *obj_A = a->get_validated_object_with_check(was_freed);
-
- if (was_freed) {
- err_text = "Left operand of 'is' is a previously freed instance.";
- OPCODE_BREAK;
- }
-
- Object *obj_B = b->get_validated_object_with_check(was_freed);
-
- if (was_freed) {
- err_text = "Right operand of 'is' is a previously freed instance.";
- OPCODE_BREAK;
- }
-#else
-
- Object *obj_A = *a;
- Object *obj_B = *b;
-#endif // DEBUG_ENABLED
-
- GDScript *scr_B = Object::cast_to<GDScript>(obj_B);
-
- if (scr_B) {
- //if B is a script, the only valid condition is that A has an instance which inherits from the script
- //in other situation, this shoul return false.
-
- if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) {
- GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr());
- //bool found=false;
- while (cmp) {
- if (cmp == scr_B) {
- //inherits from script, all ok
- extends_ok = true;
- break;
- }
-
- cmp = cmp->_base;
- }
- }
-
- } else {
- GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B);
-
-#ifdef DEBUG_ENABLED
- if (!nc) {
- err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "').";
- OPCODE_BREAK;
- }
-#endif
- extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name());
- }
- }
-
- *dst = extends_ok;
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_IS_BUILTIN) {
- CHECK_SPACE(4);
-
- GET_VARIANT_PTR(value, 1);
- Variant::Type var_type = (Variant::Type)_code_ptr[ip + 2];
- GET_VARIANT_PTR(dst, 3);
-
- GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX);
-
- *dst = value->get_type() == var_type;
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_SET) {
- CHECK_SPACE(3);
-
- GET_VARIANT_PTR(dst, 1);
- GET_VARIANT_PTR(index, 2);
- GET_VARIANT_PTR(value, 3);
-
- bool valid;
- dst->set(*index, *value, &valid);
-
-#ifdef DEBUG_ENABLED
- if (!valid) {
- String v = index->operator String();
- if (v != "") {
- v = "'" + v + "'";
- } else {
- v = "of type '" + _get_var_type(index) + "'";
- }
- err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'";
- OPCODE_BREAK;
- }
-#endif
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_GET) {
- CHECK_SPACE(3);
-
- GET_VARIANT_PTR(src, 1);
- GET_VARIANT_PTR(index, 2);
- GET_VARIANT_PTR(dst, 3);
-
- bool valid;
-#ifdef DEBUG_ENABLED
- //allow better error message in cases where src and dst are the same stack position
- Variant ret = src->get(*index, &valid);
-#else
- *dst = src->get(*index, &valid);
-
-#endif
-#ifdef DEBUG_ENABLED
- if (!valid) {
- String v = index->operator String();
- if (v != "") {
- v = "'" + v + "'";
- } else {
- v = "of type '" + _get_var_type(index) + "'";
- }
- err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "').";
- OPCODE_BREAK;
- }
- *dst = ret;
-#endif
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_SET_NAMED) {
- CHECK_SPACE(3);
-
- GET_VARIANT_PTR(dst, 1);
- GET_VARIANT_PTR(value, 3);
-
- int indexname = _code_ptr[ip + 2];
-
- GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
- const StringName *index = &_global_names_ptr[indexname];
-
- bool valid;
- dst->set_named(*index, *value, &valid);
-
-#ifdef DEBUG_ENABLED
- if (!valid) {
- String err_type;
- err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'.";
- OPCODE_BREAK;
- }
-#endif
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_GET_NAMED) {
- CHECK_SPACE(4);
-
- GET_VARIANT_PTR(src, 1);
- GET_VARIANT_PTR(dst, 3);
-
- int indexname = _code_ptr[ip + 2];
-
- GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
- const StringName *index = &_global_names_ptr[indexname];
-
- bool valid;
-#ifdef DEBUG_ENABLED
- //allow better error message in cases where src and dst are the same stack position
- Variant ret = src->get_named(*index, &valid);
-
-#else
- *dst = src->get_named(*index, &valid);
-#endif
-#ifdef DEBUG_ENABLED
- if (!valid) {
- if (src->has_method(*index)) {
- err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "'). Did you mean '." + index->operator String() + "()' or funcref(obj, \"" + index->operator String() + "\") ?";
- } else {
- err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "').";
- }
- OPCODE_BREAK;
- }
- *dst = ret;
-#endif
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_SET_MEMBER) {
- CHECK_SPACE(3);
- int indexname = _code_ptr[ip + 1];
- GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
- const StringName *index = &_global_names_ptr[indexname];
- GET_VARIANT_PTR(src, 2);
-
- bool valid;
-#ifndef DEBUG_ENABLED
- ClassDB::set_property(p_instance->owner, *index, *src, &valid);
-#else
- bool ok = ClassDB::set_property(p_instance->owner, *index, *src, &valid);
- if (!ok) {
- err_text = "Internal error setting property: " + String(*index);
- OPCODE_BREAK;
- } else if (!valid) {
- err_text = "Error setting property '" + String(*index) + "' with value of type " + Variant::get_type_name(src->get_type()) + ".";
- OPCODE_BREAK;
- }
-#endif
- ip += 3;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_GET_MEMBER) {
- CHECK_SPACE(3);
- int indexname = _code_ptr[ip + 1];
- GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
- const StringName *index = &_global_names_ptr[indexname];
- GET_VARIANT_PTR(dst, 2);
-
-#ifndef DEBUG_ENABLED
- ClassDB::get_property(p_instance->owner, *index, *dst);
-#else
- bool ok = ClassDB::get_property(p_instance->owner, *index, *dst);
- if (!ok) {
- err_text = "Internal error getting property: " + String(*index);
- OPCODE_BREAK;
- }
-#endif
- ip += 3;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_ASSIGN) {
- CHECK_SPACE(3);
- GET_VARIANT_PTR(dst, 1);
- GET_VARIANT_PTR(src, 2);
-
- *dst = *src;
-
- ip += 3;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_ASSIGN_TRUE) {
- CHECK_SPACE(2);
- GET_VARIANT_PTR(dst, 1);
-
- *dst = true;
-
- ip += 2;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_ASSIGN_FALSE) {
- CHECK_SPACE(2);
- GET_VARIANT_PTR(dst, 1);
-
- *dst = false;
-
- ip += 2;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) {
- CHECK_SPACE(4);
- GET_VARIANT_PTR(dst, 2);
- GET_VARIANT_PTR(src, 3);
-
- Variant::Type var_type = (Variant::Type)_code_ptr[ip + 1];
- GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX);
-
- if (src->get_type() != var_type) {
-#ifdef DEBUG_ENABLED
- if (Variant::can_convert_strict(src->get_type(), var_type)) {
-#endif // DEBUG_ENABLED
- Callable::CallError ce;
- *dst = Variant::construct(var_type, const_cast<const Variant **>(&src), 1, ce);
- } else {
-#ifdef DEBUG_ENABLED
- err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
- "' to a variable of type '" + Variant::get_type_name(var_type) + "'.";
- OPCODE_BREAK;
- }
- } else {
-#endif // DEBUG_ENABLED
- *dst = *src;
- }
-
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) {
- CHECK_SPACE(4);
- GET_VARIANT_PTR(dst, 2);
- GET_VARIANT_PTR(src, 3);
-
-#ifdef DEBUG_ENABLED
- GET_VARIANT_PTR(type, 1);
- GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *());
- GD_ERR_BREAK(!nc);
- if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
- err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
- "' to a variable of type '" + nc->get_name() + "'.";
- OPCODE_BREAK;
- }
- Object *src_obj = src->operator Object *();
-
- if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
- err_text = "Trying to assign value of type '" + src_obj->get_class_name() +
- "' to a variable of type '" + nc->get_name() + "'.";
- OPCODE_BREAK;
- }
-#endif // DEBUG_ENABLED
- *dst = *src;
-
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) {
- CHECK_SPACE(4);
- GET_VARIANT_PTR(dst, 2);
- GET_VARIANT_PTR(src, 3);
-
-#ifdef DEBUG_ENABLED
- GET_VARIANT_PTR(type, 1);
- Script *base_type = Object::cast_to<Script>(type->operator Object *());
-
- GD_ERR_BREAK(!base_type);
-
- if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
- err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'.";
- OPCODE_BREAK;
- }
-
- if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) {
- ScriptInstance *scr_inst = src->operator Object *()->get_script_instance();
- if (!scr_inst) {
- err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() +
- "' to a variable of type '" + base_type->get_path().get_file() + "'.";
- OPCODE_BREAK;
- }
-
- Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr();
- bool valid = false;
-
- while (src_type) {
- if (src_type == base_type) {
- valid = true;
- break;
- }
- src_type = src_type->get_base_script().ptr();
- }
-
- if (!valid) {
- err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() +
- "' to a variable of type '" + base_type->get_path().get_file() + "'.";
- OPCODE_BREAK;
- }
- }
-#endif // DEBUG_ENABLED
-
- *dst = *src;
-
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_CAST_TO_BUILTIN) {
- CHECK_SPACE(4);
- Variant::Type to_type = (Variant::Type)_code_ptr[ip + 1];
- GET_VARIANT_PTR(src, 2);
- GET_VARIANT_PTR(dst, 3);
-
- GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX);
-
- Callable::CallError err;
- *dst = Variant::construct(to_type, (const Variant **)&src, 1, err);
-
-#ifdef DEBUG_ENABLED
- if (err.error != Callable::CallError::CALL_OK) {
- err_text = "Invalid cast: could not convert value to '" + Variant::get_type_name(to_type) + "'.";
- OPCODE_BREAK;
- }
-#endif
-
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_CAST_TO_NATIVE) {
- CHECK_SPACE(4);
- GET_VARIANT_PTR(to_type, 1);
- GET_VARIANT_PTR(src, 2);
- GET_VARIANT_PTR(dst, 3);
-
- GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *());
- GD_ERR_BREAK(!nc);
-
-#ifdef DEBUG_ENABLED
- if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
- err_text = "Invalid cast: can't convert a non-object value to an object type.";
- OPCODE_BREAK;
- }
-#endif
- Object *src_obj = src->operator Object *();
-
- if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
- *dst = Variant(); // invalid cast, assign NULL
- } else {
- *dst = *src;
- }
-
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_CAST_TO_SCRIPT) {
- CHECK_SPACE(4);
- GET_VARIANT_PTR(to_type, 1);
- GET_VARIANT_PTR(src, 2);
- GET_VARIANT_PTR(dst, 3);
-
- Script *base_type = Object::cast_to<Script>(to_type->operator Object *());
-
- GD_ERR_BREAK(!base_type);
-
-#ifdef DEBUG_ENABLED
- if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
- err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'.";
- OPCODE_BREAK;
- }
-#endif
-
- bool valid = false;
-
- if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) {
- ScriptInstance *scr_inst = src->operator Object *()->get_script_instance();
-
- if (scr_inst) {
- Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr();
-
- while (src_type) {
- if (src_type == base_type) {
- valid = true;
- break;
- }
- src_type = src_type->get_base_script().ptr();
- }
- }
- }
-
- if (valid) {
- *dst = *src; // Valid cast, copy the source object
- } else {
- *dst = Variant(); // invalid cast, assign NULL
- }
-
- ip += 4;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_CONSTRUCT) {
- CHECK_SPACE(2);
- Variant::Type t = Variant::Type(_code_ptr[ip + 1]);
- int argc = _code_ptr[ip + 2];
- CHECK_SPACE(argc + 2);
- Variant **argptrs = call_args;
- for (int i = 0; i < argc; i++) {
- GET_VARIANT_PTR(v, 3 + i);
- argptrs[i] = v;
- }
-
- GET_VARIANT_PTR(dst, 3 + argc);
- Callable::CallError err;
- *dst = Variant::construct(t, (const Variant **)argptrs, argc, err);
-
-#ifdef DEBUG_ENABLED
- if (err.error != Callable::CallError::CALL_OK) {
- err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs);
- OPCODE_BREAK;
- }
-#endif
-
- ip += 4 + argc;
- //construct a basic type
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_CONSTRUCT_ARRAY) {
- CHECK_SPACE(1);
- int argc = _code_ptr[ip + 1];
- Array array; //arrays are always shared
- array.resize(argc);
- CHECK_SPACE(argc + 2);
-
- for (int i = 0; i < argc; i++) {
- GET_VARIANT_PTR(v, 2 + i);
- array[i] = *v;
- }
-
- GET_VARIANT_PTR(dst, 2 + argc);
-
- *dst = array;
-
- ip += 3 + argc;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_CONSTRUCT_DICTIONARY) {
- CHECK_SPACE(1);
- int argc = _code_ptr[ip + 1];
- Dictionary dict; //arrays are always shared
-
- CHECK_SPACE(argc * 2 + 2);
-
- for (int i = 0; i < argc; i++) {
- GET_VARIANT_PTR(k, 2 + i * 2 + 0);
- GET_VARIANT_PTR(v, 2 + i * 2 + 1);
- dict[*k] = *v;
- }
-
- GET_VARIANT_PTR(dst, 2 + argc * 2);
-
- *dst = dict;
-
- ip += 3 + argc * 2;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_CALL_ASYNC)
- OPCODE(OPCODE_CALL_RETURN)
- OPCODE(OPCODE_CALL) {
- CHECK_SPACE(4);
- 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);
- int nameg = _code_ptr[ip + 3];
-
- GD_ERR_BREAK(nameg < 0 || nameg >= _global_names_count);
- const StringName *methodname = &_global_names_ptr[nameg];
-
- GD_ERR_BREAK(argc < 0);
- ip += 4;
- CHECK_SPACE(argc + 1);
- Variant **argptrs = call_args;
-
- for (int i = 0; i < argc; i++) {
- GET_VARIANT_PTR(v, i);
- argptrs[i] = v;
- }
-
-#ifdef DEBUG_ENABLED
- uint64_t call_time = 0;
-
- if (GDScriptLanguage::get_singleton()->profiling) {
- call_time = OS::get_singleton()->get_ticks_usec();
- }
-
-#endif
- Callable::CallError err;
- 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);
- }
-#ifdef DEBUG_ENABLED
- if (GDScriptLanguage::get_singleton()->profiling) {
- function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
- }
-
- if (err.error != Callable::CallError::CALL_OK) {
- String methodstr = *methodname;
- String basestr = _get_var_type(base);
-
- if (methodstr == "call") {
- if (argc >= 1) {
- methodstr = String(*argptrs[0]) + " (via call)";
- if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
- err.argument += 1;
- }
- }
- } else if (methodstr == "free") {
- if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
- if (base->is_ref()) {
- err_text = "Attempted to free a reference.";
- OPCODE_BREAK;
- } else if (base->get_type() == Variant::OBJECT) {
- err_text = "Attempted to free a locked object (calling or emitting).";
- OPCODE_BREAK;
- }
- }
- }
- err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs);
- OPCODE_BREAK;
- }
-#endif
-
- //_call_func(nullptr,base,*methodname,ip,argc,p_instance,stack);
- ip += argc + 1;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_CALL_BUILT_IN) {
- CHECK_SPACE(4);
-
- GDScriptFunctions::Function func = GDScriptFunctions::Function(_code_ptr[ip + 1]);
- int argc = _code_ptr[ip + 2];
- GD_ERR_BREAK(argc < 0);
-
- ip += 3;
- CHECK_SPACE(argc + 1);
- Variant **argptrs = call_args;
-
- for (int i = 0; i < argc; i++) {
- GET_VARIANT_PTR(v, i);
- argptrs[i] = v;
- }
-
- GET_VARIANT_PTR(dst, argc);
-
- Callable::CallError err;
-
- GDScriptFunctions::call(func, (const Variant **)argptrs, argc, *dst, err);
-
-#ifdef DEBUG_ENABLED
- if (err.error != Callable::CallError::CALL_OK) {
- String methodstr = GDScriptFunctions::get_func_name(func);
- if (dst->get_type() == Variant::STRING) {
- //call provided error string
- err_text = "Error calling built-in function '" + methodstr + "': " + String(*dst);
- } else {
- err_text = _get_call_error(err, "built-in function '" + methodstr + "'", (const Variant **)argptrs);
- }
- OPCODE_BREAK;
- }
-#endif
- ip += argc + 1;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_CALL_SELF) {
- OPCODE_BREAK;
- }
-
- OPCODE(OPCODE_CALL_SELF_BASE) {
- CHECK_SPACE(2);
- int self_fun = _code_ptr[ip + 1];
-
-#ifdef DEBUG_ENABLED
- if (self_fun < 0 || self_fun >= _global_names_count) {
- err_text = "compiler bug, function name not found";
- OPCODE_BREAK;
- }
-#endif
- const StringName *methodname = &_global_names_ptr[self_fun];
-
- int argc = _code_ptr[ip + 2];
-
- CHECK_SPACE(2 + argc + 1);
-
- Variant **argptrs = call_args;
-
- for (int i = 0; i < argc; i++) {
- GET_VARIANT_PTR(v, i + 3);
- argptrs[i] = v;
- }
-
- GET_VARIANT_PTR(dst, argc + 3);
-
- const GDScript *gds = _script;
-
- const Map<StringName, GDScriptFunction *>::Element *E = nullptr;
- while (gds->base.ptr()) {
- gds = gds->base.ptr();
- E = gds->member_functions.find(*methodname);
- if (E) {
- break;
- }
- }
-
- Callable::CallError err;
-
- if (E) {
- *dst = E->get()->call(p_instance, (const Variant **)argptrs, argc, err);
- } else if (gds->native.ptr()) {
- if (*methodname != GDScriptLanguage::get_singleton()->strings._init) {
- MethodBind *mb = ClassDB::get_method(gds->native->get_name(), *methodname);
- if (!mb) {
- err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- } else {
- *dst = mb->call(p_instance->owner, (const Variant **)argptrs, argc, err);
- }
- } else {
- err.error = Callable::CallError::CALL_OK;
- }
- } else {
- if (*methodname != GDScriptLanguage::get_singleton()->strings._init) {
- err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- } else {
- err.error = Callable::CallError::CALL_OK;
- }
- }
-
- if (err.error != Callable::CallError::CALL_OK) {
- String methodstr = *methodname;
- err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs);
-
- OPCODE_BREAK;
- }
-
- ip += 4 + argc;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_AWAIT) {
- int ipofs = 2;
- CHECK_SPACE(3);
-
- //do the oneshot connect
- GET_VARIANT_PTR(argobj, 1);
-
- Signal sig;
- bool is_signal = true;
-
- {
- Variant result = *argobj;
-
- if (argobj->get_type() == Variant::OBJECT) {
- bool was_freed = false;
- Object *obj = argobj->get_validated_object_with_check(was_freed);
-
- if (was_freed) {
- err_text = "Trying to await on a freed object.";
- 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);
- }
- }
- }
-
- 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 (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]));
- }
- 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;
-
- 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: " + sig.get_name() + " during await.";
- OPCODE_BREAK;
- }
-
-#ifdef DEBUG_ENABLED
- exit_ok = true;
- awaited = true;
-#endif
- OPCODE_BREAK;
- }
- }
- DISPATCH_OPCODE; // Needed for synchronous calls (when result is immediately available).
-
- OPCODE(OPCODE_AWAIT_RESUME) {
- CHECK_SPACE(2);
-#ifdef DEBUG_ENABLED
- if (!p_state) {
- err_text = ("Invalid Resume (bug?)");
- OPCODE_BREAK;
- }
-#endif
- GET_VARIANT_PTR(result, 1);
- *result = p_state->result;
- ip += 2;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_JUMP) {
- CHECK_SPACE(2);
- int to = _code_ptr[ip + 1];
-
- GD_ERR_BREAK(to < 0 || to > _code_size);
- ip = to;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_JUMP_IF) {
- CHECK_SPACE(3);
-
- GET_VARIANT_PTR(test, 1);
-
- bool result = test->booleanize();
-
- if (result) {
- int to = _code_ptr[ip + 2];
- GD_ERR_BREAK(to < 0 || to > _code_size);
- ip = to;
- } else {
- ip += 3;
- }
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_JUMP_IF_NOT) {
- CHECK_SPACE(3);
-
- GET_VARIANT_PTR(test, 1);
-
- bool result = test->booleanize();
-
- if (!result) {
- int to = _code_ptr[ip + 2];
- GD_ERR_BREAK(to < 0 || to > _code_size);
- ip = to;
- } else {
- ip += 3;
- }
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_JUMP_TO_DEF_ARGUMENT) {
- CHECK_SPACE(2);
- ip = _default_arg_ptr[defarg];
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_RETURN) {
- CHECK_SPACE(2);
- GET_VARIANT_PTR(r, 1);
- retvalue = *r;
-#ifdef DEBUG_ENABLED
- exit_ok = true;
-#endif
- OPCODE_BREAK;
- }
-
- OPCODE(OPCODE_ITERATE_BEGIN) {
- CHECK_SPACE(8); //space for this a regular iterate
-
- GET_VARIANT_PTR(counter, 1);
- GET_VARIANT_PTR(container, 2);
-
- bool valid;
- if (!container->iter_init(*counter, valid)) {
-#ifdef DEBUG_ENABLED
- if (!valid) {
- err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "'.";
- OPCODE_BREAK;
- }
-#endif
- int jumpto = _code_ptr[ip + 3];
- GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
- ip = jumpto;
- } else {
- GET_VARIANT_PTR(iterator, 4);
-
- *iterator = container->iter_get(*counter, valid);
-#ifdef DEBUG_ENABLED
- if (!valid) {
- err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "'.";
- OPCODE_BREAK;
- }
-#endif
- ip += 5; //skip regular iterate which is always next
- }
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_ITERATE) {
- CHECK_SPACE(4);
-
- GET_VARIANT_PTR(counter, 1);
- GET_VARIANT_PTR(container, 2);
-
- bool valid;
- if (!container->iter_next(*counter, valid)) {
-#ifdef DEBUG_ENABLED
- if (!valid) {
- err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?).";
- OPCODE_BREAK;
- }
-#endif
- int jumpto = _code_ptr[ip + 3];
- GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
- ip = jumpto;
- } else {
- GET_VARIANT_PTR(iterator, 4);
-
- *iterator = container->iter_get(*counter, valid);
-#ifdef DEBUG_ENABLED
- if (!valid) {
- err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?).";
- OPCODE_BREAK;
- }
-#endif
- ip += 5; //loop again
- }
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_ASSERT) {
- CHECK_SPACE(3);
-
-#ifdef DEBUG_ENABLED
- GET_VARIANT_PTR(test, 1);
- bool result = test->booleanize();
-
- if (!result) {
- String message_str;
- if (_code_ptr[ip + 2] != 0) {
- GET_VARIANT_PTR(message, 2);
- message_str = *message;
- }
- if (message_str.empty()) {
- err_text = "Assertion failed.";
- } else {
- err_text = "Assertion failed: " + message_str;
- }
- OPCODE_BREAK;
- }
-
-#endif
- ip += 3;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_BREAKPOINT) {
-#ifdef DEBUG_ENABLED
- if (EngineDebugger::is_active()) {
- GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement", true);
- }
-#endif
- ip += 1;
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_LINE) {
- CHECK_SPACE(2);
-
- line = _code_ptr[ip + 1];
- ip += 2;
-
- if (EngineDebugger::is_active()) {
- // line
- bool do_break = false;
-
- if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) {
- if (EngineDebugger::get_script_debugger()->get_depth() <= 0) {
- EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1);
- }
- if (EngineDebugger::get_script_debugger()->get_lines_left() <= 0) {
- do_break = true;
- }
- }
-
- if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source)) {
- do_break = true;
- }
-
- if (do_break) {
- GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true);
- }
-
- EngineDebugger::get_singleton()->line_poll();
- }
- }
- DISPATCH_OPCODE;
-
- OPCODE(OPCODE_END) {
-#ifdef DEBUG_ENABLED
- exit_ok = true;
-#endif
- OPCODE_BREAK;
- }
-
-#if 0 // Enable for debugging.
- default: {
- err_text = "Illegal opcode " + itos(_code_ptr[ip]) + " at address " + itos(ip);
- OPCODE_BREAK;
- }
-#endif
- }
-
- OPCODES_END
-#ifdef DEBUG_ENABLED
- if (exit_ok) {
- OPCODE_OUT;
- }
- //error
- // function, file, line, error, explanation
- String err_file;
- if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->path != "") {
- err_file = p_instance->script->path;
- } else if (script) {
- err_file = script->path;
- }
- if (err_file == "") {
- err_file = "<built-in>";
- }
- String err_func = name;
- if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->name != "") {
- err_func = p_instance->script->name + "." + err_func;
- }
- int err_line = line;
- if (err_text == "") {
- err_text = "Internal Script Error! - opcode #" + itos(last_opcode) + " (report please).";
- }
-
- if (!GDScriptLanguage::get_singleton()->debug_break(err_text, false)) {
- // debugger break did not happen
-
- _err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text.utf8().get_data(), ERR_HANDLER_SCRIPT);
- }
-
-#endif
- OPCODE_OUT;
- }
-
- OPCODES_OUT
-#ifdef DEBUG_ENABLED
- if (GDScriptLanguage::get_singleton()->profiling) {
- uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time;
- profile.total_time += time_taken;
- profile.self_time += time_taken - function_call_time;
- profile.frame_total_time += time_taken;
- profile.frame_self_time += time_taken - function_call_time;
- GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
- }
-
- // 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 || awaited) {
- if (EngineDebugger::is_active()) {
- GDScriptLanguage::get_singleton()->exit_function();
- }
-#endif
-
- if (_stack_size) {
- //free stack
- for (int i = 0; i < _stack_size; i++) {
- stack[i].~Variant();
- }
- }
-
-#ifdef DEBUG_ENABLED
- }
-#endif
-
- return retvalue;
-}
const int *GDScriptFunction::get_code() const {
return _code_ptr;
@@ -1699,31 +139,13 @@ void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<String
}
}
-GDScriptFunction::GDScriptFunction() :
- function_list(this) {
- _stack_size = 0;
- _call_size = 0;
- rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+GDScriptFunction::GDScriptFunction() {
name = "<anonymous>";
#ifdef DEBUG_ENABLED
- _func_cname = nullptr;
-
{
MutexLock lock(GDScriptLanguage::get_singleton()->lock);
-
GDScriptLanguage::get_singleton()->function_list.add(&function_list);
}
-
- profile.call_count = 0;
- profile.self_time = 0;
- profile.total_time = 0;
- profile.frame_call_count = 0;
- profile.frame_self_time = 0;
- profile.frame_total_time = 0;
- profile.last_frame_call_count = 0;
- profile.last_frame_self_time = 0;
- profile.last_frame_total_time = 0;
-
#endif
}
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 771baf6a08..7bc20672d5 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -31,13 +31,13 @@
#ifndef GDSCRIPT_FUNCTION_H
#define GDSCRIPT_FUNCTION_H
+#include "core/object/reference.h"
+#include "core/object/script_language.h"
#include "core/os/thread.h"
-#include "core/pair.h"
-#include "core/reference.h"
-#include "core/script_language.h"
-#include "core/self_list.h"
-#include "core/string_name.h"
-#include "core/variant.h"
+#include "core/string/string_name.h"
+#include "core/templates/pair.h"
+#include "core/templates/self_list.h"
+#include "core/variant/variant.h"
class GDScriptInstance;
class GDScript;
@@ -56,7 +56,8 @@ struct GDScriptDataType {
bool has_type = false;
Variant::Type builtin_type = Variant::NIL;
StringName native_type;
- Ref<Script> script_type;
+ Script *script_type = nullptr;
+ Ref<Script> script_type_ref;
bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const {
if (!has_type) {
@@ -158,12 +159,19 @@ class GDScriptFunction {
public:
enum Opcode {
OPCODE_OPERATOR,
+ OPCODE_OPERATOR_VALIDATED,
OPCODE_EXTENDS_TEST,
OPCODE_IS_BUILTIN,
- OPCODE_SET,
- OPCODE_GET,
+ OPCODE_SET_KEYED,
+ OPCODE_SET_KEYED_VALIDATED,
+ OPCODE_SET_INDEXED_VALIDATED,
+ OPCODE_GET_KEYED,
+ OPCODE_GET_KEYED_VALIDATED,
+ OPCODE_GET_INDEXED_VALIDATED,
OPCODE_SET_NAMED,
+ OPCODE_SET_NAMED_VALIDATED,
OPCODE_GET_NAMED,
+ OPCODE_GET_NAMED_VALIDATED,
OPCODE_SET_MEMBER,
OPCODE_GET_MEMBER,
OPCODE_ASSIGN,
@@ -175,15 +183,54 @@ public:
OPCODE_CAST_TO_BUILTIN,
OPCODE_CAST_TO_NATIVE,
OPCODE_CAST_TO_SCRIPT,
- OPCODE_CONSTRUCT, //only for basic types!!
+ OPCODE_CONSTRUCT, // Only for basic types!
+ OPCODE_CONSTRUCT_VALIDATED, // Only for basic types!
OPCODE_CONSTRUCT_ARRAY,
OPCODE_CONSTRUCT_DICTIONARY,
OPCODE_CALL,
OPCODE_CALL_RETURN,
OPCODE_CALL_ASYNC,
OPCODE_CALL_BUILT_IN,
- OPCODE_CALL_SELF,
+ OPCODE_CALL_BUILTIN_TYPE_VALIDATED,
OPCODE_CALL_SELF_BASE,
+ OPCODE_CALL_METHOD_BIND,
+ OPCODE_CALL_METHOD_BIND_RET,
+ // ptrcall have one instruction per return type.
+ OPCODE_CALL_PTRCALL_NO_RETURN,
+ OPCODE_CALL_PTRCALL_BOOL,
+ OPCODE_CALL_PTRCALL_INT,
+ OPCODE_CALL_PTRCALL_FLOAT,
+ OPCODE_CALL_PTRCALL_STRING,
+ OPCODE_CALL_PTRCALL_VECTOR2,
+ OPCODE_CALL_PTRCALL_VECTOR2I,
+ OPCODE_CALL_PTRCALL_RECT2,
+ OPCODE_CALL_PTRCALL_RECT2I,
+ OPCODE_CALL_PTRCALL_VECTOR3,
+ OPCODE_CALL_PTRCALL_VECTOR3I,
+ OPCODE_CALL_PTRCALL_TRANSFORM2D,
+ OPCODE_CALL_PTRCALL_PLANE,
+ OPCODE_CALL_PTRCALL_QUAT,
+ OPCODE_CALL_PTRCALL_AABB,
+ OPCODE_CALL_PTRCALL_BASIS,
+ OPCODE_CALL_PTRCALL_TRANSFORM,
+ OPCODE_CALL_PTRCALL_COLOR,
+ OPCODE_CALL_PTRCALL_STRING_NAME,
+ OPCODE_CALL_PTRCALL_NODE_PATH,
+ OPCODE_CALL_PTRCALL_RID,
+ OPCODE_CALL_PTRCALL_OBJECT,
+ OPCODE_CALL_PTRCALL_CALLABLE,
+ OPCODE_CALL_PTRCALL_SIGNAL,
+ OPCODE_CALL_PTRCALL_DICTIONARY,
+ OPCODE_CALL_PTRCALL_ARRAY,
+ OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY,
+ OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY,
+ OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY,
+ OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY,
+ OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY,
+ OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY,
+ OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY,
+ OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY,
+ OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY,
OPCODE_AWAIT,
OPCODE_AWAIT_RESUME,
OPCODE_JUMP,
@@ -192,7 +239,45 @@ public:
OPCODE_JUMP_TO_DEF_ARGUMENT,
OPCODE_RETURN,
OPCODE_ITERATE_BEGIN,
+ OPCODE_ITERATE_BEGIN_INT,
+ OPCODE_ITERATE_BEGIN_FLOAT,
+ OPCODE_ITERATE_BEGIN_VECTOR2,
+ OPCODE_ITERATE_BEGIN_VECTOR2I,
+ OPCODE_ITERATE_BEGIN_VECTOR3,
+ OPCODE_ITERATE_BEGIN_VECTOR3I,
+ OPCODE_ITERATE_BEGIN_STRING,
+ OPCODE_ITERATE_BEGIN_DICTIONARY,
+ OPCODE_ITERATE_BEGIN_ARRAY,
+ OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY,
+ OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY,
+ OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY,
+ OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY,
+ OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY,
+ OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY,
+ OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY,
+ OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY,
+ OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY,
+ OPCODE_ITERATE_BEGIN_OBJECT,
OPCODE_ITERATE,
+ OPCODE_ITERATE_INT,
+ OPCODE_ITERATE_FLOAT,
+ OPCODE_ITERATE_VECTOR2,
+ OPCODE_ITERATE_VECTOR2I,
+ OPCODE_ITERATE_VECTOR3,
+ OPCODE_ITERATE_VECTOR3I,
+ OPCODE_ITERATE_STRING,
+ OPCODE_ITERATE_DICTIONARY,
+ OPCODE_ITERATE_ARRAY,
+ OPCODE_ITERATE_PACKED_BYTE_ARRAY,
+ OPCODE_ITERATE_PACKED_INT32_ARRAY,
+ OPCODE_ITERATE_PACKED_INT64_ARRAY,
+ OPCODE_ITERATE_PACKED_FLOAT32_ARRAY,
+ OPCODE_ITERATE_PACKED_FLOAT64_ARRAY,
+ OPCODE_ITERATE_PACKED_STRING_ARRAY,
+ OPCODE_ITERATE_PACKED_VECTOR2_ARRAY,
+ OPCODE_ITERATE_PACKED_VECTOR3_ARRAY,
+ OPCODE_ITERATE_PACKED_COLOR_ARRAY,
+ OPCODE_ITERATE_OBJECT,
OPCODE_ASSERT,
OPCODE_BREAKPOINT,
OPCODE_LINE,
@@ -215,6 +300,12 @@ public:
ADDR_TYPE_NIL = 9
};
+ enum Instruction {
+ INSTR_BITS = 20,
+ INSTR_MASK = ((1 << INSTR_BITS) - 1),
+ INSTR_ARGS_MASK = ~INSTR_MASK,
+ };
+
struct StackDebug {
int line;
int pos;
@@ -224,44 +315,71 @@ public:
private:
friend class GDScriptCompiler;
+ friend class GDScriptByteCodeGenerator;
StringName source;
mutable Variant nil;
- mutable Variant *_constants_ptr;
- int _constant_count;
- const StringName *_global_names_ptr;
- int _global_names_count;
-#ifdef TOOLS_ENABLED
- const StringName *_named_globals_ptr;
- int _named_globals_count;
-#endif
- const int *_default_arg_ptr;
- int _default_arg_count;
- const int *_code_ptr;
- int _code_size;
- int _argument_count;
- int _stack_size;
- int _call_size;
- int _initial_line;
- bool _static;
- MultiplayerAPI::RPCMode rpc_mode;
-
- GDScript *_script;
+ mutable Variant *_constants_ptr = nullptr;
+ int _constant_count = 0;
+ const StringName *_global_names_ptr = nullptr;
+ int _global_names_count = 0;
+ const int *_default_arg_ptr = nullptr;
+ int _default_arg_count = 0;
+ int _operator_funcs_count = 0;
+ const Variant::ValidatedOperatorEvaluator *_operator_funcs_ptr = nullptr;
+ int _setters_count = 0;
+ const Variant::ValidatedSetter *_setters_ptr = nullptr;
+ int _getters_count = 0;
+ const Variant::ValidatedGetter *_getters_ptr = nullptr;
+ int _keyed_setters_count = 0;
+ const Variant::ValidatedKeyedSetter *_keyed_setters_ptr = nullptr;
+ int _keyed_getters_count = 0;
+ const Variant::ValidatedKeyedGetter *_keyed_getters_ptr = nullptr;
+ int _indexed_setters_count = 0;
+ const Variant::ValidatedIndexedSetter *_indexed_setters_ptr = nullptr;
+ int _indexed_getters_count = 0;
+ const Variant::ValidatedIndexedGetter *_indexed_getters_ptr = nullptr;
+ int _builtin_methods_count = 0;
+ const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr;
+ int _constructors_count = 0;
+ const Variant::ValidatedConstructor *_constructors_ptr = nullptr;
+ int _methods_count = 0;
+ MethodBind **_methods_ptr = nullptr;
+ const int *_code_ptr = nullptr;
+ int _code_size = 0;
+ int _argument_count = 0;
+ int _stack_size = 0;
+ int _instruction_args_size = 0;
+ int _ptrcall_args_size = 0;
+
+ int _initial_line = 0;
+ bool _static = false;
+ MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+
+ GDScript *_script = nullptr;
StringName name;
Vector<Variant> constants;
Vector<StringName> global_names;
-#ifdef TOOLS_ENABLED
- Vector<StringName> named_globals;
-#endif
Vector<int> default_arguments;
+ Vector<Variant::ValidatedOperatorEvaluator> operator_funcs;
+ Vector<Variant::ValidatedSetter> setters;
+ Vector<Variant::ValidatedGetter> getters;
+ Vector<Variant::ValidatedKeyedSetter> keyed_setters;
+ Vector<Variant::ValidatedKeyedGetter> keyed_getters;
+ Vector<Variant::ValidatedIndexedSetter> indexed_setters;
+ Vector<Variant::ValidatedIndexedGetter> indexed_getters;
+ Vector<Variant::ValidatedBuiltInMethod> builtin_methods;
+ Vector<Variant::ValidatedConstructor> constructors;
+ Vector<MethodBind *> methods;
Vector<int> code;
Vector<GDScriptDataType> argument_types;
GDScriptDataType return_type;
#ifdef TOOLS_ENABLED
Vector<StringName> arg_names;
+ Vector<Variant> default_arg_values;
#endif
List<StackDebug> stack_debug;
@@ -271,22 +389,22 @@ private:
friend class GDScriptLanguage;
- SelfList<GDScriptFunction> function_list;
+ SelfList<GDScriptFunction> function_list{ this };
#ifdef DEBUG_ENABLED
CharString func_cname;
- const char *_func_cname;
+ const char *_func_cname = nullptr;
struct Profile {
StringName signature;
- uint64_t call_count;
- uint64_t self_time;
- uint64_t total_time;
- uint64_t frame_call_count;
- uint64_t frame_self_time;
- uint64_t frame_total_time;
- uint64_t last_frame_call_count;
- uint64_t last_frame_self_time;
- uint64_t last_frame_total_time;
+ uint64_t call_count = 0;
+ uint64_t self_time = 0;
+ uint64_t total_time = 0;
+ uint64_t frame_call_count = 0;
+ uint64_t frame_self_time = 0;
+ uint64_t frame_total_time = 0;
+ uint64_t last_frame_call_count = 0;
+ uint64_t last_frame_self_time = 0;
+ uint64_t last_frame_total_time = 0;
} profile;
#endif
@@ -341,9 +459,18 @@ public:
ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), Variant());
return default_arguments[p_idx];
}
+#ifdef TOOLS_ENABLED
+ const Vector<Variant> &get_default_arg_values() const {
+ return default_arg_values;
+ }
+#endif // TOOLS_ENABLED
Variant call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state = nullptr);
+#ifdef DEBUG_ENABLED
+ void disassemble(const Vector<String> &p_code_lines) const;
+#endif
+
_FORCE_INLINE_ MultiplayerAPI::RPCMode get_rpc_mode() const { return rpc_mode; }
GDScriptFunction();
~GDScriptFunction();
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index fefbf906f0..3a7c1a8676 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -30,14 +30,13 @@
#include "gdscript_functions.h"
-#include "core/class_db.h"
-#include "core/func_ref.h"
#include "core/io/json.h"
#include "core/io/marshalls.h"
#include "core/math/math_funcs.h"
+#include "core/object/class_db.h"
+#include "core/object/reference.h"
#include "core/os/os.h"
-#include "core/reference.h"
-#include "core/variant_parser.h"
+#include "core/variant/variant_parser.h"
#include "gdscript.h"
const char *GDScriptFunctions::get_func_name(Function p_func) {
@@ -83,7 +82,8 @@ const char *GDScriptFunctions::get_func_name(Function p_func) {
"randomize",
"randi",
"randf",
- "rand_range",
+ "randf_range",
+ "randi_range",
"seed",
"rand_seed",
"deg2rad",
@@ -99,7 +99,6 @@ const char *GDScriptFunctions::get_func_name(Function p_func) {
"clamp",
"nearest_po2",
"weakref",
- "funcref",
"convert",
"typeof",
"type_exists",
@@ -286,7 +285,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
int64_t i = *p_args[0];
r_ret = i < 0 ? -1 : (i > 0 ? +1 : 0);
} else if (p_args[0]->get_type() == Variant::FLOAT) {
- real_t r = *p_args[0];
+ double r = *p_args[0];
r_ret = r < 0.0 ? -1.0 : (r > 0.0 ? +1.0 : 0.0);
} else {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
@@ -419,7 +418,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
Math::randomize();
r_ret = Variant();
} break;
- case MATH_RAND: {
+ case MATH_RANDI: {
VALIDATE_ARG_COUNT(0);
r_ret = Math::rand();
} break;
@@ -427,12 +426,18 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
VALIDATE_ARG_COUNT(0);
r_ret = Math::randf();
} break;
- case MATH_RANDOM: {
+ case MATH_RANDF_RANGE: {
VALIDATE_ARG_COUNT(2);
VALIDATE_ARG_NUM(0);
VALIDATE_ARG_NUM(1);
r_ret = Math::random((double)*p_args[0], (double)*p_args[1]);
} break;
+ case MATH_RANDI_RANGE: {
+ VALIDATE_ARG_COUNT(2);
+ VALIDATE_ARG_NUM(0);
+ VALIDATE_ARG_NUM(1);
+ r_ret = Math::random((int)*p_args[0], (int)*p_args[1]);
+ } break;
case MATH_SEED: {
VALIDATE_ARG_COUNT(1);
VALIDATE_ARG_NUM(0);
@@ -505,8 +510,8 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
VALIDATE_ARG_NUM(0);
VALIDATE_ARG_NUM(1);
- real_t a = *p_args[0];
- real_t b = *p_args[1];
+ double a = *p_args[0];
+ double b = *p_args[1];
r_ret = MAX(a, b);
}
@@ -522,8 +527,8 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
VALIDATE_ARG_NUM(0);
VALIDATE_ARG_NUM(1);
- real_t a = *p_args[0];
- real_t b = *p_args[1];
+ double a = *p_args[0];
+ double b = *p_args[1];
r_ret = MIN(a, b);
}
@@ -540,9 +545,9 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
VALIDATE_ARG_NUM(1);
VALIDATE_ARG_NUM(2);
- real_t a = *p_args[0];
- real_t b = *p_args[1];
- real_t c = *p_args[2];
+ double a = *p_args[0];
+ double b = *p_args[1];
+ double c = *p_args[2];
r_ret = CLAMP(a, b, c);
}
@@ -582,31 +587,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
return;
}
} break;
- case FUNC_FUNCREF: {
- VALIDATE_ARG_COUNT(2);
- if (p_args[0]->get_type() != Variant::OBJECT) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- r_ret = Variant();
- return;
- }
- if (p_args[1]->get_type() != Variant::STRING && p_args[1]->get_type() != Variant::NODE_PATH) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::STRING;
- r_ret = Variant();
- return;
- }
-
- Ref<FuncRef> fr = memnew(FuncRef);
-
- fr->set_instance(*p_args[0]);
- fr->set_function(*p_args[1]);
-
- r_ret = fr;
-
- } break;
case TYPE_CONVERT: {
VALIDATE_ARG_COUNT(2);
VALIDATE_ARG_NUM(1);
@@ -619,7 +599,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
return;
} else {
- r_ret = Variant::construct(Variant::Type(type), p_args, 1, r_error);
+ Variant::construct(Variant::Type(type), r_ret, p_args, 1, r_error);
}
} break;
case TYPE_OF: {
@@ -635,7 +615,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
case TEXT_CHAR: {
VALIDATE_ARG_COUNT(1);
VALIDATE_ARG_NUM(0);
- CharType result[2] = { *p_args[0], 0 };
+ char32_t result[2] = { *p_args[0], 0 };
r_ret = String(result);
} break;
case TEXT_ORD: {
@@ -1655,7 +1635,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
mi.return_val.type = Variant::NIL;
return mi;
} break;
- case MATH_RAND: {
+ case MATH_RANDI: {
MethodInfo mi("randi");
mi.return_val.type = Variant::INT;
return mi;
@@ -1665,11 +1645,16 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
mi.return_val.type = Variant::FLOAT;
return mi;
} break;
- case MATH_RANDOM: {
- MethodInfo mi("rand_range", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"));
+ case MATH_RANDF_RANGE: {
+ MethodInfo mi("randf_range", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"));
mi.return_val.type = Variant::FLOAT;
return mi;
} break;
+ case MATH_RANDI_RANGE: {
+ MethodInfo mi("randi_range", PropertyInfo(Variant::INT, "from"), PropertyInfo(Variant::INT, "to"));
+ mi.return_val.type = Variant::INT;
+ return mi;
+ } break;
case MATH_SEED: {
MethodInfo mi("seed", PropertyInfo(Variant::INT, "seed"));
mi.return_val.type = Variant::NIL;
@@ -1749,13 +1734,6 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
return mi;
} break;
- case FUNC_FUNCREF: {
- MethodInfo mi("funcref", PropertyInfo(Variant::OBJECT, "instance"), PropertyInfo(Variant::STRING, "funcname"));
- mi.return_val.type = Variant::OBJECT;
- mi.return_val.class_name = "FuncRef";
- return mi;
-
- } break;
case TYPE_CONVERT: {
MethodInfo mi("convert", PropertyInfo(Variant::NIL, "what", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::INT, "type"));
mi.return_val.type = Variant::NIL;
diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_functions.h
index 2c6dc02913..005b49c5da 100644
--- a/modules/gdscript/gdscript_functions.h
+++ b/modules/gdscript/gdscript_functions.h
@@ -31,7 +31,7 @@
#ifndef GDSCRIPT_FUNCTIONS_H
#define GDSCRIPT_FUNCTIONS_H
-#include "core/variant.h"
+#include "core/variant/variant.h"
class GDScriptFunctions {
public:
@@ -73,9 +73,10 @@ public:
MATH_MOVE_TOWARD,
MATH_DECTIME,
MATH_RANDOMIZE,
- MATH_RAND,
+ MATH_RANDI,
MATH_RANDF,
- MATH_RANDOM,
+ MATH_RANDF_RANGE,
+ MATH_RANDI_RANGE,
MATH_SEED,
MATH_RANDSEED,
MATH_DEG2RAD,
@@ -91,7 +92,6 @@ public:
LOGIC_CLAMP,
LOGIC_NEAREST_PO2,
OBJ_WEAKREF,
- FUNC_FUNCREF,
TYPE_CONVERT,
TYPE_OF,
TYPE_EXISTS,
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 1f3e1c1e40..48fca16ab1 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -30,15 +30,15 @@
#include "gdscript_parser.h"
+#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
#include "core/math/math_defs.h"
#include "core/os/file_access.h"
-#include "core/project_settings.h"
#include "gdscript.h"
#ifdef DEBUG_ENABLED
#include "core/os/os.h"
-#include "core/string_builder.h"
+#include "core/string/string_builder.h"
#endif // DEBUG_ENABLED
#ifdef TOOLS_ENABLED
@@ -65,7 +65,7 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
builtin_types["Basis"] = Variant::BASIS;
builtin_types["Transform"] = Variant::TRANSFORM;
builtin_types["Color"] = Variant::COLOR;
- builtin_types["RID"] = Variant::_RID;
+ builtin_types["RID"] = Variant::RID;
builtin_types["Object"] = Variant::OBJECT;
builtin_types["StringName"] = Variant::STRING_NAME;
builtin_types["NodePath"] = Variant::NODE_PATH;
@@ -184,6 +184,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
#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) {
+ ERR_FAIL_COND(p_source == nullptr);
Vector<String> symbols;
if (!p_symbol1.empty()) {
symbols.push_back(p_symbol1);
@@ -201,6 +202,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
}
void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) {
+ ERR_FAIL_COND(p_source == nullptr);
if (is_ignoring_warnings) {
return;
}
@@ -327,7 +329,7 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
bool found = false;
const String &line = lines[i];
for (int j = 0; j < line.size(); j++) {
- if (line[j] == CharType(0xFFFF)) {
+ if (line[j] == char32_t(0xFFFF)) {
found = true;
break;
} else if (line[j] == '\t') {
@@ -466,44 +468,40 @@ void GDScriptParser::pop_multiline() {
}
bool GDScriptParser::is_statement_end() {
- return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON);
+ return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF);
}
void GDScriptParser::end_statement(const String &p_context) {
bool found = false;
- while (is_statement_end()) {
+ while (is_statement_end() && !is_at_end()) {
// Remove sequential newlines/semicolons.
found = true;
advance();
}
- if (!found) {
+ if (!found && !is_at_end()) {
push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name()));
}
}
void GDScriptParser::parse_program() {
- if (current.type == GDScriptTokenizer::Token::TK_EOF) {
- // Empty file.
- push_error("Source file is empty.");
- return;
- }
-
head = alloc_node<ClassNode>();
current_class = head;
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 (annotation != nullptr) {
+ 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);
+ }
}
}
@@ -560,6 +558,17 @@ void GDScriptParser::parse_program() {
parse_class_body();
+#ifdef TOOLS_ENABLED
+ for (Map<int, GDScriptTokenizer::CommentData>::Element *E = tokenizer.get_comments().front(); E; E = E->next()) {
+ if (E->get().new_line && E->get().comment.begins_with("##")) {
+ class_doc_line = MIN(class_doc_line, E->key());
+ }
+ }
+ if (has_comment(class_doc_line)) {
+ get_class_doc_comment(class_doc_line, head->doc_brief_description, head->doc_description, head->doc_tutorials, false);
+ }
+#endif // TOOLS_ENABLED
+
if (!check(GDScriptTokenizer::Token::TK_EOF)) {
push_error("Expected end of file.");
}
@@ -590,6 +599,14 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
return n_class;
}
+ if (match(GDScriptTokenizer::Token::EXTENDS)) {
+ if (n_class->extends_used) {
+ push_error(R"(Cannot use "extends" more than once in the same class.)");
+ }
+ parse_extends();
+ end_statement("superclass");
+ }
+
parse_class_body();
consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)");
@@ -635,7 +652,6 @@ void GDScriptParser::parse_extends() {
current_class->extends_path = previous.literal;
if (!match(GDScriptTokenizer::Token::PERIOD)) {
- end_statement("superclass path");
return;
}
}
@@ -663,6 +679,10 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
if (member == nullptr) {
return;
}
+#ifdef TOOLS_ENABLED
+ int doc_comment_line = member->start_line - 1;
+#endif // TOOLS_ENABLED
+
// Consume annotations.
while (!annotation_stack.empty()) {
AnnotationNode *last_annotation = annotation_stack.back()->get();
@@ -675,7 +695,25 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
clear_unused_annotations();
return;
}
+#ifdef TOOLS_ENABLED
+ if (last_annotation->start_line == doc_comment_line) {
+ doc_comment_line--;
+ }
+#endif // TOOLS_ENABLED
+ }
+
+#ifdef TOOLS_ENABLED
+ // Consume doc comments.
+ class_doc_line = MIN(class_doc_line, doc_comment_line - 1);
+ if (has_comment(doc_comment_line)) {
+ if constexpr (std::is_same_v<T, ClassNode>) {
+ get_class_doc_comment(doc_comment_line, member->doc_brief_description, member->doc_description, member->doc_tutorials, true);
+ } else {
+ member->doc_description = get_doc_comment(doc_comment_line);
+ }
}
+#endif // TOOLS_ENABLED
+
if (member->identifier != nullptr) {
// Enums may be unnamed.
// TODO: Consider names in outer scope too, for constants and classes (and static functions?)
@@ -990,9 +1028,15 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
signal->identifier = parse_identifier();
if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
- while (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
+ do {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+ // Allow for trailing comma.
+ break;
+ }
+
ParameterNode *parameter = parse_parameter();
if (parameter == nullptr) {
+ push_error("Expected signal parameter name.");
break;
}
if (parameter->default_value != nullptr) {
@@ -1004,7 +1048,8 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
signal->parameters_indices[parameter->identifier->name] = signal->parameters.size();
signal->parameters.push_back(parameter);
}
- }
+ } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
+
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");
}
@@ -1026,17 +1071,23 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
push_multiline(true);
consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
- int current_value = 0;
+ HashMap<StringName, int> elements;
do {
if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
break; // Allow trailing comma.
}
- if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) {
+ if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)")) {
EnumNode::Value item;
item.identifier = parse_identifier();
-
- if (!named) {
+ item.parent_enum = enum_node;
+ item.line = previous.start_line;
+ item.leftmost_column = previous.leftmost_column;
+ item.rightmost_column = previous.rightmost_column;
+
+ if (elements.has(item.identifier->name)) {
+ push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier);
+ } else if (!named) {
// TODO: Abstract this recursive member check.
ClassNode *parent = current_class;
while (parent != nullptr) {
@@ -1048,19 +1099,18 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
}
}
- if (match(GDScriptTokenizer::Token::EQUAL)) {
- if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected integer value after "=".)")) {
- item.custom_value = parse_literal();
+ elements[item.identifier->name] = item.line;
- if (item.custom_value->value.get_type() != Variant::INT) {
- push_error(R"(Expected integer value after "=".)");
- item.custom_value = nullptr;
- } else {
- current_value = item.custom_value->value;
- }
+ if (match(GDScriptTokenizer::Token::EQUAL)) {
+ ExpressionNode *value = parse_expression(false);
+ if (value == nullptr) {
+ push_error(R"(Expected expression value after "=".)");
}
+ item.custom_value = value;
}
- item.value = current_value++;
+ item.rightmost_column = previous.rightmost_column;
+
+ item.index = enum_node->values.size();
enum_node->values.push_back(item);
if (!named) {
// Add as member of current class.
@@ -1072,6 +1122,31 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
pop_multiline();
consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
+#ifdef TOOLS_ENABLED
+ // Enum values documentaion.
+ for (int i = 0; i < enum_node->values.size(); i++) {
+ if (i == enum_node->values.size() - 1) {
+ // If close bracket is same line as last value.
+ if (enum_node->values[i].line != previous.start_line && has_comment(enum_node->values[i].line)) {
+ if (named) {
+ enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true);
+ } else {
+ current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true));
+ }
+ }
+ } else {
+ // If two values are same line.
+ if (enum_node->values[i].line != enum_node->values[i + 1].line && has_comment(enum_node->values[i].line)) {
+ if (named) {
+ enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true);
+ } else {
+ current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true));
+ }
+ }
+ }
+ }
+#endif // TOOLS_ENABLED
+
end_statement("enum");
return enum_node;
@@ -1142,6 +1217,9 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function);
function->return_type = parse_type(true);
+ if (function->return_type == nullptr) {
+ push_error(R"(Expected return type or "void" after "->".)");
+ }
}
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
@@ -1346,7 +1424,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
advance();
ReturnNode *n_return = alloc_node<ReturnNode>();
if (!is_statement_end()) {
- if (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
+ if (current_function && current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
push_error(R"(Constructor cannot return a value.)");
}
n_return->return_value = parse_expression(false);
@@ -1402,9 +1480,13 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
}
#ifdef DEBUG_ENABLED
- if (unreachable) {
+ if (unreachable && result != nullptr) {
current_suite->has_unreachable_code = true;
- push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name);
+ if (current_function) {
+ push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name);
+ } else {
+ // TODO: Properties setters and getters with unreachable code are not being warned
+ }
}
#endif
@@ -1459,7 +1541,9 @@ GDScriptParser::ContinueNode *GDScriptParser::parse_continue() {
}
current_suite->has_continue = true;
end_statement(R"("continue")");
- return alloc_node<ContinueNode>();
+ ContinueNode *cont = alloc_node<ContinueNode>();
+ cont->is_for_match = is_continue_match;
+ return cont;
}
GDScriptParser::ForNode *GDScriptParser::parse_for() {
@@ -1478,10 +1562,12 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
// Save break/continue state.
bool could_break = can_break;
bool could_continue = can_continue;
+ bool was_continue_match = is_continue_match;
// Allow break/continue.
can_break = true;
can_continue = true;
+ is_continue_match = false;
SuiteNode *suite = alloc_node<SuiteNode>();
if (n_for->variable) {
@@ -1494,6 +1580,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
// Reset break/continue state.
can_break = could_break;
can_continue = could_continue;
+ is_continue_match = was_continue_match;
return n_for;
}
@@ -1628,8 +1715,10 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
// Save continue state.
bool could_continue = can_continue;
+ bool was_continue_match = is_continue_match;
// Allow continue for match.
can_continue = true;
+ is_continue_match = true;
SuiteNode *suite = alloc_node<SuiteNode>();
if (branch->patterns.size() > 0) {
@@ -1646,6 +1735,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
// Restore continue state.
can_continue = could_continue;
+ is_continue_match = was_continue_match;
return branch;
}
@@ -1803,16 +1893,19 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() {
// Save break/continue state.
bool could_break = can_break;
bool could_continue = can_continue;
+ bool was_continue_match = is_continue_match;
// Allow break/continue.
can_break = true;
can_continue = true;
+ is_continue_match = false;
n_while->loop = parse_suite(R"("while" block)");
// Reset break/continue state.
can_break = could_break;
can_continue = could_continue;
+ is_continue_match = was_continue_match;
return n_while;
}
@@ -1932,8 +2025,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_
}
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.)");
+ if (current_function && current_function->is_static) {
+ push_error(R"(Cannot use "self" inside a static function.)");
}
SelfNode *self = alloc_node<SelfNode>();
self->current_class = current_class;
@@ -2009,7 +2102,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression
push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name()));
}
- // TODO: Store the Variant operator here too (in the node).
// TODO: Also for unary, ternary, and assignment.
switch (op.type) {
case GDScriptTokenizer::Token::PLUS:
@@ -2171,39 +2263,50 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
switch (previous.type) {
case GDScriptTokenizer::Token::EQUAL:
assignment->operation = AssignmentNode::OP_NONE;
+ assignment->variant_op = Variant::OP_MAX;
#ifdef DEBUG_ENABLED
has_operator = false;
#endif
break;
case GDScriptTokenizer::Token::PLUS_EQUAL:
assignment->operation = AssignmentNode::OP_ADDITION;
+ assignment->variant_op = Variant::OP_ADD;
break;
case GDScriptTokenizer::Token::MINUS_EQUAL:
assignment->operation = AssignmentNode::OP_SUBTRACTION;
+ assignment->variant_op = Variant::OP_SUBTRACT;
break;
case GDScriptTokenizer::Token::STAR_EQUAL:
assignment->operation = AssignmentNode::OP_MULTIPLICATION;
+ assignment->variant_op = Variant::OP_MULTIPLY;
break;
case GDScriptTokenizer::Token::SLASH_EQUAL:
assignment->operation = AssignmentNode::OP_DIVISION;
+ assignment->variant_op = Variant::OP_DIVIDE;
break;
case GDScriptTokenizer::Token::PERCENT_EQUAL:
assignment->operation = AssignmentNode::OP_MODULO;
+ assignment->variant_op = Variant::OP_MODULE;
break;
case GDScriptTokenizer::Token::LESS_LESS_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_SHIFT_LEFT;
+ assignment->variant_op = Variant::OP_SHIFT_LEFT;
break;
case GDScriptTokenizer::Token::GREATER_GREATER_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_SHIFT_RIGHT;
+ assignment->variant_op = Variant::OP_SHIFT_RIGHT;
break;
case GDScriptTokenizer::Token::AMPERSAND_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_AND;
+ assignment->variant_op = Variant::OP_BIT_AND;
break;
case GDScriptTokenizer::Token::PIPE_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_OR;
+ assignment->variant_op = Variant::OP_BIT_OR;
break;
case GDScriptTokenizer::Token::CARET_EQUAL:
assignment->operation = AssignmentNode::OP_BIT_XOR;
+ assignment->variant_op = Variant::OP_BIT_XOR;
break;
default:
break; // Unreachable.
@@ -2332,7 +2435,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
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.)*");
+ if (grouped == nullptr) {
+ push_error(R"(Expected grouping expression.)");
+ } else {
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after grouping expression.)*");
+ }
return grouped;
}
@@ -2423,7 +2530,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
} else {
call->callee = p_previous_operand;
- if (call->callee->type == Node::IDENTIFIER) {
+ if (call->callee == nullptr) {
+ push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");
+ } else 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) {
@@ -2442,26 +2551,28 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
}
}
- 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();
+ // Arguments.
+ CompletionType ct = COMPLETION_CALL_ARGUMENTS;
+ if (get_builtin_function(call->function_name) == GDScriptFunctions::RESOURCE_LOAD) {
+ ct = COMPLETION_RESOURCE_PATH;
}
+ push_completion_call(call);
+ int argument_index = 0;
+ do {
+ make_completion_context(ct, 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);
+ }
+ ct = COMPLETION_CALL_ARGUMENTS;
+ } while (match(GDScriptTokenizer::Token::COMMA));
+ pop_completion_call();
pop_multiline();
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after call arguments.)*");
@@ -2472,27 +2583,30 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
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 "$".)");
+ push_error(R"(Expect node path as string or identifier 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)) {
+ } else if (current.is_node_name()) {
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 "/".)")) {
+ if (!current.is_node_name()) {
+ push_error(R"(Expect node path after "/".)");
return nullptr;
}
- IdentifierNode *identifier = parse_identifier();
+ advance();
+ IdentifierNode *identifier = alloc_node<IdentifierNode>();
+ identifier->name = previous.get_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 "$".)");
+ push_error(R"(Expect node path as string or identifier after "$".)");
return nullptr;
}
}
@@ -2511,29 +2625,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
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));
- }
- }
- }
}
pop_completion_call();
@@ -2592,6 +2683,218 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
return type;
}
+#ifdef TOOLS_ENABLED
+static bool _in_codeblock(String p_line, bool p_already_in, int *r_block_begins = nullptr) {
+ int start_block = p_line.rfind("[codeblock]");
+ int end_block = p_line.rfind("[/codeblock]");
+
+ if (start_block != -1 && r_block_begins) {
+ *r_block_begins = start_block;
+ }
+
+ if (p_already_in) {
+ if (end_block == -1) {
+ return true;
+ } else if (start_block == -1) {
+ return false;
+ } else {
+ return start_block > end_block;
+ }
+ } else {
+ if (start_block == -1) {
+ return false;
+ } else if (end_block == -1) {
+ return true;
+ } else {
+ return start_block > end_block;
+ }
+ }
+}
+
+bool GDScriptParser::has_comment(int p_line) {
+ return tokenizer.get_comments().has(p_line);
+}
+
+String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
+ const Map<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
+ ERR_FAIL_COND_V(!comments.has(p_line), String());
+
+ if (p_single_line) {
+ if (comments[p_line].comment.begins_with("##")) {
+ return comments[p_line].comment.trim_prefix("##").strip_edges();
+ }
+ return "";
+ }
+
+ String doc;
+
+ int line = p_line;
+ bool in_codeblock = false;
+
+ while (comments.has(line - 1)) {
+ if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
+ break;
+ }
+ line--;
+ }
+
+ int codeblock_begins = 0;
+ while (comments.has(line)) {
+ if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
+ break;
+ }
+ String doc_line = comments[line].comment.trim_prefix("##");
+
+ in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins);
+
+ if (in_codeblock) {
+ int i = 0;
+ for (; i < codeblock_begins; i++) {
+ if (doc_line[i] != ' ') {
+ break;
+ }
+ }
+ doc_line = doc_line.substr(i);
+ } else {
+ doc_line = doc_line.strip_edges();
+ }
+ String line_join = (in_codeblock) ? "\n" : " ";
+
+ doc = (doc.empty()) ? doc_line : doc + line_join + doc_line;
+ line++;
+ }
+
+ return doc;
+}
+
+void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class) {
+ const Map<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
+ if (!comments.has(p_line)) {
+ return;
+ }
+ ERR_FAIL_COND(p_brief != "" || p_desc != "" || p_tutorials.size() != 0);
+
+ int line = p_line;
+ bool in_codeblock = false;
+ enum Mode {
+ BRIEF,
+ DESC,
+ TUTORIALS,
+ DONE,
+ };
+ Mode mode = BRIEF;
+
+ if (p_inner_class) {
+ while (comments.has(line - 1)) {
+ if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
+ break;
+ }
+ line--;
+ }
+ }
+
+ int codeblock_begins = 0;
+ while (comments.has(line)) {
+ if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
+ break;
+ }
+
+ String title, link; // For tutorials.
+ String doc_line = comments[line++].comment.trim_prefix("##");
+ String striped_line = doc_line.strip_edges();
+
+ // Set the read mode.
+ if (striped_line.begins_with("@desc:") && p_desc == "") {
+ mode = DESC;
+ striped_line = striped_line.trim_prefix("@desc:");
+ in_codeblock = _in_codeblock(doc_line, in_codeblock);
+
+ } else if (striped_line.begins_with("@tutorial")) {
+ int begin_scan = String("@tutorial").length();
+ if (begin_scan >= striped_line.length()) {
+ continue; // invalid syntax.
+ }
+
+ if (striped_line[begin_scan] == ':') { // No title.
+ // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional.
+ title = "";
+ link = striped_line.trim_prefix("@tutorial:").strip_edges();
+
+ } else {
+ /* Syntax:
+ @tutorial ( The Title Here ) : http://the.url/
+ ^ open ^ close ^ colon ^ url
+ */
+ int open_bracket_pos = begin_scan, close_bracket_pos = 0;
+ while (open_bracket_pos < striped_line.length() && (striped_line[open_bracket_pos] == ' ' || striped_line[open_bracket_pos] == '\t')) {
+ open_bracket_pos++;
+ }
+ if (open_bracket_pos == striped_line.length() || striped_line[open_bracket_pos++] != '(') {
+ continue; // invalid syntax.
+ }
+ close_bracket_pos = open_bracket_pos;
+ while (close_bracket_pos < striped_line.length() && striped_line[close_bracket_pos] != ')') {
+ close_bracket_pos++;
+ }
+ if (close_bracket_pos == striped_line.length()) {
+ continue; // invalid syntax.
+ }
+
+ int colon_pos = close_bracket_pos + 1;
+ while (colon_pos < striped_line.length() && (striped_line[colon_pos] == ' ' || striped_line[colon_pos] == '\t')) {
+ colon_pos++;
+ }
+ if (colon_pos == striped_line.length() || striped_line[colon_pos++] != ':') {
+ continue; // invalid syntax.
+ }
+
+ title = striped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges();
+ link = striped_line.substr(colon_pos).strip_edges();
+ }
+
+ mode = TUTORIALS;
+ in_codeblock = false;
+ } else if (striped_line.empty()) {
+ continue;
+ } else {
+ // Tutorial docs are single line, we need a @tag after it.
+ if (mode == TUTORIALS) {
+ mode = DONE;
+ }
+
+ in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins);
+ }
+
+ if (in_codeblock) {
+ int i = 0;
+ for (; i < codeblock_begins; i++) {
+ if (doc_line[i] != ' ') {
+ break;
+ }
+ }
+ doc_line = doc_line.substr(i);
+ } else {
+ doc_line = striped_line;
+ }
+ String line_join = (in_codeblock) ? "\n" : " ";
+
+ switch (mode) {
+ case BRIEF:
+ p_brief = (p_brief.length() == 0) ? doc_line : p_brief + line_join + doc_line;
+ break;
+ case DESC:
+ p_desc = (p_desc.length() == 0) ? doc_line : p_desc + line_join + doc_line;
+ break;
+ case TUTORIALS:
+ p_tutorials.append(Pair<String, String>(title, link));
+ break;
+ case DONE:
+ return;
+ }
+ }
+}
+#endif // TOOLS_ENABLED
+
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.
@@ -2778,7 +3081,9 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
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));
+ Variant r;
+ Variant::construct(parameter.type, r, &(name), 1, error);
+ p_annotation->resolved_arguments.push_back(r);
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);
@@ -2800,7 +3105,9 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
}
Callable::CallError error;
const Variant *args = &value;
- p_annotation->resolved_arguments.push_back(Variant::construct(parameter.type, &(args), 1, error));
+ Variant r;
+ Variant::construct(parameter.type, r, &(args), 1, error);
+ p_annotation->resolved_arguments.push_back(r);
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);
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index c9ab3d4e12..44605bc20f 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -31,23 +31,23 @@
#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/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 "core/io/resource.h"
+#include "core/object/reference.h"
+#include "core/object/script_language.h"
+#include "core/string/string_name.h"
+#include "core/string/ustring.h"
+#include "core/templates/hash_map.h"
+#include "core/templates/list.h"
+#include "core/templates/map.h"
+#include "core/templates/vector.h"
+#include "core/variant/variant.h"
#include "gdscript_cache.h"
#include "gdscript_functions.h"
#include "gdscript_tokenizer.h"
#ifdef DEBUG_ENABLED
-#include "core/string_builder.h"
+#include "core/string/string_builder.h"
#include "gdscript_warning.h"
#endif // DEBUG_ENABLED
@@ -383,6 +383,14 @@ public:
CallNode() {
type = CALL;
}
+
+ Type get_callee_type() const {
+ if (callee == nullptr) {
+ return Type::NONE;
+ } else {
+ return callee->type;
+ }
+ }
};
struct CastNode : public ExpressionNode {
@@ -397,14 +405,24 @@ public:
struct EnumNode : public Node {
struct Value {
IdentifierNode *identifier = nullptr;
- LiteralNode *custom_value = nullptr;
+ ExpressionNode *custom_value = nullptr;
+ EnumNode *parent_enum = nullptr;
+ int index = -1;
+ bool resolved = false;
int value = 0;
int line = 0;
int leftmost_column = 0;
int rightmost_column = 0;
+#ifdef TOOLS_ENABLED
+ String doc_description;
+#endif // TOOLS_ENABLED
};
+
IdentifierNode *identifier = nullptr;
Vector<Value> values;
+#ifdef TOOLS_ENABLED
+ String doc_description;
+#endif // TOOLS_ENABLED
EnumNode() {
type = ENUM;
@@ -557,6 +575,17 @@ public:
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.
+#ifdef TOOLS_ENABLED
+ String doc_description;
+ String doc_brief_description;
+ Vector<Pair<String, String>> doc_tutorials;
+
+ // EnumValue docs are parsed after itself, so we need a method to add/modify the doc property later.
+ void set_enum_value_doc(const StringName &p_name, const String &p_doc_description) {
+ ERR_FAIL_INDEX(members_indices[p_name], members.size());
+ members.write[members_indices[p_name]].enum_value.doc_description = p_doc_description;
+ }
+#endif // TOOLS_ENABLED
bool resolved_interface = false;
bool resolved_body = false;
@@ -591,6 +620,9 @@ public:
TypeNode *datatype_specifier = nullptr;
bool infer_datatype = false;
int usages = 0;
+#ifdef TOOLS_ENABLED
+ String doc_description;
+#endif // TOOLS_ENABLED
ConstantNode() {
type = CONSTANT;
@@ -598,6 +630,7 @@ public:
};
struct ContinueNode : public Node {
+ bool is_for_match = false;
ContinueNode() {
type = CONTINUE;
}
@@ -641,6 +674,10 @@ public:
bool is_coroutine = false;
MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
MethodInfo info;
+#ifdef TOOLS_ENABLED
+ Vector<Variant> default_arg_values;
+ String doc_description;
+#endif // TOOLS_ENABLED
bool resolved_signature = false;
bool resolved_body = false;
@@ -808,6 +845,9 @@ public:
IdentifierNode *identifier = nullptr;
Vector<ParameterNode *> parameters;
HashMap<StringName, int> parameters_indices;
+#ifdef TOOLS_ENABLED
+ String doc_description;
+#endif // TOOLS_ENABLED
SignalNode() {
type = SIGNAL;
@@ -1000,6 +1040,9 @@ public:
MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
int assignments = 0;
int usages = 0;
+#ifdef TOOLS_ENABLED
+ String doc_description;
+#endif // TOOLS_ENABLED
VariableNode() {
type = VARIABLE;
@@ -1068,6 +1111,7 @@ private:
bool panic_mode = false;
bool can_break = false;
bool can_continue = false;
+ bool is_continue_match = false; // Whether a `continue` will act on a `match`.
bool is_ignoring_warnings = false;
List<bool> multiline_stack;
@@ -1257,6 +1301,13 @@ private:
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);
+#ifdef TOOLS_ENABLED
+ // Doc comments.
+ int class_doc_line = 0x7FFFFFFF;
+ bool has_comment(int p_line);
+ String get_doc_comment(int p_line, bool p_single_line = false);
+ void get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class);
+#endif // TOOLS_ENABLED
public:
Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion);
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 7a4bdd88ba..ac43105254 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -30,7 +30,7 @@
#include "gdscript_tokenizer.h"
-#include "core/error_macros.h"
+#include "core/error/error_macros.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
@@ -156,6 +156,64 @@ const char *GDScriptTokenizer::Token::get_name() const {
return token_names[type];
}
+bool GDScriptTokenizer::Token::is_identifier() const {
+ // Note: Most keywords should not be recognized as identifiers.
+ // These are only exceptions for stuff that already is on the engine's API.
+ switch (type) {
+ case IDENTIFIER:
+ case MATCH: // Used in String.match().
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool GDScriptTokenizer::Token::is_node_name() const {
+ // This is meant to allow keywords with the $ notation, but not as general identifiers.
+ switch (type) {
+ case IDENTIFIER:
+ case AND:
+ case AS:
+ case ASSERT:
+ case AWAIT:
+ case BREAK:
+ case BREAKPOINT:
+ case CLASS_NAME:
+ case CLASS:
+ case CONST:
+ case CONTINUE:
+ case ELIF:
+ case ELSE:
+ case ENUM:
+ case EXTENDS:
+ case FOR:
+ case FUNC:
+ case IF:
+ case IN:
+ case IS:
+ case MATCH:
+ case NAMESPACE:
+ case NOT:
+ case OR:
+ case PASS:
+ case PRELOAD:
+ case RETURN:
+ case SELF:
+ case SIGNAL:
+ case STATIC:
+ case SUPER:
+ case TRAIT:
+ case UNDERSCORE:
+ case VAR:
+ case VOID:
+ case WHILE:
+ case YIELD:
+ return true;
+ 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];
@@ -164,7 +222,7 @@ String GDScriptTokenizer::get_token_name(Token::Type p_token_type) {
void GDScriptTokenizer::set_source_code(const String &p_source_code) {
source = p_source_code;
if (source.empty()) {
- _source = L"";
+ _source = U"";
} else {
_source = source.ptr();
}
@@ -205,7 +263,7 @@ bool GDScriptTokenizer::is_past_cursor() const {
return true;
}
-CharType GDScriptTokenizer::_advance() {
+char32_t GDScriptTokenizer::_advance() {
if (unlikely(_is_at_end())) {
return '\0';
}
@@ -224,15 +282,15 @@ CharType GDScriptTokenizer::_advance() {
return _peek(-1);
}
-void GDScriptTokenizer::push_paren(CharType p_char) {
+void GDScriptTokenizer::push_paren(char32_t p_char) {
paren_stack.push_back(p_char);
}
-bool GDScriptTokenizer::pop_paren(CharType p_expected) {
+bool GDScriptTokenizer::pop_paren(char32_t p_expected) {
if (paren_stack.empty()) {
return false;
}
- CharType actual = paren_stack.back()->get();
+ char32_t actual = paren_stack.back()->get();
paren_stack.pop_back();
return actual == p_expected;
@@ -244,19 +302,19 @@ GDScriptTokenizer::Token GDScriptTokenizer::pop_error() {
return error;
}
-static bool _is_alphanumeric(CharType c) {
+static bool _is_alphanumeric(char32_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
}
-static bool _is_digit(CharType c) {
+static bool _is_digit(char32_t c) {
return (c >= '0' && c <= '9');
}
-static bool _is_hex_digit(CharType c) {
+static bool _is_hex_digit(char32_t c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
-static bool _is_binary_digit(CharType c) {
+static bool _is_binary_digit(char32_t c) {
return (c == '0' || c == '1');
}
@@ -346,7 +404,7 @@ void GDScriptTokenizer::push_error(const Token &p_error) {
error_stack.push_back(p_error);
}
-GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(CharType p_paren) {
+GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(char32_t p_paren) {
if (paren_stack.empty()) {
return make_error(vformat("Closing \"%c\" doesn't have an opening counterpart.", p_paren));
}
@@ -355,8 +413,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(CharType p_paren) {
return error;
}
-GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(CharType p_test, Token::Type p_double_type) {
- const CharType *next = _current + 1;
+GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, Token::Type p_double_type) {
+ const char32_t *next = _current + 1;
int chars = 2; // Two already matched.
// Test before consuming characters, since we don't want to consume more than needed.
@@ -520,7 +578,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
}
void GDScriptTokenizer::newline(bool p_make_token) {
- // Don't overwrite previous newline, nor create if we want a line contination.
+ // Don't overwrite previous newline, nor create if we want a line continuation.
if (p_make_token && !pending_newline && !line_continuation) {
Token newline(Token::NEWLINE);
newline.start_line = line;
@@ -544,7 +602,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
bool has_decimal = false;
bool has_exponent = false;
bool has_error = false;
- bool (*digit_check_func)(CharType) = _is_digit;
+ bool (*digit_check_func)(char32_t) = _is_digit;
if (_peek(-1) == '.') {
has_decimal = true;
@@ -563,7 +621,19 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
}
// Allow '_' to be used in a number, for readability.
+ bool previous_was_underscore = false;
while (digit_check_func(_peek()) || _peek() == '_') {
+ if (_peek() == '_') {
+ if (previous_was_underscore) {
+ Token error = make_error(R"(Only one underscore can be used as a numeric separator.)");
+ error.start_column = column;
+ error.leftmost_column = column;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ push_error(error);
+ }
+ previous_was_underscore = true;
+ }
_advance();
}
@@ -614,7 +684,27 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
_advance();
}
// Consume exponent digits.
+ if (!_is_digit(_peek())) {
+ Token error = make_error(R"(Expected exponent value after "e".)");
+ error.start_column = column;
+ error.leftmost_column = column;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ push_error(error);
+ }
+ previous_was_underscore = false;
while (_is_digit(_peek()) || _peek() == '_') {
+ if (_peek() == '_') {
+ if (previous_was_underscore) {
+ Token error = make_error(R"(Only one underscore can be used as a numeric separator.)");
+ error.start_column = column;
+ error.leftmost_column = column;
+ error.end_column = column + 1;
+ error.rightmost_column = column + 1;
+ push_error(error);
+ }
+ previous_was_underscore = true;
+ }
_advance();
}
}
@@ -672,7 +762,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
_advance();
}
- CharType quote_char = _peek(-1);
+ char32_t quote_char = _peek(-1);
if (_peek() == quote_char && _peek(1) == quote_char) {
is_multiline = true;
@@ -689,7 +779,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
return make_error("Unterminated string.");
}
- CharType ch = _peek();
+ char32_t ch = _peek();
if (ch == '\\') {
// Escape pattern.
@@ -699,13 +789,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
}
// Grab escape character.
- CharType code = _peek();
+ char32_t code = _peek();
_advance();
if (_is_at_end()) {
return make_error("Unterminated string.");
}
- CharType escaped = 0;
+ char32_t escaped = 0;
bool valid_escape = true;
switch (code) {
@@ -746,8 +836,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
return make_error("Unterminated string.");
}
- CharType digit = _peek();
- CharType value = 0;
+ char32_t digit = _peek();
+ char32_t value = 0;
if (digit >= '0' && digit <= '9') {
value = digit - '0';
} else if (digit >= 'a' && digit <= 'f') {
@@ -850,7 +940,7 @@ void GDScriptTokenizer::check_indent() {
}
for (;;) {
- CharType current_indent_char = _peek();
+ char32_t current_indent_char = _peek();
int indent_count = 0;
if (current_indent_char != ' ' && current_indent_char != '\t' && current_indent_char != '\r' && current_indent_char != '\n' && current_indent_char != '#') {
@@ -880,7 +970,7 @@ void GDScriptTokenizer::check_indent() {
// Check indent level.
bool mixed = false;
while (!_is_at_end()) {
- CharType space = _peek();
+ char32_t space = _peek();
if (space == '\t') {
// Consider individual tab columns.
column += tab_size - 1;
@@ -924,9 +1014,17 @@ void GDScriptTokenizer::check_indent() {
}
if (_peek() == '#') {
// Comment. Advance to the next line.
+#ifdef TOOLS_ENABLED
+ String comment;
+ while (_peek() != '\n' && !_is_at_end()) {
+ comment += _advance();
+ }
+ comments[line] = CommentData(comment, true);
+#else
while (_peek() != '\n' && !_is_at_end()) {
_advance();
}
+#endif // TOOLS_ENABLED
if (_is_at_end()) {
// Reached the end with an empty line, so just dedent as much as needed.
pending_indents -= indent_level();
@@ -949,7 +1047,7 @@ void GDScriptTokenizer::check_indent() {
// 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()));
+ Token error = make_error(vformat("Used \"%s\" for indentation instead \"%s\" 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;
@@ -1013,7 +1111,7 @@ void GDScriptTokenizer::_skip_whitespace() {
}
for (;;) {
- CharType c = _peek();
+ char32_t c = _peek();
switch (c) {
case ' ':
_advance();
@@ -1035,18 +1133,26 @@ void GDScriptTokenizer::_skip_whitespace() {
newline(!is_bol); // Don't create new line token if line is empty.
check_indent();
break;
- case '#':
+ case '#': {
// Comment.
+#ifdef TOOLS_ENABLED
+ String comment;
+ while (_peek() != '\n' && !_is_at_end()) {
+ comment += _advance();
+ }
+ comments[line] = CommentData(comment, is_bol);
+#else
while (_peek() != '\n' && !_is_at_end()) {
_advance();
}
+#endif // TOOLS_ENABLED
if (_is_at_end()) {
return;
}
_advance(); // Consume '\n'
newline(!is_bol);
check_indent();
- break;
+ } break;
default:
return;
}
@@ -1102,7 +1208,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
return make_token(Token::TK_EOF);
}
- const CharType c = _advance();
+ const char32_t c = _advance();
if (c == '\\') {
// Line continuation with backslash.
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 059a226924..f236c86f9f 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -31,10 +31,11 @@
#ifndef GDSCRIPT_TOKENIZER_H
#define GDSCRIPT_TOKENIZER_H
-#include "core/list.h"
-#include "core/set.h"
-#include "core/variant.h"
-#include "core/vector.h"
+#include "core/templates/list.h"
+#include "core/templates/map.h"
+#include "core/templates/set.h"
+#include "core/templates/vector.h"
+#include "core/variant/variant.h"
class GDScriptTokenizer {
public:
@@ -168,9 +169,9 @@ public:
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; }
+ bool is_identifier() const;
+ bool is_node_name() const;
+ StringName get_identifier() const { return source; }
Token(Type p_type) {
type = p_type;
@@ -181,16 +182,31 @@ public:
}
};
+#ifdef TOOLS_ENABLED
+ struct CommentData {
+ String comment;
+ bool new_line = false;
+ CommentData() {}
+ CommentData(const String &p_comment, bool p_new_line) {
+ comment = p_comment;
+ new_line = p_new_line;
+ }
+ };
+ const Map<int, CommentData> &get_comments() const {
+ return comments;
+ }
+#endif // TOOLS_ENABLED
+
private:
String source;
- const CharType *_source = nullptr;
- const CharType *_current = nullptr;
+ const char32_t *_source = nullptr;
+ const char32_t *_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;
+ const char32_t *_start = nullptr;
int start_line = 0, start_column = 0;
int leftmost_column = 0, rightmost_column = 0;
@@ -202,30 +218,34 @@ private:
Token last_newline;
int pending_indents = 0;
List<int> indent_stack;
- List<CharType> paren_stack;
- CharType indent_char = '\0';
+ List<char32_t> paren_stack;
+ char32_t indent_char = '\0';
int position = 0;
int length = 0;
+#ifdef TOOLS_ENABLED
+ Map<int, CommentData> comments;
+#endif // TOOLS_ENABLED
+
_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'; }
+ _FORCE_INLINE_ char32_t _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();
+ char32_t _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_paren_error(char32_t 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);
+ Token check_vcs_marker(char32_t p_test, Token::Type p_double_type);
+ void push_paren(char32_t p_char);
+ bool pop_paren(char32_t p_expected);
void newline(bool p_make_token);
Token number();
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
new file mode 100644
index 0000000000..7942ee8d97
--- /dev/null
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -0,0 +1,2863 @@
+/*************************************************************************/
+/* gdscript_vm.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_function.h"
+
+#include "core/core_string_names.h"
+#include "core/os/os.h"
+#include "gdscript.h"
+#include "gdscript_functions.h"
+
+Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const {
+ int address = p_address & ADDR_MASK;
+
+ //sequential table (jump table generated by compiler)
+ switch ((p_address & ADDR_TYPE_MASK) >> ADDR_BITS) {
+ case ADDR_TYPE_SELF: {
+#ifdef DEBUG_ENABLED
+ if (unlikely(!p_instance)) {
+ r_error = "Cannot access self without instance.";
+ return nullptr;
+ }
+#endif
+ return &self;
+ } break;
+ case ADDR_TYPE_CLASS: {
+ return &static_ref;
+ } break;
+ case ADDR_TYPE_MEMBER: {
+#ifdef DEBUG_ENABLED
+ if (unlikely(!p_instance)) {
+ r_error = "Cannot access member without instance.";
+ return nullptr;
+ }
+#endif
+ //member indexing is O(1)
+ return &p_instance->members.write[address];
+ } break;
+ case ADDR_TYPE_CLASS_CONSTANT: {
+ //todo change to index!
+ GDScript *s = p_script;
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_INDEX_V(address, _global_names_count, nullptr);
+#endif
+ const StringName *sn = &_global_names_ptr[address];
+
+ while (s) {
+ GDScript *o = s;
+ while (o) {
+ Map<StringName, Variant>::Element *E = o->constants.find(*sn);
+ if (E) {
+ return &E->get();
+ }
+ o = o->_owner;
+ }
+ s = s->_base;
+ }
+
+ ERR_FAIL_V_MSG(nullptr, "GDScriptCompiler bug.");
+ } break;
+ case ADDR_TYPE_LOCAL_CONSTANT: {
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_INDEX_V(address, _constant_count, nullptr);
+#endif
+ return &_constants_ptr[address];
+ } break;
+ case ADDR_TYPE_STACK:
+ case ADDR_TYPE_STACK_VARIABLE: {
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_INDEX_V(address, _stack_size, nullptr);
+#endif
+ return &p_stack[address];
+ } break;
+ case ADDR_TYPE_GLOBAL: {
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_INDEX_V(address, GDScriptLanguage::get_singleton()->get_global_array_size(), nullptr);
+#endif
+ return &GDScriptLanguage::get_singleton()->get_global_array()[address];
+ } break;
+#ifdef TOOLS_ENABLED
+ case ADDR_TYPE_NAMED_GLOBAL: {
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_INDEX_V(address, _global_names_count, nullptr);
+#endif
+ StringName id = _global_names_ptr[address];
+
+ if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) {
+ return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id];
+ } else {
+ r_error = "Autoload singleton '" + String(id) + "' has been removed.";
+ return nullptr;
+ }
+ } break;
+#endif
+ case ADDR_TYPE_NIL: {
+ return &nil;
+ } break;
+ }
+
+ ERR_FAIL_V_MSG(nullptr, "Bad code! (unknown addressing mode).");
+ return nullptr;
+}
+
+#ifdef DEBUG_ENABLED
+static String _get_var_type(const Variant *p_var) {
+ String basestr;
+
+ if (p_var->get_type() == Variant::OBJECT) {
+ bool was_freed;
+ Object *bobj = p_var->get_validated_object_with_check(was_freed);
+ if (!bobj) {
+ if (was_freed) {
+ basestr = "null instance";
+ } else {
+ basestr = "previously freed";
+ }
+ } else {
+ if (bobj->get_script_instance()) {
+ basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")";
+ } else {
+ basestr = bobj->get_class();
+ }
+ }
+
+ } else {
+ basestr = Variant::get_type_name(p_var->get_type());
+ }
+
+ return basestr;
+}
+#endif // DEBUG_ENABLED
+
+String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const {
+ String err_text;
+
+ if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
+ int errorarg = p_err.argument;
+ // Handle the Object to Object case separately as we don't have further class details.
+#ifdef DEBUG_ENABLED
+ if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) {
+ err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class.";
+ } else
+#endif // DEBUG_ENABLED
+ {
+ err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + ".";
+ }
+ } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
+ err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
+ } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
+ err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
+ } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
+ err_text = "Invalid call. Nonexistent " + p_where + ".";
+ } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) {
+ err_text = "Attempt to call " + p_where + " on a null instance.";
+ } else {
+ err_text = "Bug, call error: #" + itos(p_err.error);
+ }
+
+ return err_text;
+}
+
+#if defined(__GNUC__)
+#define OPCODES_TABLE \
+ static const void *switch_table_ops[] = { \
+ &&OPCODE_OPERATOR, \
+ &&OPCODE_OPERATOR_VALIDATED, \
+ &&OPCODE_EXTENDS_TEST, \
+ &&OPCODE_IS_BUILTIN, \
+ &&OPCODE_SET_KEYED, \
+ &&OPCODE_SET_KEYED_VALIDATED, \
+ &&OPCODE_SET_INDEXED_VALIDATED, \
+ &&OPCODE_GET_KEYED, \
+ &&OPCODE_GET_KEYED_VALIDATED, \
+ &&OPCODE_GET_INDEXED_VALIDATED, \
+ &&OPCODE_SET_NAMED, \
+ &&OPCODE_SET_NAMED_VALIDATED, \
+ &&OPCODE_GET_NAMED, \
+ &&OPCODE_GET_NAMED_VALIDATED, \
+ &&OPCODE_SET_MEMBER, \
+ &&OPCODE_GET_MEMBER, \
+ &&OPCODE_ASSIGN, \
+ &&OPCODE_ASSIGN_TRUE, \
+ &&OPCODE_ASSIGN_FALSE, \
+ &&OPCODE_ASSIGN_TYPED_BUILTIN, \
+ &&OPCODE_ASSIGN_TYPED_NATIVE, \
+ &&OPCODE_ASSIGN_TYPED_SCRIPT, \
+ &&OPCODE_CAST_TO_BUILTIN, \
+ &&OPCODE_CAST_TO_NATIVE, \
+ &&OPCODE_CAST_TO_SCRIPT, \
+ &&OPCODE_CONSTRUCT, \
+ &&OPCODE_CONSTRUCT_VALIDATED, \
+ &&OPCODE_CONSTRUCT_ARRAY, \
+ &&OPCODE_CONSTRUCT_DICTIONARY, \
+ &&OPCODE_CALL, \
+ &&OPCODE_CALL_RETURN, \
+ &&OPCODE_CALL_ASYNC, \
+ &&OPCODE_CALL_BUILT_IN, \
+ &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \
+ &&OPCODE_CALL_SELF_BASE, \
+ &&OPCODE_CALL_METHOD_BIND, \
+ &&OPCODE_CALL_METHOD_BIND_RET, \
+ &&OPCODE_CALL_PTRCALL_NO_RETURN, \
+ &&OPCODE_CALL_PTRCALL_BOOL, \
+ &&OPCODE_CALL_PTRCALL_INT, \
+ &&OPCODE_CALL_PTRCALL_FLOAT, \
+ &&OPCODE_CALL_PTRCALL_STRING, \
+ &&OPCODE_CALL_PTRCALL_VECTOR2, \
+ &&OPCODE_CALL_PTRCALL_VECTOR2I, \
+ &&OPCODE_CALL_PTRCALL_RECT2, \
+ &&OPCODE_CALL_PTRCALL_RECT2I, \
+ &&OPCODE_CALL_PTRCALL_VECTOR3, \
+ &&OPCODE_CALL_PTRCALL_VECTOR3I, \
+ &&OPCODE_CALL_PTRCALL_TRANSFORM2D, \
+ &&OPCODE_CALL_PTRCALL_PLANE, \
+ &&OPCODE_CALL_PTRCALL_QUAT, \
+ &&OPCODE_CALL_PTRCALL_AABB, \
+ &&OPCODE_CALL_PTRCALL_BASIS, \
+ &&OPCODE_CALL_PTRCALL_TRANSFORM, \
+ &&OPCODE_CALL_PTRCALL_COLOR, \
+ &&OPCODE_CALL_PTRCALL_STRING_NAME, \
+ &&OPCODE_CALL_PTRCALL_NODE_PATH, \
+ &&OPCODE_CALL_PTRCALL_RID, \
+ &&OPCODE_CALL_PTRCALL_OBJECT, \
+ &&OPCODE_CALL_PTRCALL_CALLABLE, \
+ &&OPCODE_CALL_PTRCALL_SIGNAL, \
+ &&OPCODE_CALL_PTRCALL_DICTIONARY, \
+ &&OPCODE_CALL_PTRCALL_ARRAY, \
+ &&OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY, \
+ &&OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY, \
+ &&OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY, \
+ &&OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY, \
+ &&OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY, \
+ &&OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY, \
+ &&OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY, \
+ &&OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY, \
+ &&OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, \
+ &&OPCODE_AWAIT, \
+ &&OPCODE_AWAIT_RESUME, \
+ &&OPCODE_JUMP, \
+ &&OPCODE_JUMP_IF, \
+ &&OPCODE_JUMP_IF_NOT, \
+ &&OPCODE_JUMP_TO_DEF_ARGUMENT, \
+ &&OPCODE_RETURN, \
+ &&OPCODE_ITERATE_BEGIN, \
+ &&OPCODE_ITERATE_BEGIN_INT, \
+ &&OPCODE_ITERATE_BEGIN_FLOAT, \
+ &&OPCODE_ITERATE_BEGIN_VECTOR2, \
+ &&OPCODE_ITERATE_BEGIN_VECTOR2I, \
+ &&OPCODE_ITERATE_BEGIN_VECTOR3, \
+ &&OPCODE_ITERATE_BEGIN_VECTOR3I, \
+ &&OPCODE_ITERATE_BEGIN_STRING, \
+ &&OPCODE_ITERATE_BEGIN_DICTIONARY, \
+ &&OPCODE_ITERATE_BEGIN_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_OBJECT, \
+ &&OPCODE_ITERATE, \
+ &&OPCODE_ITERATE_INT, \
+ &&OPCODE_ITERATE_FLOAT, \
+ &&OPCODE_ITERATE_VECTOR2, \
+ &&OPCODE_ITERATE_VECTOR2I, \
+ &&OPCODE_ITERATE_VECTOR3, \
+ &&OPCODE_ITERATE_VECTOR3I, \
+ &&OPCODE_ITERATE_STRING, \
+ &&OPCODE_ITERATE_DICTIONARY, \
+ &&OPCODE_ITERATE_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_INT32_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_INT64_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_STRING_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \
+ &&OPCODE_ITERATE_OBJECT, \
+ &&OPCODE_ASSERT, \
+ &&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:
+#define OPCODE_WHILE(m_test) \
+ OPSWHILE:
+#define OPCODES_END \
+ OPSEXIT:
+#define OPCODES_OUT \
+ OPSOUT:
+#define DISPATCH_OPCODE goto OPSWHILE
+#define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test];
+#define OPCODE_BREAK goto OPSEXIT
+#define OPCODE_OUT goto OPSOUT
+#else
+#define OPCODES_TABLE
+#define OPCODE(m_op) case m_op:
+#define OPCODE_WHILE(m_test) while (m_test)
+#define OPCODES_END
+#define OPCODES_OUT
+#define DISPATCH_OPCODE continue
+#define OPCODE_SWITCH(m_test) switch (m_test)
+#define OPCODE_BREAK break
+#define OPCODE_OUT break
+#endif
+
+// Helpers for VariantInternal methods in macros.
+#define OP_GET_BOOL get_bool
+#define OP_GET_INT get_int
+#define OP_GET_FLOAT get_float
+#define OP_GET_VECTOR2 get_vector2
+#define OP_GET_VECTOR2I get_vector2i
+#define OP_GET_VECTOR3 get_vector3
+#define OP_GET_VECTOR3I get_vector3i
+#define OP_GET_RECT2 get_rect2
+#define OP_GET_RECT2I get_rect2i
+#define OP_GET_QUAT get_quat
+#define OP_GET_COLOR get_color
+#define OP_GET_STRING get_string
+#define OP_GET_STRING_NAME get_string_name
+#define OP_GET_NODE_PATH get_node_path
+#define OP_GET_CALLABLE get_callable
+#define OP_GET_SIGNAL get_signal
+#define OP_GET_ARRAY get_array
+#define OP_GET_DICTIONARY get_dictionary
+#define OP_GET_PACKED_BYTE_ARRAY get_byte_array
+#define OP_GET_PACKED_INT32_ARRAY get_int32_array
+#define OP_GET_PACKED_INT64_ARRAY get_int64_array
+#define OP_GET_PACKED_FLOAT32_ARRAY get_float32_array
+#define OP_GET_PACKED_FLOAT64_ARRAY get_float64_array
+#define OP_GET_PACKED_STRING_ARRAY get_string_array
+#define OP_GET_PACKED_VECTOR2_ARRAY get_vector2_array
+#define OP_GET_PACKED_VECTOR3_ARRAY get_vector3_array
+#define OP_GET_PACKED_COLOR_ARRAY get_color_array
+#define OP_GET_TRANSFORM get_transform
+#define OP_GET_TRANSFORM2D get_transform2d
+#define OP_GET_PLANE get_plane
+#define OP_GET_AABB get_aabb
+#define OP_GET_BASIS get_basis
+#define OP_GET_RID get_rid
+
+Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) {
+ OPCODES_TABLE;
+
+ if (!_code_ptr) {
+ return Variant();
+ }
+
+ r_err.error = Callable::CallError::CALL_OK;
+
+ Variant self;
+ Variant static_ref;
+ Variant retvalue;
+ Variant *stack = nullptr;
+ Variant **instruction_args;
+ const void **call_args_ptr = nullptr;
+ int defarg = 0;
+
+#ifdef DEBUG_ENABLED
+
+ //GDScriptLanguage::get_singleton()->calls++;
+
+#endif
+
+ uint32_t alloca_size = 0;
+ GDScript *script;
+ int ip = 0;
+ int line = _initial_line;
+
+ if (p_state) {
+ //use existing (supplied) state (awaited)
+ stack = (Variant *)p_state->stack.ptr();
+ instruction_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check
+ line = p_state->line;
+ ip = p_state->ip;
+ alloca_size = p_state->stack.size();
+ script = p_state->script;
+ p_instance = p_state->instance;
+ defarg = p_state->defarg;
+ self = p_state->self;
+
+ } else {
+ if (p_argcount != _argument_count) {
+ if (p_argcount > _argument_count) {
+ r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
+ r_err.argument = _argument_count;
+
+ return Variant();
+ } else if (p_argcount < _argument_count - _default_arg_count) {
+ r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
+ r_err.argument = _argument_count - _default_arg_count;
+ return Variant();
+ } else {
+ defarg = _argument_count - p_argcount;
+ }
+ }
+
+ alloca_size = sizeof(Variant *) * _instruction_args_size + sizeof(Variant) * _stack_size;
+
+ if (alloca_size) {
+ uint8_t *aptr = (uint8_t *)alloca(alloca_size);
+
+ if (_stack_size) {
+ stack = (Variant *)aptr;
+ for (int i = 0; i < p_argcount; i++) {
+ if (!argument_types[i].has_type) {
+ memnew_placement(&stack[i], Variant(*p_args[i]));
+ continue;
+ }
+
+ if (!argument_types[i].is_type(*p_args[i], true)) {
+ r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_err.argument = i;
+ r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT;
+ return Variant();
+ }
+ if (argument_types[i].kind == GDScriptDataType::BUILTIN) {
+ Variant arg;
+ Variant::construct(argument_types[i].builtin_type, arg, &p_args[i], 1, r_err);
+ memnew_placement(&stack[i], Variant(arg));
+ } else {
+ memnew_placement(&stack[i], Variant(*p_args[i]));
+ }
+ }
+ for (int i = p_argcount; i < _stack_size; i++) {
+ memnew_placement(&stack[i], Variant);
+ }
+ } else {
+ stack = nullptr;
+ }
+
+ if (_instruction_args_size) {
+ instruction_args = (Variant **)&aptr[sizeof(Variant) * _stack_size];
+ } else {
+ instruction_args = nullptr;
+ }
+
+ } else {
+ stack = nullptr;
+ instruction_args = nullptr;
+ }
+
+ if (p_instance) {
+ if (p_instance->base_ref && static_cast<Reference *>(p_instance->owner)->is_referenced()) {
+ self = REF(static_cast<Reference *>(p_instance->owner));
+ } else {
+ self = p_instance->owner;
+ }
+ script = p_instance->script.ptr();
+ } else {
+ script = _script;
+ }
+ }
+ if (_ptrcall_args_size) {
+ call_args_ptr = (const void **)alloca(_ptrcall_args_size * sizeof(void *));
+ } else {
+ call_args_ptr = nullptr;
+ }
+
+ static_ref = script;
+
+ String err_text;
+
+#ifdef DEBUG_ENABLED
+
+ if (EngineDebugger::is_active()) {
+ GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line);
+ }
+
+#define GD_ERR_BREAK(m_cond) \
+ { \
+ if (unlikely(m_cond)) { \
+ _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition ' " _STR(m_cond) " ' is true. Breaking..:"); \
+ OPCODE_BREAK; \
+ } \
+ }
+
+#define CHECK_SPACE(m_space) \
+ GD_ERR_BREAK((ip + m_space) > _code_size)
+
+#define GET_VARIANT_PTR(m_v, m_code_ofs) \
+ Variant *m_v; \
+ m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); \
+ if (unlikely(!m_v)) \
+ OPCODE_BREAK;
+
+#else
+#define GD_ERR_BREAK(m_cond)
+#define CHECK_SPACE(m_space)
+#define GET_VARIANT_PTR(m_v, m_code_ofs) \
+ Variant *m_v; \
+ m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text);
+
+#endif
+
+#define GET_INSTRUCTION_ARG(m_v, m_idx) \
+ Variant *m_v = instruction_args[m_idx]
+
+#ifdef DEBUG_ENABLED
+
+ uint64_t function_start_time = 0;
+ uint64_t function_call_time = 0;
+
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ function_start_time = OS::get_singleton()->get_ticks_usec();
+ function_call_time = 0;
+ profile.call_count++;
+ profile.frame_call_count++;
+ }
+ bool exit_ok = false;
+ bool awaited = false;
+#endif
+
+#ifdef DEBUG_ENABLED
+ OPCODE_WHILE(ip < _code_size) {
+ int last_opcode = _code_ptr[ip];
+#else
+ OPCODE_WHILE(true) {
+#endif
+ // Load arguments for the instruction before each instruction.
+ int instr_arg_count = ((_code_ptr[ip]) & INSTR_ARGS_MASK) >> INSTR_BITS;
+ for (int i = 0; i < instr_arg_count; i++) {
+ GET_VARIANT_PTR(v, i + 1);
+ instruction_args[i] = v;
+ }
+
+ OPCODE_SWITCH(_code_ptr[ip] & INSTR_MASK) {
+ OPCODE(OPCODE_OPERATOR) {
+ CHECK_SPACE(5);
+
+ bool valid;
+ Variant::Operator op = (Variant::Operator)_code_ptr[ip + 4];
+ GD_ERR_BREAK(op >= Variant::OP_MAX);
+
+ GET_INSTRUCTION_ARG(a, 0);
+ GET_INSTRUCTION_ARG(b, 1);
+ GET_INSTRUCTION_ARG(dst, 2);
+
+#ifdef DEBUG_ENABLED
+
+ Variant ret;
+ Variant::evaluate(op, *a, *b, ret, valid);
+#else
+ Variant::evaluate(op, *a, *b, *dst, valid);
+#endif
+#ifdef DEBUG_ENABLED
+ if (!valid) {
+ if (ret.get_type() == Variant::STRING) {
+ //return a string when invalid with the error
+ err_text = ret;
+ err_text += " in operator '" + Variant::get_operator_name(op) + "'.";
+ } else {
+ err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'.";
+ }
+ OPCODE_BREAK;
+ }
+ *dst = ret;
+#endif
+ ip += 5;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_OPERATOR_VALIDATED) {
+ CHECK_SPACE(5);
+
+ int operator_idx = _code_ptr[ip + 4];
+ GD_ERR_BREAK(operator_idx < 0 || operator_idx >= _operator_funcs_count);
+ Variant::ValidatedOperatorEvaluator operator_func = _operator_funcs_ptr[operator_idx];
+
+ GET_INSTRUCTION_ARG(a, 0);
+ GET_INSTRUCTION_ARG(b, 1);
+ GET_INSTRUCTION_ARG(dst, 2);
+
+ operator_func(a, b, dst);
+
+ ip += 5;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_EXTENDS_TEST) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(a, 0);
+ GET_INSTRUCTION_ARG(b, 1);
+ GET_INSTRUCTION_ARG(dst, 2);
+
+#ifdef DEBUG_ENABLED
+ if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) {
+ err_text = "Right operand of 'is' is not a class.";
+ OPCODE_BREAK;
+ }
+#endif
+
+ bool extends_ok = false;
+ if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) {
+#ifdef DEBUG_ENABLED
+ bool was_freed;
+ Object *obj_A = a->get_validated_object_with_check(was_freed);
+
+ if (was_freed) {
+ err_text = "Left operand of 'is' is a previously freed instance.";
+ OPCODE_BREAK;
+ }
+
+ Object *obj_B = b->get_validated_object_with_check(was_freed);
+
+ if (was_freed) {
+ err_text = "Right operand of 'is' is a previously freed instance.";
+ OPCODE_BREAK;
+ }
+#else
+
+ Object *obj_A = *a;
+ Object *obj_B = *b;
+#endif // DEBUG_ENABLED
+
+ GDScript *scr_B = Object::cast_to<GDScript>(obj_B);
+
+ if (scr_B) {
+ //if B is a script, the only valid condition is that A has an instance which inherits from the script
+ //in other situation, this shoul return false.
+
+ if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) {
+ GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr());
+ //bool found=false;
+ while (cmp) {
+ if (cmp == scr_B) {
+ //inherits from script, all ok
+ extends_ok = true;
+ break;
+ }
+
+ cmp = cmp->_base;
+ }
+ }
+
+ } else {
+ GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B);
+
+#ifdef DEBUG_ENABLED
+ if (!nc) {
+ err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "').";
+ OPCODE_BREAK;
+ }
+#endif
+ extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name());
+ }
+ }
+
+ *dst = extends_ok;
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_IS_BUILTIN) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(value, 0);
+ GET_INSTRUCTION_ARG(dst, 1);
+ Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3];
+
+ GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX);
+
+ *dst = value->get_type() == var_type;
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_SET_KEYED) {
+ CHECK_SPACE(3);
+
+ GET_INSTRUCTION_ARG(dst, 0);
+ GET_INSTRUCTION_ARG(index, 1);
+ GET_INSTRUCTION_ARG(value, 2);
+
+ bool valid;
+ dst->set(*index, *value, &valid);
+
+#ifdef DEBUG_ENABLED
+ if (!valid) {
+ String v = index->operator String();
+ if (v != "") {
+ v = "'" + v + "'";
+ } else {
+ v = "of type '" + _get_var_type(index) + "'";
+ }
+ err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'";
+ OPCODE_BREAK;
+ }
+#endif
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_SET_KEYED_VALIDATED) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(dst, 0);
+ GET_INSTRUCTION_ARG(index, 1);
+ GET_INSTRUCTION_ARG(value, 2);
+
+ int index_setter = _code_ptr[ip + 4];
+ GD_ERR_BREAK(index_setter < 0 || index_setter >= _keyed_setters_count);
+ const Variant::ValidatedKeyedSetter setter = _keyed_setters_ptr[index_setter];
+
+ bool valid;
+ setter(dst, index, value, valid);
+
+#ifdef DEBUG_ENABLED
+ if (!valid) {
+ String v = index->operator String();
+ if (v != "") {
+ v = "'" + v + "'";
+ } else {
+ v = "of type '" + _get_var_type(index) + "'";
+ }
+ err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'";
+ OPCODE_BREAK;
+ }
+#endif
+ ip += 5;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_SET_INDEXED_VALIDATED) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(dst, 0);
+ GET_INSTRUCTION_ARG(index, 1);
+ GET_INSTRUCTION_ARG(value, 2);
+
+ int index_setter = _code_ptr[ip + 4];
+ GD_ERR_BREAK(index_setter < 0 || index_setter >= _indexed_setters_count);
+ const Variant::ValidatedIndexedSetter setter = _indexed_setters_ptr[index_setter];
+
+ int64_t int_index = *VariantInternal::get_int(index);
+
+ bool oob;
+ setter(dst, int_index, value, oob);
+
+#ifdef DEBUG_ENABLED
+ if (oob) {
+ String v = index->operator String();
+ if (v != "") {
+ v = "'" + v + "'";
+ } else {
+ v = "of type '" + _get_var_type(index) + "'";
+ }
+ err_text = "Out of bounds set index " + v + " (on base: '" + _get_var_type(dst) + "')";
+ OPCODE_BREAK;
+ }
+#endif
+ ip += 5;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_GET_KEYED) {
+ CHECK_SPACE(3);
+
+ GET_INSTRUCTION_ARG(src, 0);
+ GET_INSTRUCTION_ARG(index, 1);
+ GET_INSTRUCTION_ARG(dst, 2);
+
+ bool valid;
+#ifdef DEBUG_ENABLED
+ // Allow better error message in cases where src and dst are the same stack position.
+ Variant ret = src->get(*index, &valid);
+#else
+ *dst = src->get(*index, &valid);
+
+#endif
+#ifdef DEBUG_ENABLED
+ if (!valid) {
+ String v = index->operator String();
+ if (v != "") {
+ v = "'" + v + "'";
+ } else {
+ v = "of type '" + _get_var_type(index) + "'";
+ }
+ err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "').";
+ OPCODE_BREAK;
+ }
+ *dst = ret;
+#endif
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_GET_KEYED_VALIDATED) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(src, 0);
+ GET_INSTRUCTION_ARG(key, 1);
+ GET_INSTRUCTION_ARG(dst, 2);
+
+ int index_getter = _code_ptr[ip + 4];
+ GD_ERR_BREAK(index_getter < 0 || index_getter >= _keyed_getters_count);
+ const Variant::ValidatedKeyedGetter getter = _keyed_getters_ptr[index_getter];
+
+ bool valid;
+#ifdef DEBUG_ENABLED
+ // Allow better error message in cases where src and dst are the same stack position.
+ Variant ret;
+ getter(src, key, &ret, valid);
+#else
+ getter(src, key, dst, valid);
+#endif
+#ifdef DEBUG_ENABLED
+ if (!valid) {
+ String v = key->operator String();
+ if (v != "") {
+ v = "'" + v + "'";
+ } else {
+ v = "of type '" + _get_var_type(key) + "'";
+ }
+ err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "').";
+ OPCODE_BREAK;
+ }
+ *dst = ret;
+#endif
+ ip += 5;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_GET_INDEXED_VALIDATED) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(src, 0);
+ GET_INSTRUCTION_ARG(index, 1);
+ GET_INSTRUCTION_ARG(dst, 2);
+
+ int index_getter = _code_ptr[ip + 4];
+ GD_ERR_BREAK(index_getter < 0 || index_getter >= _indexed_getters_count);
+ const Variant::ValidatedIndexedGetter getter = _indexed_getters_ptr[index_getter];
+
+ int64_t int_index = *VariantInternal::get_int(index);
+
+ bool oob;
+ getter(src, int_index, dst, oob);
+
+#ifdef DEBUG_ENABLED
+ if (oob) {
+ String v = index->operator String();
+ if (v != "") {
+ v = "'" + v + "'";
+ } else {
+ v = "of type '" + _get_var_type(index) + "'";
+ }
+ err_text = "Out of bounds get index " + v + " (on base: '" + _get_var_type(src) + "')";
+ OPCODE_BREAK;
+ }
+#endif
+ ip += 5;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_SET_NAMED) {
+ CHECK_SPACE(3);
+
+ GET_INSTRUCTION_ARG(dst, 0);
+ GET_INSTRUCTION_ARG(value, 1);
+
+ int indexname = _code_ptr[ip + 3];
+
+ GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
+ const StringName *index = &_global_names_ptr[indexname];
+
+ bool valid;
+ dst->set_named(*index, *value, valid);
+
+#ifdef DEBUG_ENABLED
+ if (!valid) {
+ String err_type;
+ err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'.";
+ OPCODE_BREAK;
+ }
+#endif
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_SET_NAMED_VALIDATED) {
+ CHECK_SPACE(3);
+
+ GET_INSTRUCTION_ARG(dst, 0);
+ GET_INSTRUCTION_ARG(value, 1);
+
+ int index_setter = _code_ptr[ip + 3];
+ GD_ERR_BREAK(index_setter < 0 || index_setter >= _setters_count);
+ const Variant::ValidatedSetter setter = _setters_ptr[index_setter];
+
+ setter(dst, value);
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_GET_NAMED) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(src, 0);
+ GET_INSTRUCTION_ARG(dst, 1);
+
+ int indexname = _code_ptr[ip + 3];
+
+ GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
+ const StringName *index = &_global_names_ptr[indexname];
+
+ bool valid;
+#ifdef DEBUG_ENABLED
+ //allow better error message in cases where src and dst are the same stack position
+ Variant ret = src->get_named(*index, valid);
+
+#else
+ *dst = src->get_named(*index, valid);
+#endif
+#ifdef DEBUG_ENABLED
+ if (!valid) {
+ if (src->has_method(*index)) {
+ err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "'). Did you mean '." + index->operator String() + "()' or funcref(obj, \"" + index->operator String() + "\") ?";
+ } else {
+ err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "').";
+ }
+ OPCODE_BREAK;
+ }
+ *dst = ret;
+#endif
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_GET_NAMED_VALIDATED) {
+ CHECK_SPACE(3);
+
+ GET_INSTRUCTION_ARG(src, 0);
+ GET_INSTRUCTION_ARG(dst, 1);
+
+ int index_getter = _code_ptr[ip + 3];
+ GD_ERR_BREAK(index_getter < 0 || index_getter >= _getters_count);
+ const Variant::ValidatedGetter getter = _getters_ptr[index_getter];
+
+ getter(src, dst);
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_SET_MEMBER) {
+ CHECK_SPACE(3);
+ GET_INSTRUCTION_ARG(src, 0);
+ int indexname = _code_ptr[ip + 2];
+ GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
+ const StringName *index = &_global_names_ptr[indexname];
+
+ bool valid;
+#ifndef DEBUG_ENABLED
+ ClassDB::set_property(p_instance->owner, *index, *src, &valid);
+#else
+ bool ok = ClassDB::set_property(p_instance->owner, *index, *src, &valid);
+ if (!ok) {
+ err_text = "Internal error setting property: " + String(*index);
+ OPCODE_BREAK;
+ } else if (!valid) {
+ err_text = "Error setting property '" + String(*index) + "' with value of type " + Variant::get_type_name(src->get_type()) + ".";
+ OPCODE_BREAK;
+ }
+#endif
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_GET_MEMBER) {
+ CHECK_SPACE(3);
+ GET_INSTRUCTION_ARG(dst, 0);
+ int indexname = _code_ptr[ip + 2];
+ GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count);
+ const StringName *index = &_global_names_ptr[indexname];
+#ifndef DEBUG_ENABLED
+ ClassDB::get_property(p_instance->owner, *index, *dst);
+#else
+ bool ok = ClassDB::get_property(p_instance->owner, *index, *dst);
+ if (!ok) {
+ err_text = "Internal error getting property: " + String(*index);
+ OPCODE_BREAK;
+ }
+#endif
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ASSIGN) {
+ CHECK_SPACE(3);
+ GET_INSTRUCTION_ARG(dst, 0);
+ GET_INSTRUCTION_ARG(src, 1);
+
+ *dst = *src;
+
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ASSIGN_TRUE) {
+ CHECK_SPACE(2);
+ GET_INSTRUCTION_ARG(dst, 0);
+
+ *dst = true;
+
+ ip += 2;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ASSIGN_FALSE) {
+ CHECK_SPACE(2);
+ GET_INSTRUCTION_ARG(dst, 0);
+
+ *dst = false;
+
+ ip += 2;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) {
+ CHECK_SPACE(4);
+ GET_INSTRUCTION_ARG(dst, 0);
+ GET_INSTRUCTION_ARG(src, 1);
+
+ Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3];
+ GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX);
+
+ if (src->get_type() != var_type) {
+#ifdef DEBUG_ENABLED
+ if (Variant::can_convert_strict(src->get_type(), var_type)) {
+#endif // DEBUG_ENABLED
+ Callable::CallError ce;
+ Variant::construct(var_type, *dst, const_cast<const Variant **>(&src), 1, ce);
+ } else {
+#ifdef DEBUG_ENABLED
+ err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
+ "' to a variable of type '" + Variant::get_type_name(var_type) + "'.";
+ OPCODE_BREAK;
+ }
+ } else {
+#endif // DEBUG_ENABLED
+ *dst = *src;
+ }
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) {
+ CHECK_SPACE(4);
+ GET_INSTRUCTION_ARG(dst, 0);
+ GET_INSTRUCTION_ARG(src, 1);
+
+#ifdef DEBUG_ENABLED
+ GET_INSTRUCTION_ARG(type, 2);
+ GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *());
+ GD_ERR_BREAK(!nc);
+ if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
+ err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
+ "' to a variable of type '" + nc->get_name() + "'.";
+ OPCODE_BREAK;
+ }
+ Object *src_obj = src->operator Object *();
+
+ if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
+ err_text = "Trying to assign value of type '" + src_obj->get_class_name() +
+ "' to a variable of type '" + nc->get_name() + "'.";
+ OPCODE_BREAK;
+ }
+#endif // DEBUG_ENABLED
+ *dst = *src;
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) {
+ CHECK_SPACE(4);
+ GET_INSTRUCTION_ARG(dst, 0);
+ GET_INSTRUCTION_ARG(src, 1);
+
+#ifdef DEBUG_ENABLED
+ GET_INSTRUCTION_ARG(type, 2);
+ Script *base_type = Object::cast_to<Script>(type->operator Object *());
+
+ GD_ERR_BREAK(!base_type);
+
+ if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
+ err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'.";
+ OPCODE_BREAK;
+ }
+
+ if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) {
+ ScriptInstance *scr_inst = src->operator Object *()->get_script_instance();
+ if (!scr_inst) {
+ err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() +
+ "' to a variable of type '" + base_type->get_path().get_file() + "'.";
+ OPCODE_BREAK;
+ }
+
+ Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr();
+ bool valid = false;
+
+ while (src_type) {
+ if (src_type == base_type) {
+ valid = true;
+ break;
+ }
+ src_type = src_type->get_base_script().ptr();
+ }
+
+ if (!valid) {
+ err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() +
+ "' to a variable of type '" + base_type->get_path().get_file() + "'.";
+ OPCODE_BREAK;
+ }
+ }
+#endif // DEBUG_ENABLED
+
+ *dst = *src;
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CAST_TO_BUILTIN) {
+ CHECK_SPACE(4);
+ GET_INSTRUCTION_ARG(src, 0);
+ GET_INSTRUCTION_ARG(dst, 1);
+ Variant::Type to_type = (Variant::Type)_code_ptr[ip + 3];
+
+ GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX);
+
+ Callable::CallError err;
+ Variant::construct(to_type, *dst, (const Variant **)&src, 1, err);
+
+#ifdef DEBUG_ENABLED
+ if (err.error != Callable::CallError::CALL_OK) {
+ err_text = "Invalid cast: could not convert value to '" + Variant::get_type_name(to_type) + "'.";
+ OPCODE_BREAK;
+ }
+#endif
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CAST_TO_NATIVE) {
+ CHECK_SPACE(4);
+ GET_INSTRUCTION_ARG(src, 0);
+ GET_INSTRUCTION_ARG(dst, 1);
+ GET_INSTRUCTION_ARG(to_type, 2);
+
+ GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *());
+ GD_ERR_BREAK(!nc);
+
+#ifdef DEBUG_ENABLED
+ if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
+ err_text = "Invalid cast: can't convert a non-object value to an object type.";
+ OPCODE_BREAK;
+ }
+#endif
+ Object *src_obj = src->operator Object *();
+
+ if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
+ *dst = Variant(); // invalid cast, assign NULL
+ } else {
+ *dst = *src;
+ }
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CAST_TO_SCRIPT) {
+ CHECK_SPACE(4);
+ GET_INSTRUCTION_ARG(src, 0);
+ GET_INSTRUCTION_ARG(dst, 1);
+ GET_INSTRUCTION_ARG(to_type, 2);
+
+ Script *base_type = Object::cast_to<Script>(to_type->operator Object *());
+
+ GD_ERR_BREAK(!base_type);
+
+#ifdef DEBUG_ENABLED
+ if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) {
+ err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'.";
+ OPCODE_BREAK;
+ }
+#endif
+
+ bool valid = false;
+
+ if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) {
+ ScriptInstance *scr_inst = src->operator Object *()->get_script_instance();
+
+ if (scr_inst) {
+ Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr();
+
+ while (src_type) {
+ if (src_type == base_type) {
+ valid = true;
+ break;
+ }
+ src_type = src_type->get_base_script().ptr();
+ }
+ }
+ }
+
+ if (valid) {
+ *dst = *src; // Valid cast, copy the source object
+ } else {
+ *dst = Variant(); // invalid cast, assign NULL
+ }
+
+ ip += 4;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CONSTRUCT) {
+ CHECK_SPACE(2 + instr_arg_count);
+
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+
+ Variant::Type t = Variant::Type(_code_ptr[ip + 2]);
+ Variant **argptrs = instruction_args;
+
+ GET_INSTRUCTION_ARG(dst, argc);
+
+ Callable::CallError err;
+ Variant::construct(t, *dst, (const Variant **)argptrs, argc, err);
+
+#ifdef DEBUG_ENABLED
+ if (err.error != Callable::CallError::CALL_OK) {
+ err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs);
+ OPCODE_BREAK;
+ }
+#endif
+
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CONSTRUCT_VALIDATED) {
+ CHECK_SPACE(2 + instr_arg_count);
+
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+
+ int constructor_idx = _code_ptr[ip + 2];
+ GD_ERR_BREAK(constructor_idx < 0 || constructor_idx >= _constructors_count);
+ Variant::ValidatedConstructor constructor = _constructors_ptr[constructor_idx];
+
+ Variant **argptrs = instruction_args;
+
+ GET_INSTRUCTION_ARG(dst, argc);
+
+ constructor(*dst, (const Variant **)argptrs);
+
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CONSTRUCT_ARRAY) {
+ CHECK_SPACE(1 + instr_arg_count);
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+ Array array;
+ array.resize(argc);
+
+ for (int i = 0; i < argc; i++) {
+ array[i] = *(instruction_args[i]);
+ }
+
+ GET_INSTRUCTION_ARG(dst, argc);
+
+ *dst = array;
+
+ ip += 2;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CONSTRUCT_DICTIONARY) {
+ CHECK_SPACE(2 + instr_arg_count);
+
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+ Dictionary dict;
+
+ for (int i = 0; i < argc; i++) {
+ GET_INSTRUCTION_ARG(k, i * 2 + 0);
+ GET_INSTRUCTION_ARG(v, i * 2 + 1);
+ dict[*k] = *v;
+ }
+
+ GET_INSTRUCTION_ARG(dst, argc * 2);
+
+ *dst = dict;
+
+ ip += 2;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CALL_ASYNC)
+ OPCODE(OPCODE_CALL_RETURN)
+ OPCODE(OPCODE_CALL) {
+ CHECK_SPACE(3 + instr_arg_count);
+ bool call_ret = (_code_ptr[ip] & INSTR_MASK) != OPCODE_CALL;
+#ifdef DEBUG_ENABLED
+ bool call_async = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_ASYNC;
+#endif
+
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+ GD_ERR_BREAK(argc < 0);
+
+ int methodname_idx = _code_ptr[ip + 2];
+ GD_ERR_BREAK(methodname_idx < 0 || methodname_idx >= _global_names_count);
+ const StringName *methodname = &_global_names_ptr[methodname_idx];
+
+ GET_INSTRUCTION_ARG(base, argc);
+ Variant **argptrs = instruction_args;
+
+#ifdef DEBUG_ENABLED
+ uint64_t call_time = 0;
+
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ call_time = OS::get_singleton()->get_ticks_usec();
+ }
+
+#endif
+ Callable::CallError err;
+ if (call_ret) {
+ GET_INSTRUCTION_ARG(ret, argc + 1);
+ base->call(*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 && obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) {
+ err_text = R"(Trying to call an async function without "await".)";
+ OPCODE_BREAK;
+ }
+ }
+#endif
+ } else {
+ Variant ret;
+ base->call(*methodname, (const Variant **)argptrs, argc, ret, err);
+ }
+#ifdef DEBUG_ENABLED
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+ }
+
+ if (err.error != Callable::CallError::CALL_OK) {
+ String methodstr = *methodname;
+ String basestr = _get_var_type(base);
+
+ if (methodstr == "call") {
+ if (argc >= 1) {
+ methodstr = String(*argptrs[0]) + " (via call)";
+ if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
+ err.argument += 1;
+ }
+ }
+ } else if (methodstr == "free") {
+ if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
+ if (base->is_ref()) {
+ err_text = "Attempted to free a reference.";
+ OPCODE_BREAK;
+ } else if (base->get_type() == Variant::OBJECT) {
+ err_text = "Attempted to free a locked object (calling or emitting).";
+ OPCODE_BREAK;
+ }
+ }
+ } else if (methodstr == "call_recursive" && basestr == "TreeItem") {
+ if (argc >= 1) {
+ methodstr = String(*argptrs[0]) + " (via TreeItem.call_recursive)";
+ if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
+ err.argument += 1;
+ }
+ }
+ }
+ err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs);
+ OPCODE_BREAK;
+ }
+#endif
+
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CALL_METHOD_BIND)
+ OPCODE(OPCODE_CALL_METHOD_BIND_RET) {
+ CHECK_SPACE(3 + instr_arg_count);
+ bool call_ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_METHOD_BIND_RET;
+
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+ GD_ERR_BREAK(argc < 0);
+ GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count);
+ MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
+
+ GET_INSTRUCTION_ARG(base, argc);
+
+#ifdef DEBUG_ENABLED
+ bool freed = false;
+ Object *base_obj = base->get_validated_object_with_check(freed);
+ if (freed) {
+ err_text = "Trying to call a function on a previously freed instance.";
+ OPCODE_BREAK;
+ } else if (!base_obj) {
+ err_text = "Trying to call a function on a null value.";
+ OPCODE_BREAK;
+ }
+#else
+ Object *base_obj = base->operator Object *();
+#endif
+ Variant **argptrs = instruction_args;
+
+#ifdef DEBUG_ENABLED
+ uint64_t call_time = 0;
+
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ call_time = OS::get_singleton()->get_ticks_usec();
+ }
+#endif
+
+ Callable::CallError err;
+ if (call_ret) {
+ GET_INSTRUCTION_ARG(ret, argc + 1);
+ *ret = method->call(base_obj, (const Variant **)argptrs, argc, err);
+ } else {
+ method->call(base_obj, (const Variant **)argptrs, argc, err);
+ }
+
+#ifdef DEBUG_ENABLED
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+ }
+
+ if (err.error != Callable::CallError::CALL_OK) {
+ String methodstr = method->get_name();
+ String basestr = _get_var_type(base);
+
+ if (methodstr == "call") {
+ if (argc >= 1) {
+ methodstr = String(*argptrs[0]) + " (via call)";
+ if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
+ err.argument += 1;
+ }
+ }
+ } else if (methodstr == "free") {
+ if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
+ if (base->is_ref()) {
+ err_text = "Attempted to free a reference.";
+ OPCODE_BREAK;
+ } else if (base->get_type() == Variant::OBJECT) {
+ err_text = "Attempted to free a locked object (calling or emitting).";
+ OPCODE_BREAK;
+ }
+ }
+ }
+ err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs);
+ OPCODE_BREAK;
+ }
+#endif
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+#ifdef DEBUG_ENABLED
+#define OPCODE_CALL_PTR(m_type) \
+ OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \
+ CHECK_SPACE(3 + instr_arg_count); \
+ ip += instr_arg_count; \
+ int argc = _code_ptr[ip + 1]; \
+ GD_ERR_BREAK(argc < 0); \
+ GET_INSTRUCTION_ARG(base, argc); \
+ GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); \
+ MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \
+ bool freed = false; \
+ Object *base_obj = base->get_validated_object_with_check(freed); \
+ if (freed) { \
+ err_text = "Trying to call a function on a previously freed instance."; \
+ OPCODE_BREAK; \
+ } else if (!base_obj) { \
+ err_text = "Trying to call a function on a null value."; \
+ OPCODE_BREAK; \
+ } \
+ const void **argptrs = call_args_ptr; \
+ for (int i = 0; i < argc; i++) { \
+ GET_INSTRUCTION_ARG(v, i); \
+ argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \
+ } \
+ uint64_t call_time = 0; \
+ if (GDScriptLanguage::get_singleton()->profiling) { \
+ call_time = OS::get_singleton()->get_ticks_usec(); \
+ } \
+ GET_INSTRUCTION_ARG(ret, argc + 1); \
+ VariantInternal::initialize(ret, Variant::m_type); \
+ void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \
+ method->ptrcall(base_obj, argptrs, ret_opaque); \
+ if (GDScriptLanguage::get_singleton()->profiling) { \
+ function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; \
+ } \
+ ip += 3; \
+ } \
+ DISPATCH_OPCODE
+#else
+#define OPCODE_CALL_PTR(m_type) \
+ OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \
+ CHECK_SPACE(3 + instr_arg_count); \
+ int argc = _code_ptr[ip + 1]; \
+ GET_INSTRUCTION_ARG(base, argc); \
+ MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \
+ Object *base_obj = *VariantInternal::get_object(base); \
+ const void **argptrs = call_args_ptr; \
+ for (int i = 0; i < argc; i++) { \
+ GET_INSTRUCTION_ARG(v, i); \
+ argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \
+ } \
+ GET_INSTRUCTION_ARG(ret, argc + 1); \
+ VariantInternal::initialize(ret, Variant::m_type); \
+ void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \
+ method->ptrcall(base_obj, argptrs, ret_opaque); \
+ ip += 3; \
+ } \
+ DISPATCH_OPCODE
+#endif
+
+ OPCODE_CALL_PTR(BOOL);
+ OPCODE_CALL_PTR(INT);
+ OPCODE_CALL_PTR(FLOAT);
+ OPCODE_CALL_PTR(STRING);
+ OPCODE_CALL_PTR(VECTOR2);
+ OPCODE_CALL_PTR(VECTOR2I);
+ OPCODE_CALL_PTR(RECT2);
+ OPCODE_CALL_PTR(RECT2I);
+ OPCODE_CALL_PTR(VECTOR3);
+ OPCODE_CALL_PTR(VECTOR3I);
+ OPCODE_CALL_PTR(TRANSFORM2D);
+ OPCODE_CALL_PTR(PLANE);
+ OPCODE_CALL_PTR(QUAT);
+ OPCODE_CALL_PTR(AABB);
+ OPCODE_CALL_PTR(BASIS);
+ OPCODE_CALL_PTR(TRANSFORM);
+ OPCODE_CALL_PTR(COLOR);
+ OPCODE_CALL_PTR(STRING_NAME);
+ OPCODE_CALL_PTR(NODE_PATH);
+ OPCODE_CALL_PTR(RID);
+ OPCODE_CALL_PTR(CALLABLE);
+ OPCODE_CALL_PTR(SIGNAL);
+ OPCODE_CALL_PTR(DICTIONARY);
+ OPCODE_CALL_PTR(ARRAY);
+ OPCODE_CALL_PTR(PACKED_BYTE_ARRAY);
+ OPCODE_CALL_PTR(PACKED_INT32_ARRAY);
+ OPCODE_CALL_PTR(PACKED_INT64_ARRAY);
+ OPCODE_CALL_PTR(PACKED_FLOAT32_ARRAY);
+ OPCODE_CALL_PTR(PACKED_FLOAT64_ARRAY);
+ OPCODE_CALL_PTR(PACKED_STRING_ARRAY);
+ OPCODE_CALL_PTR(PACKED_VECTOR2_ARRAY);
+ OPCODE_CALL_PTR(PACKED_VECTOR3_ARRAY);
+ OPCODE_CALL_PTR(PACKED_COLOR_ARRAY);
+ OPCODE(OPCODE_CALL_PTRCALL_OBJECT) {
+ CHECK_SPACE(3 + instr_arg_count);
+
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+ GD_ERR_BREAK(argc < 0);
+
+ GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count);
+ MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
+
+ GET_INSTRUCTION_ARG(base, argc);
+#ifdef DEBUG_ENABLED
+ bool freed = false;
+ Object *base_obj = base->get_validated_object_with_check(freed);
+ if (freed) {
+ err_text = "Trying to call a function on a previously freed instance.";
+ OPCODE_BREAK;
+ } else if (!base_obj) {
+ err_text = "Trying to call a function on a null value.";
+ OPCODE_BREAK;
+ }
+#else
+ Object *base_obj = *VariantInternal::get_object(base);
+#endif
+
+ const void **argptrs = call_args_ptr;
+
+ for (int i = 0; i < argc; i++) {
+ GET_INSTRUCTION_ARG(v, i);
+ argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
+ }
+#ifdef DEBUG_ENABLED
+ uint64_t call_time = 0;
+
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ call_time = OS::get_singleton()->get_ticks_usec();
+ }
+#endif
+
+ GET_INSTRUCTION_ARG(ret, argc + 1);
+ VariantInternal::initialize(ret, Variant::OBJECT);
+ Object **ret_opaque = VariantInternal::get_object(ret);
+ method->ptrcall(base_obj, argptrs, ret_opaque);
+ VariantInternal::object_assign(ret, *ret_opaque); // Set so ID is correct too.
+
+#ifdef DEBUG_ENABLED
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+ }
+#endif
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+ OPCODE(OPCODE_CALL_PTRCALL_NO_RETURN) {
+ CHECK_SPACE(3 + instr_arg_count);
+
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+ GD_ERR_BREAK(argc < 0);
+
+ GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count);
+ MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
+
+ GET_INSTRUCTION_ARG(base, argc);
+#ifdef DEBUG_ENABLED
+ bool freed = false;
+ Object *base_obj = base->get_validated_object_with_check(freed);
+ if (freed) {
+ err_text = "Trying to call a function on a previously freed instance.";
+ OPCODE_BREAK;
+ } else if (!base_obj) {
+ err_text = "Trying to call a function on a null value.";
+ OPCODE_BREAK;
+ }
+#else
+ Object *base_obj = *VariantInternal::get_object(base);
+#endif
+ const void **argptrs = call_args_ptr;
+
+ for (int i = 0; i < argc; i++) {
+ GET_INSTRUCTION_ARG(v, i);
+ argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
+ }
+#ifdef DEBUG_ENABLED
+ uint64_t call_time = 0;
+
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ call_time = OS::get_singleton()->get_ticks_usec();
+ }
+#endif
+
+ GET_INSTRUCTION_ARG(ret, argc + 1);
+ VariantInternal::initialize(ret, Variant::NIL);
+ method->ptrcall(base_obj, argptrs, nullptr);
+
+#ifdef DEBUG_ENABLED
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+ }
+#endif
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CALL_BUILTIN_TYPE_VALIDATED) {
+ CHECK_SPACE(3 + instr_arg_count);
+
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+ GD_ERR_BREAK(argc < 0);
+
+ GET_INSTRUCTION_ARG(base, argc);
+
+ GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _builtin_methods_count);
+ Variant::ValidatedBuiltInMethod method = _builtin_methods_ptr[_code_ptr[ip + 2]];
+ Variant **argptrs = instruction_args;
+
+#ifdef DEBUG_ENABLED
+ uint64_t call_time = 0;
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ call_time = OS::get_singleton()->get_ticks_usec();
+ }
+#endif
+
+ GET_INSTRUCTION_ARG(ret, argc + 1);
+ method(base, (const Variant **)argptrs, argc, ret);
+
+#ifdef DEBUG_ENABLED
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+ }
+#endif
+
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CALL_BUILT_IN) {
+ CHECK_SPACE(3 + instr_arg_count);
+
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+ GD_ERR_BREAK(argc < 0);
+
+ GDScriptFunctions::Function func = GDScriptFunctions::Function(_code_ptr[ip + 2]);
+ Variant **argptrs = instruction_args;
+
+ GET_INSTRUCTION_ARG(dst, argc);
+
+ Callable::CallError err;
+ GDScriptFunctions::call(func, (const Variant **)argptrs, argc, *dst, err);
+
+#ifdef DEBUG_ENABLED
+ if (err.error != Callable::CallError::CALL_OK) {
+ String methodstr = GDScriptFunctions::get_func_name(func);
+ if (dst->get_type() == Variant::STRING) {
+ //call provided error string
+ err_text = "Error calling built-in function '" + methodstr + "': " + String(*dst);
+ } else {
+ err_text = _get_call_error(err, "built-in function '" + methodstr + "'", (const Variant **)argptrs);
+ }
+ OPCODE_BREAK;
+ }
+#endif
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_CALL_SELF_BASE) {
+ CHECK_SPACE(3 + instr_arg_count);
+
+ ip += instr_arg_count;
+
+ int self_fun = _code_ptr[ip + 1];
+#ifdef DEBUG_ENABLED
+ if (self_fun < 0 || self_fun >= _global_names_count) {
+ err_text = "compiler bug, function name not found";
+ OPCODE_BREAK;
+ }
+#endif
+ const StringName *methodname = &_global_names_ptr[self_fun];
+
+ int argc = _code_ptr[ip + 2];
+ GD_ERR_BREAK(argc < 0);
+
+ Variant **argptrs = instruction_args;
+
+ GET_INSTRUCTION_ARG(dst, argc);
+
+ const GDScript *gds = _script;
+
+ const Map<StringName, GDScriptFunction *>::Element *E = nullptr;
+ while (gds->base.ptr()) {
+ gds = gds->base.ptr();
+ E = gds->member_functions.find(*methodname);
+ if (E) {
+ break;
+ }
+ }
+
+ Callable::CallError err;
+
+ if (E) {
+ *dst = E->get()->call(p_instance, (const Variant **)argptrs, argc, err);
+ } else if (gds->native.ptr()) {
+ if (*methodname != GDScriptLanguage::get_singleton()->strings._init) {
+ MethodBind *mb = ClassDB::get_method(gds->native->get_name(), *methodname);
+ if (!mb) {
+ err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
+ } else {
+ *dst = mb->call(p_instance->owner, (const Variant **)argptrs, argc, err);
+ }
+ } else {
+ err.error = Callable::CallError::CALL_OK;
+ }
+ } else {
+ if (*methodname != GDScriptLanguage::get_singleton()->strings._init) {
+ err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
+ } else {
+ err.error = Callable::CallError::CALL_OK;
+ }
+ }
+
+ if (err.error != Callable::CallError::CALL_OK) {
+ String methodstr = *methodname;
+ err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs);
+
+ OPCODE_BREAK;
+ }
+
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_AWAIT) {
+ CHECK_SPACE(2);
+
+ // Do the oneshot connect.
+ GET_INSTRUCTION_ARG(argobj, 0);
+
+ Signal sig;
+ bool is_signal = true;
+
+ {
+ Variant result = *argobj;
+
+ if (argobj->get_type() == Variant::OBJECT) {
+ bool was_freed = false;
+ Object *obj = argobj->get_validated_object_with_check(was_freed);
+
+ if (was_freed) {
+ err_text = "Trying to await on a freed object.";
+ 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);
+ }
+ }
+ }
+
+ 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 (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]));
+ }
+ gdfs->state.stack_size = _stack_size;
+ gdfs->state.self = self;
+ gdfs->state.alloca_size = alloca_size;
+ gdfs->state.ip = ip + 2;
+ 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;
+
+ 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: " + sig.get_name() + " during await.";
+ OPCODE_BREAK;
+ }
+
+#ifdef DEBUG_ENABLED
+ exit_ok = true;
+ awaited = true;
+#endif
+ OPCODE_BREAK;
+ }
+ }
+ DISPATCH_OPCODE; // Needed for synchronous calls (when result is immediately available).
+
+ OPCODE(OPCODE_AWAIT_RESUME) {
+ CHECK_SPACE(2);
+#ifdef DEBUG_ENABLED
+ if (!p_state) {
+ err_text = ("Invalid Resume (bug?)");
+ OPCODE_BREAK;
+ }
+#endif
+ GET_INSTRUCTION_ARG(result, 0);
+ *result = p_state->result;
+ ip += 2;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_JUMP) {
+ CHECK_SPACE(2);
+ int to = _code_ptr[ip + 1];
+
+ GD_ERR_BREAK(to < 0 || to > _code_size);
+ ip = to;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_JUMP_IF) {
+ CHECK_SPACE(3);
+
+ GET_INSTRUCTION_ARG(test, 0);
+
+ bool result = test->booleanize();
+
+ if (result) {
+ int to = _code_ptr[ip + 2];
+ GD_ERR_BREAK(to < 0 || to > _code_size);
+ ip = to;
+ } else {
+ ip += 3;
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_JUMP_IF_NOT) {
+ CHECK_SPACE(3);
+
+ GET_INSTRUCTION_ARG(test, 0);
+
+ bool result = test->booleanize();
+
+ if (!result) {
+ int to = _code_ptr[ip + 2];
+ GD_ERR_BREAK(to < 0 || to > _code_size);
+ ip = to;
+ } else {
+ ip += 3;
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_JUMP_TO_DEF_ARGUMENT) {
+ CHECK_SPACE(2);
+ ip = _default_arg_ptr[defarg];
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_RETURN) {
+ CHECK_SPACE(2);
+ GET_INSTRUCTION_ARG(r, 0);
+ retvalue = *r;
+#ifdef DEBUG_ENABLED
+ exit_ok = true;
+#endif
+ OPCODE_BREAK;
+ }
+
+ OPCODE(OPCODE_ITERATE_BEGIN) {
+ CHECK_SPACE(8); // Space for this and a regular iterate.
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ bool valid;
+ if (!container->iter_init(*counter, valid)) {
+#ifdef DEBUG_ENABLED
+ if (!valid) {
+ err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "'.";
+ OPCODE_BREAK;
+ }
+#endif
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+
+ *iterator = container->iter_get(*counter, valid);
+#ifdef DEBUG_ENABLED
+ if (!valid) {
+ err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "'.";
+ OPCODE_BREAK;
+ }
+#endif
+ ip += 5; // Skip regular iterate which is always next.
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_BEGIN_INT) {
+ CHECK_SPACE(8); // Check space for iterate instruction too.
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ int64_t size = *VariantInternal::get_int(container);
+
+ VariantInternal::initialize(counter, Variant::INT);
+ *VariantInternal::get_int(counter) = 0;
+
+ if (size > 0) {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ VariantInternal::initialize(iterator, Variant::INT);
+ *VariantInternal::get_int(iterator) = 0;
+
+ // Skip regular iterate.
+ ip += 5;
+ } else {
+ // Jump to end of loop.
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_BEGIN_FLOAT) {
+ CHECK_SPACE(8); // Check space for iterate instruction too.
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ double size = *VariantInternal::get_float(container);
+
+ VariantInternal::initialize(counter, Variant::FLOAT);
+ *VariantInternal::get_float(counter) = 0.0;
+
+ if (size > 0) {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ VariantInternal::initialize(iterator, Variant::FLOAT);
+ *VariantInternal::get_float(iterator) = 0;
+
+ // Skip regular iterate.
+ ip += 5;
+ } else {
+ // Jump to end of loop.
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_BEGIN_VECTOR2) {
+ CHECK_SPACE(8); // Check space for iterate instruction too.
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ Vector2 *bounds = VariantInternal::get_vector2(container);
+
+ VariantInternal::initialize(counter, Variant::FLOAT);
+ *VariantInternal::get_float(counter) = bounds->x;
+
+ if (bounds->x < bounds->y) {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ VariantInternal::initialize(iterator, Variant::FLOAT);
+ *VariantInternal::get_float(iterator) = bounds->x;
+
+ // Skip regular iterate.
+ ip += 5;
+ } else {
+ // Jump to end of loop.
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_BEGIN_VECTOR2I) {
+ CHECK_SPACE(8); // Check space for iterate instruction too.
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ Vector2i *bounds = VariantInternal::get_vector2i(container);
+
+ VariantInternal::initialize(counter, Variant::FLOAT);
+ *VariantInternal::get_int(counter) = bounds->x;
+
+ if (bounds->x < bounds->y) {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ VariantInternal::initialize(iterator, Variant::INT);
+ *VariantInternal::get_int(iterator) = bounds->x;
+
+ // Skip regular iterate.
+ ip += 5;
+ } else {
+ // Jump to end of loop.
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_BEGIN_VECTOR3) {
+ CHECK_SPACE(8); // Check space for iterate instruction too.
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ Vector3 *bounds = VariantInternal::get_vector3(container);
+ double from = bounds->x;
+ double to = bounds->y;
+ double step = bounds->z;
+
+ VariantInternal::initialize(counter, Variant::FLOAT);
+ *VariantInternal::get_float(counter) = from;
+
+ bool do_continue = from == to ? false : (from < to ? step > 0 : step < 0);
+
+ if (do_continue) {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ VariantInternal::initialize(iterator, Variant::FLOAT);
+ *VariantInternal::get_float(iterator) = from;
+
+ // Skip regular iterate.
+ ip += 5;
+ } else {
+ // Jump to end of loop.
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_BEGIN_VECTOR3I) {
+ CHECK_SPACE(8); // Check space for iterate instruction too.
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ Vector3i *bounds = VariantInternal::get_vector3i(container);
+ int64_t from = bounds->x;
+ int64_t to = bounds->y;
+ int64_t step = bounds->z;
+
+ VariantInternal::initialize(counter, Variant::INT);
+ *VariantInternal::get_int(counter) = from;
+
+ bool do_continue = from == to ? false : (from < to ? step > 0 : step < 0);
+
+ if (do_continue) {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ VariantInternal::initialize(iterator, Variant::INT);
+ *VariantInternal::get_int(iterator) = from;
+
+ // Skip regular iterate.
+ ip += 5;
+ } else {
+ // Jump to end of loop.
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_BEGIN_STRING) {
+ CHECK_SPACE(8); // Check space for iterate instruction too.
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ String *str = VariantInternal::get_string(container);
+
+ VariantInternal::initialize(counter, Variant::INT);
+ *VariantInternal::get_int(counter) = 0;
+
+ if (!str->empty()) {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ VariantInternal::initialize(iterator, Variant::STRING);
+ *VariantInternal::get_string(iterator) = str->substr(0, 1);
+
+ // Skip regular iterate.
+ ip += 5;
+ } else {
+ // Jump to end of loop.
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_BEGIN_DICTIONARY) {
+ CHECK_SPACE(8); // Check space for iterate instruction too.
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ Dictionary *dict = VariantInternal::get_dictionary(container);
+ const Variant *next = dict->next(nullptr);
+ *counter = *next;
+
+ if (!dict->empty()) {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *iterator = *next;
+
+ // Skip regular iterate.
+ ip += 5;
+ } else {
+ // Jump to end of loop.
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_BEGIN_ARRAY) {
+ CHECK_SPACE(8); // Check space for iterate instruction too.
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ Array *array = VariantInternal::get_array(container);
+
+ VariantInternal::initialize(counter, Variant::INT);
+ *VariantInternal::get_int(counter) = 0;
+
+ if (!array->empty()) {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *iterator = array->get(0);
+
+ // Skip regular iterate.
+ ip += 5;
+ } else {
+ // Jump to end of loop.
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ }
+ }
+ DISPATCH_OPCODE;
+
+#define OPCODE_ITERATE_BEGIN_PACKED_ARRAY(m_var_type, m_elem_type, m_get_func, m_var_ret_type, m_ret_type, m_ret_get_func) \
+ OPCODE(OPCODE_ITERATE_BEGIN_PACKED_##m_var_type##_ARRAY) { \
+ CHECK_SPACE(8); \
+ GET_INSTRUCTION_ARG(counter, 0); \
+ GET_INSTRUCTION_ARG(container, 1); \
+ Vector<m_elem_type> *array = VariantInternal::m_get_func(container); \
+ VariantInternal::initialize(counter, Variant::INT); \
+ *VariantInternal::get_int(counter) = 0; \
+ if (!array->empty()) { \
+ GET_INSTRUCTION_ARG(iterator, 2); \
+ VariantInternal::initialize(iterator, Variant::m_var_ret_type); \
+ m_ret_type *it = VariantInternal::m_ret_get_func(iterator); \
+ *it = array->get(0); \
+ ip += 5; \
+ } else { \
+ int jumpto = _code_ptr[ip + 4]; \
+ GD_ERR_BREAK(jumpto<0 || jumpto> _code_size); \
+ ip = jumpto; \
+ } \
+ } \
+ DISPATCH_OPCODE
+
+ OPCODE_ITERATE_BEGIN_PACKED_ARRAY(BYTE, uint8_t, get_byte_array, INT, int64_t, get_int);
+ OPCODE_ITERATE_BEGIN_PACKED_ARRAY(INT32, int32_t, get_int32_array, INT, int64_t, get_int);
+ OPCODE_ITERATE_BEGIN_PACKED_ARRAY(INT64, int64_t, get_int64_array, INT, int64_t, get_int);
+ OPCODE_ITERATE_BEGIN_PACKED_ARRAY(FLOAT32, float, get_float32_array, FLOAT, double, get_float);
+ OPCODE_ITERATE_BEGIN_PACKED_ARRAY(FLOAT64, double, get_float64_array, FLOAT, double, get_float);
+ OPCODE_ITERATE_BEGIN_PACKED_ARRAY(STRING, String, get_string_array, STRING, String, get_string);
+ OPCODE_ITERATE_BEGIN_PACKED_ARRAY(VECTOR2, Vector2, get_vector2_array, VECTOR2, Vector2, get_vector2);
+ OPCODE_ITERATE_BEGIN_PACKED_ARRAY(VECTOR3, Vector3, get_vector3_array, VECTOR3, Vector3, get_vector3);
+ OPCODE_ITERATE_BEGIN_PACKED_ARRAY(COLOR, Color, get_color_array, COLOR, Color, get_color);
+
+ OPCODE(OPCODE_ITERATE_BEGIN_OBJECT) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+#ifdef DEBUG_ENABLED
+ bool freed = false;
+ Object *obj = container->get_validated_object_with_check(freed);
+ if (freed) {
+ err_text = "Trying to iterate on a previously freed object.";
+ OPCODE_BREAK;
+ } else if (!obj) {
+ err_text = "Trying to iterate on a null value.";
+ OPCODE_BREAK;
+ }
+#else
+ Object *obj = *VariantInternal::get_object(container);
+#endif
+ Array ref;
+ ref.push_back(*counter);
+ Variant vref;
+ VariantInternal::initialize(&vref, Variant::ARRAY);
+ *VariantInternal::get_array(&vref) = ref;
+
+ Variant **args = instruction_args; // Overriding an instruction argument, but we don't need access to that anymore.
+ args[0] = &vref;
+
+ Callable::CallError ce;
+ Variant has_next = obj->call(CoreStringNames::get_singleton()->_iter_init, (const Variant **)args, 1, ce);
+
+#ifdef DEBUG_ENABLED
+ if (ce.error != Callable::CallError::CALL_OK) {
+ err_text = vformat(R"(There was an error calling "_iter_next" on iterator object of type %s.)", *container);
+ OPCODE_BREAK;
+ }
+#endif
+ if (!has_next.booleanize()) {
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *iterator = obj->call(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce);
+#ifdef DEBUG_ENABLED
+ if (ce.error != Callable::CallError::CALL_OK) {
+ err_text = vformat(R"(There was an error calling "_iter_get" on iterator object of type %s.)", *container);
+ OPCODE_BREAK;
+ }
+#endif
+
+ ip += 5; // Loop again.
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ bool valid;
+ if (!container->iter_next(*counter, valid)) {
+#ifdef DEBUG_ENABLED
+ if (!valid) {
+ err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?).";
+ OPCODE_BREAK;
+ }
+#endif
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+
+ *iterator = container->iter_get(*counter, valid);
+#ifdef DEBUG_ENABLED
+ if (!valid) {
+ err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?).";
+ OPCODE_BREAK;
+ }
+#endif
+ ip += 5; //loop again
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_INT) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ int64_t size = *VariantInternal::get_int(container);
+ int64_t *count = VariantInternal::get_int(counter);
+
+ (*count)++;
+
+ if (*count >= size) {
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *VariantInternal::get_int(iterator) = *count;
+
+ ip += 5; // Loop again.
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_FLOAT) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ double size = *VariantInternal::get_float(container);
+ double *count = VariantInternal::get_float(counter);
+
+ (*count)++;
+
+ if (*count >= size) {
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *VariantInternal::get_float(iterator) = *count;
+
+ ip += 5; // Loop again.
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_VECTOR2) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ const Vector2 *bounds = VariantInternal::get_vector2((const Variant *)container);
+ double *count = VariantInternal::get_float(counter);
+
+ (*count)++;
+
+ if (*count >= bounds->y) {
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *VariantInternal::get_float(iterator) = *count;
+
+ ip += 5; // Loop again.
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_VECTOR2I) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ const Vector2i *bounds = VariantInternal::get_vector2i((const Variant *)container);
+ int64_t *count = VariantInternal::get_int(counter);
+
+ (*count)++;
+
+ if (*count >= bounds->y) {
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *VariantInternal::get_int(iterator) = *count;
+
+ ip += 5; // Loop again.
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_VECTOR3) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ const Vector3 *bounds = VariantInternal::get_vector3((const Variant *)container);
+ double *count = VariantInternal::get_float(counter);
+
+ *count += bounds->z;
+
+ if ((bounds->z < 0 && *count <= bounds->y) || (bounds->z > 0 && *count >= bounds->y)) {
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *VariantInternal::get_float(iterator) = *count;
+
+ ip += 5; // Loop again.
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_VECTOR3I) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ const Vector3i *bounds = VariantInternal::get_vector3i((const Variant *)container);
+ int64_t *count = VariantInternal::get_int(counter);
+
+ *count += bounds->z;
+
+ if ((bounds->z < 0 && *count <= bounds->y) || (bounds->z > 0 && *count >= bounds->y)) {
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *VariantInternal::get_int(iterator) = *count;
+
+ ip += 5; // Loop again.
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_STRING) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ const String *str = VariantInternal::get_string((const Variant *)container);
+ int64_t *idx = VariantInternal::get_int(counter);
+ (*idx)++;
+
+ if (*idx >= str->length()) {
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *VariantInternal::get_string(iterator) = str->substr(*idx, 1);
+
+ ip += 5; // Loop again.
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_DICTIONARY) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ const Dictionary *dict = VariantInternal::get_dictionary((const Variant *)container);
+ const Variant *next = dict->next(counter);
+
+ if (!next) {
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *counter = *next;
+ *iterator = *next;
+
+ ip += 5; // Loop again.
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ITERATE_ARRAY) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+ const Array *array = VariantInternal::get_array((const Variant *)container);
+ int64_t *idx = VariantInternal::get_int(counter);
+ (*idx)++;
+
+ if (*idx >= array->size()) {
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *iterator = array->get(*idx);
+
+ ip += 5; // Loop again.
+ }
+ }
+ DISPATCH_OPCODE;
+
+#define OPCODE_ITERATE_PACKED_ARRAY(m_var_type, m_elem_type, m_get_func, m_ret_get_func) \
+ OPCODE(OPCODE_ITERATE_PACKED_##m_var_type##_ARRAY) { \
+ CHECK_SPACE(4); \
+ GET_INSTRUCTION_ARG(counter, 0); \
+ GET_INSTRUCTION_ARG(container, 1); \
+ const Vector<m_elem_type> *array = VariantInternal::m_get_func((const Variant *)container); \
+ int64_t *idx = VariantInternal::get_int(counter); \
+ (*idx)++; \
+ if (*idx >= array->size()) { \
+ int jumpto = _code_ptr[ip + 4]; \
+ GD_ERR_BREAK(jumpto<0 || jumpto> _code_size); \
+ ip = jumpto; \
+ } else { \
+ GET_INSTRUCTION_ARG(iterator, 2); \
+ *VariantInternal::m_ret_get_func(iterator) = array->get(*idx); \
+ ip += 5; \
+ } \
+ } \
+ DISPATCH_OPCODE
+
+ OPCODE_ITERATE_PACKED_ARRAY(BYTE, uint8_t, get_byte_array, get_int);
+ OPCODE_ITERATE_PACKED_ARRAY(INT32, int32_t, get_int32_array, get_int);
+ OPCODE_ITERATE_PACKED_ARRAY(INT64, int64_t, get_int64_array, get_int);
+ OPCODE_ITERATE_PACKED_ARRAY(FLOAT32, float, get_float32_array, get_float);
+ OPCODE_ITERATE_PACKED_ARRAY(FLOAT64, double, get_float64_array, get_float);
+ OPCODE_ITERATE_PACKED_ARRAY(STRING, String, get_string_array, get_string);
+ OPCODE_ITERATE_PACKED_ARRAY(VECTOR2, Vector2, get_vector2_array, get_vector2);
+ OPCODE_ITERATE_PACKED_ARRAY(VECTOR3, Vector3, get_vector3_array, get_vector3);
+ OPCODE_ITERATE_PACKED_ARRAY(COLOR, Color, get_color_array, get_color);
+
+ OPCODE(OPCODE_ITERATE_OBJECT) {
+ CHECK_SPACE(4);
+
+ GET_INSTRUCTION_ARG(counter, 0);
+ GET_INSTRUCTION_ARG(container, 1);
+
+#ifdef DEBUG_ENABLED
+ bool freed = false;
+ Object *obj = container->get_validated_object_with_check(freed);
+ if (freed) {
+ err_text = "Trying to iterate on a previously freed object.";
+ OPCODE_BREAK;
+ } else if (!obj) {
+ err_text = "Trying to iterate on a null value.";
+ OPCODE_BREAK;
+ }
+#else
+ Object *obj = *VariantInternal::get_object(container);
+#endif
+ Array ref;
+ ref.push_back(*counter);
+ Variant vref;
+ VariantInternal::initialize(&vref, Variant::ARRAY);
+ *VariantInternal::get_array(&vref) = ref;
+
+ Variant **args = instruction_args; // Overriding an instruction argument, but we don't need access to that anymore.
+ args[0] = &vref;
+
+ Callable::CallError ce;
+ Variant has_next = obj->call(CoreStringNames::get_singleton()->_iter_next, (const Variant **)args, 1, ce);
+
+#ifdef DEBUG_ENABLED
+ if (ce.error != Callable::CallError::CALL_OK) {
+ err_text = vformat(R"(There was an error calling "_iter_next" on iterator object of type %s.)", *container);
+ OPCODE_BREAK;
+ }
+#endif
+ if (!has_next.booleanize()) {
+ int jumpto = _code_ptr[ip + 4];
+ GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
+ ip = jumpto;
+ } else {
+ GET_INSTRUCTION_ARG(iterator, 2);
+ *iterator = obj->call(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce);
+#ifdef DEBUG_ENABLED
+ if (ce.error != Callable::CallError::CALL_OK) {
+ err_text = vformat(R"(There was an error calling "_iter_get" on iterator object of type %s.)", *container);
+ OPCODE_BREAK;
+ }
+#endif
+
+ ip += 5; // Loop again.
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_ASSERT) {
+ CHECK_SPACE(3);
+
+#ifdef DEBUG_ENABLED
+ GET_INSTRUCTION_ARG(test, 0);
+ bool result = test->booleanize();
+
+ if (!result) {
+ String message_str;
+ if (_code_ptr[ip + 2] != 0) {
+ GET_INSTRUCTION_ARG(message, 1);
+ message_str = *message;
+ }
+ if (message_str.empty()) {
+ err_text = "Assertion failed.";
+ } else {
+ err_text = "Assertion failed: " + message_str;
+ }
+ OPCODE_BREAK;
+ }
+
+#endif
+ ip += 3;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_BREAKPOINT) {
+#ifdef DEBUG_ENABLED
+ if (EngineDebugger::is_active()) {
+ GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement", true);
+ }
+#endif
+ ip += 1;
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_LINE) {
+ CHECK_SPACE(2);
+
+ line = _code_ptr[ip + 1];
+ ip += 2;
+
+ if (EngineDebugger::is_active()) {
+ // line
+ bool do_break = false;
+
+ if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) {
+ if (EngineDebugger::get_script_debugger()->get_depth() <= 0) {
+ EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1);
+ }
+ if (EngineDebugger::get_script_debugger()->get_lines_left() <= 0) {
+ do_break = true;
+ }
+ }
+
+ if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source)) {
+ do_break = true;
+ }
+
+ if (do_break) {
+ GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true);
+ }
+
+ EngineDebugger::get_singleton()->line_poll();
+ }
+ }
+ DISPATCH_OPCODE;
+
+ OPCODE(OPCODE_END) {
+#ifdef DEBUG_ENABLED
+ exit_ok = true;
+#endif
+ OPCODE_BREAK;
+ }
+
+#if 0 // Enable for debugging.
+ default: {
+ err_text = "Illegal opcode " + itos(_code_ptr[ip]) + " at address " + itos(ip);
+ OPCODE_BREAK;
+ }
+#endif
+ }
+
+ OPCODES_END
+#ifdef DEBUG_ENABLED
+ if (exit_ok) {
+ OPCODE_OUT;
+ }
+ //error
+ // function, file, line, error, explanation
+ String err_file;
+ if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->path != "") {
+ err_file = p_instance->script->path;
+ } else if (script) {
+ err_file = script->path;
+ }
+ if (err_file == "") {
+ err_file = "<built-in>";
+ }
+ String err_func = name;
+ if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->name != "") {
+ err_func = p_instance->script->name + "." + err_func;
+ }
+ int err_line = line;
+ if (err_text == "") {
+ err_text = "Internal Script Error! - opcode #" + itos(last_opcode) + " (report please).";
+ }
+
+ if (!GDScriptLanguage::get_singleton()->debug_break(err_text, false)) {
+ // debugger break did not happen
+
+ _err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text.utf8().get_data(), ERR_HANDLER_SCRIPT);
+ }
+
+#endif
+ OPCODE_OUT;
+ }
+
+ OPCODES_OUT
+#ifdef DEBUG_ENABLED
+ if (GDScriptLanguage::get_singleton()->profiling) {
+ uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time;
+ profile.total_time += time_taken;
+ profile.self_time += time_taken - function_call_time;
+ profile.frame_total_time += time_taken;
+ profile.frame_self_time += time_taken - function_call_time;
+ GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
+ }
+
+ // 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 || awaited) {
+ if (EngineDebugger::is_active()) {
+ GDScriptLanguage::get_singleton()->exit_function();
+ }
+#endif
+
+ if (_stack_size) {
+ //free stack
+ for (int i = 0; i < _stack_size; i++) {
+ stack[i].~Variant();
+ }
+ }
+
+#ifdef DEBUG_ENABLED
+ }
+#endif
+
+ return retvalue;
+}
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 105facd9d0..56704d3e0a 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -30,7 +30,7 @@
#include "gdscript_warning.h"
-#include "core/variant.h"
+#include "core/variant/variant.h"
#ifdef DEBUG_ENABLED
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index e183d6f302..e0857703d8 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -33,8 +33,8 @@
#ifdef DEBUG_ENABLED
-#include "core/ustring.h"
-#include "core/vector.h"
+#include "core/string/ustring.h"
+#include "core/templates/vector.h"
class GDScriptWarning {
public:
diff --git a/modules/gdscript/icons/GDScript.svg b/modules/gdscript/icons/GDScript.svg
index 953bb9ae9e..aa59125ea9 100644
--- a/modules/gdscript/icons/GDScript.svg
+++ b/modules/gdscript/icons/GDScript.svg
@@ -1,5 +1 @@
-<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
-<g transform="translate(0 -1036.4)">
-<path transform="translate(0 1036.4)" d="m7 1l-0.56445 2.2578a5 5 0 0 0 -0.68945 0.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -0.28516 0.68555l-2.2539 0.5625v2l2.2578 0.56445a5 5 0 0 0 0.2793 0.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953a5 5 0 0 0 0.68555 0.28516l0.5625 2.2539h2l0.56445-2.2578a5 5 0 0 0 0.6875 -0.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941a5 5 0 0 0 0.28516 -0.68555l2.2539-0.5625v-2l-2.2578-0.56445a5 5 0 0 0 -0.2793 -0.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -0.68555 -0.28516l-0.5625-2.2539h-2zm1 5a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2 -2 2 2 0 0 1 2 -2z" fill="#e0e0e0"/>
-</g>
-</svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2l2.2578.56445a5 5 0 0 0 .2793.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953a5 5 0 0 0 .68555.28516l.5625 2.2539h2l.56445-2.2578a5 5 0 0 0 .6875-.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941a5 5 0 0 0 .28516-.68555l2.2539-.5625v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2zm1 5a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2z" fill="#e0e0e0"/></svg>
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index ae7898fdf2..bd2d170e52 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -237,7 +237,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
case ClassNode::Member::ENUM_VALUE: {
lsp::DocumentSymbol symbol;
- symbol.name = m.constant->identifier->name;
+ symbol.name = m.enum_value.identifier->name;
symbol.kind = lsp::SymbolKind::EnumMember;
symbol.deprecated = false;
symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
@@ -491,7 +491,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &
int start_pos = p_position.character;
for (int c = p_position.character; c >= 0; c--) {
start_pos = c;
- CharType ch = line[c];
+ char32_t ch = line[c];
bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
if (!valid_char) {
break;
@@ -500,7 +500,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &
int end_pos = p_position.character;
for (int c = p_position.character; c < line.length(); c++) {
- CharType ch = line[c];
+ char32_t ch = line[c];
bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
if (!valid_char) {
break;
@@ -552,7 +552,7 @@ Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_positi
}
while (c >= 0) {
- const CharType &character = line[c];
+ const char32_t &character = line[c];
if (character == ')') {
++bracket_stack;
} else if (character == '(') {
@@ -723,8 +723,8 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
} 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;
+ for (int j = 0; j < m.m_enum->values.size(); j++) {
+ enum_dict[m.m_enum->values[j].identifier->name] = m.m_enum->values[j].value;
}
Dictionary api;
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h
index 0c031d7883..bb02d3dc99 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.h
+++ b/modules/gdscript/language_server/gdscript_extend_parser.h
@@ -32,7 +32,7 @@
#define GDSCRIPT_EXTEND_PARSER_H
#include "../gdscript_parser.h"
-#include "core/variant.h"
+#include "core/variant/variant.h"
#include "lsp.hpp"
#ifndef LINE_NUMBER_TO_INDEX
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp
index 2a67d2ff4f..729be237ec 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.cpp
+++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp
@@ -30,9 +30,10 @@
#include "gdscript_language_protocol.h"
+#include "core/config/project_settings.h"
#include "core/io/json.h"
#include "core/os/copymem.h"
-#include "core/project_settings.h"
+#include "editor/doc_tools.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
@@ -212,7 +213,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
void GDScriptLanguageProtocol::initialized(const Variant &p_params) {
lsp::GodotCapabilities capabilities;
- DocData *doc = EditorHelp::get_doc_data();
+ DocTools *doc = EditorHelp::get_doc_data();
for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) {
lsp::GodotNativeClassInfo gdclass;
gdclass.name = E->get().name;
diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h
index b2fd0c31f9..2a5755bec6 100644
--- a/modules/gdscript/language_server/gdscript_text_document.h
+++ b/modules/gdscript/language_server/gdscript_text_document.h
@@ -31,8 +31,8 @@
#ifndef GDSCRIPT_TEXT_DOCUMENT_H
#define GDSCRIPT_TEXT_DOCUMENT_H
+#include "core/object/reference.h"
#include "core/os/file_access.h"
-#include "core/reference.h"
#include "lsp.hpp"
class GDScriptTextDocument : public Reference {
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index 776193e37c..60668e7b31 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -32,8 +32,9 @@
#include "../gdscript.h"
#include "../gdscript_parser.h"
-#include "core/project_settings.h"
-#include "core/script_language.h"
+#include "core/config/project_settings.h"
+#include "core/object/script_language.h"
+#include "editor/doc_tools.h"
#include "editor/editor_file_system.h"
#include "editor/editor_help.h"
#include "editor/editor_node.h"
@@ -189,7 +190,7 @@ Error GDScriptWorkspace::initialize() {
return OK;
}
- DocData *doc = EditorHelp::get_doc_data();
+ DocTools *doc = EditorHelp::get_doc_data();
for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) {
const DocData::ClassDoc &class_data = E->value();
lsp::DocumentSymbol class_symbol;
diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h
index e45b06747d..fc244c6357 100644
--- a/modules/gdscript/language_server/gdscript_workspace.h
+++ b/modules/gdscript/language_server/gdscript_workspace.h
@@ -32,7 +32,7 @@
#define GDSCRIPT_WORKSPACE_H
#include "../gdscript_parser.h"
-#include "core/variant.h"
+#include "core/variant/variant.h"
#include "editor/editor_file_system.h"
#include "gdscript_extend_parser.h"
#include "lsp.hpp"
diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp
index cf27a1578c..1029c53bbf 100644
--- a/modules/gdscript/language_server/lsp.hpp
+++ b/modules/gdscript/language_server/lsp.hpp
@@ -31,9 +31,9 @@
#ifndef GODOT_LSP_H
#define GODOT_LSP_H
-#include "core/class_db.h"
-#include "core/list.h"
-#include "editor/doc_data.h"
+#include "core/doc_data.h"
+#include "core/object/class_db.h"
+#include "core/templates/list.h"
namespace lsp {
@@ -1781,7 +1781,6 @@ static String marked_documentation(const String &p_bbcode) {
}
return markdown;
}
-
} // namespace lsp
#endif
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index c554cbac05..6c2af66c65 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -39,6 +39,11 @@
#include "gdscript_cache.h"
#include "gdscript_tokenizer.h"
+#ifdef TESTS_ENABLED
+#include "tests/test_gdscript.h"
+#include "tests/test_macros.h"
+#endif
+
GDScriptLanguage *script_language_gd = nullptr;
Ref<ResourceFormatLoaderGDScript> resource_loader_gd;
Ref<ResourceFormatSaverGDScript> resource_saver_gd;
@@ -54,7 +59,7 @@ GDScriptCache *gdscript_cache = nullptr;
#include "editor/gdscript_translation_parser_plugin.h"
#ifndef GDSCRIPT_NO_LSP
-#include "core/engine.h"
+#include "core/config/engine.h"
#include "language_server/gdscript_language_server.h"
#endif // !GDSCRIPT_NO_LSP
@@ -79,7 +84,7 @@ public:
return;
}
- // TODO: Readd compiled/encrypted GDScript on export.
+ // TODO: Readd compiled GDScript on export.
return;
}
};
@@ -107,7 +112,6 @@ static void _editor_init() {
void register_gdscript_types() {
ClassDB::register_class<GDScript>();
- ClassDB::register_virtual_class<GDScriptFunctionState>();
script_language_gd = memnew(GDScriptLanguage);
ScriptServer::register_language(script_language_gd);
@@ -153,3 +157,26 @@ void unregister_gdscript_types() {
GDScriptParser::cleanup();
GDScriptAnalyzer::cleanup();
}
+
+#ifdef TESTS_ENABLED
+void test_tokenizer() {
+ TestGDScript::test(TestGDScript::TestType::TEST_TOKENIZER);
+}
+
+void test_parser() {
+ TestGDScript::test(TestGDScript::TestType::TEST_PARSER);
+}
+
+void test_compiler() {
+ TestGDScript::test(TestGDScript::TestType::TEST_COMPILER);
+}
+
+void test_bytecode() {
+ TestGDScript::test(TestGDScript::TestType::TEST_BYTECODE);
+}
+
+REGISTER_TEST_COMMAND("gdscript-tokenizer", &test_tokenizer);
+REGISTER_TEST_COMMAND("gdscript-parser", &test_parser);
+REGISTER_TEST_COMMAND("gdscript-compiler", &test_compiler);
+REGISTER_TEST_COMMAND("gdscript-bytecode", &test_bytecode);
+#endif
diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp
new file mode 100644
index 0000000000..643c2f10a2
--- /dev/null
+++ b/modules/gdscript/tests/test_gdscript.cpp
@@ -0,0 +1,306 @@
+/*************************************************************************/
+/* test_gdscript.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "test_gdscript.h"
+
+#include "core/config/project_settings.h"
+#include "core/io/file_access_pack.h"
+#include "core/os/file_access.h"
+#include "core/os/main_loop.h"
+#include "core/os/os.h"
+#include "core/string/string_builder.h"
+#include "scene/resources/packed_scene.h"
+
+#include "modules/gdscript/gdscript_analyzer.h"
+#include "modules/gdscript/gdscript_compiler.h"
+#include "modules/gdscript/gdscript_parser.h"
+#include "modules/gdscript/gdscript_tokenizer.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif
+
+namespace TestGDScript {
+
+static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) {
+ GDScriptTokenizer tokenizer;
+ tokenizer.set_source_code(p_code);
+
+ int tab_size = 4;
+#ifdef TOOLS_ENABLED
+ if (EditorSettings::get_singleton()) {
+ tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size");
+ }
+#endif // TOOLS_ENABLED
+ String tab = String(" ").repeat(tab_size);
+
+ GDScriptTokenizer::Token current = tokenizer.scan();
+ while (current.type != GDScriptTokenizer::Token::TK_EOF) {
+ StringBuilder token;
+ token += " --> "; // Padding for line number.
+
+ for (int l = current.start_line; l <= current.end_line; l++) {
+ print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));
+ }
+
+ {
+ // Print carets to point at the token.
+ StringBuilder pointer;
+ pointer += " "; // Padding for line number.
+ int rightmost_column = current.rightmost_column;
+ if (current.end_line > current.start_line) {
+ rightmost_column--; // Don't point to the newline as a column.
+ }
+ for (int col = 1; col < rightmost_column; col++) {
+ if (col < current.leftmost_column) {
+ pointer += " ";
+ } else {
+ pointer += "^";
+ }
+ }
+ print_line(pointer.as_string());
+ }
+
+ token += current.get_name();
+
+ if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) {
+ token += "(";
+ token += Variant::get_type_name(current.literal.get_type());
+ token += ") ";
+ token += current.literal;
+ }
+
+ print_line(token.as_string());
+
+ print_line("-------------------------------------------------------");
+
+ current = tokenizer.scan();
+ }
+
+ print_line(current.get_name()); // Should be EOF
+}
+
+static void test_parser(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {
+ GDScriptParser parser;
+ Error err = parser.parse(p_code, p_script_path, false);
+
+ if (err != OK) {
+ const List<GDScriptParser::ParserError> &errors = parser.get_errors();
+ for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) {
+ const GDScriptParser::ParserError &error = E->get();
+ print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
+ }
+ }
+
+ GDScriptParser::TreePrinter printer;
+
+ printer.print_tree(parser);
+}
+
+static void test_compiler(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {
+ GDScriptParser parser;
+ Error err = parser.parse(p_code, p_script_path, false);
+
+ if (err != OK) {
+ print_line("Error in parser:");
+ 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));
+ }
+ return;
+ }
+
+ GDScriptAnalyzer analyzer(&parser);
+ err = analyzer.analyze();
+
+ if (err != OK) {
+ print_line("Error in analyzer:");
+ 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));
+ }
+ return;
+ }
+
+ GDScriptCompiler compiler;
+ Ref<GDScript> script;
+ script.instance();
+ script->set_path(p_script_path);
+
+ err = compiler.compile(&parser, script.ptr(), false);
+
+ if (err) {
+ print_line("Error in compiler:");
+ print_line(vformat("%02d:%02d: %s", compiler.get_error_line(), compiler.get_error_column(), compiler.get_error()));
+ return;
+ }
+
+ for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) {
+ const GDScriptFunction *func = E->value();
+
+ String signature = "Disassembling " + func->get_name().operator String() + "(";
+ for (int i = 0; i < func->get_argument_count(); i++) {
+ if (i > 0) {
+ signature += ", ";
+ }
+ signature += func->get_argument_name(i);
+ }
+ print_line(signature + ")");
+
+ func->disassemble(p_lines);
+ print_line("");
+ print_line("");
+ }
+}
+
+void init_autoloads() {
+ Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+
+ // First pass, add the constants so they exist before any script is loaded.
+ for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
+ const ProjectSettings::AutoloadInfo &info = E->get();
+
+ if (info.is_singleton) {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->add_global_constant(info.name, Variant());
+ }
+ }
+ }
+
+ // Second pass, load into global constants.
+ for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
+ const ProjectSettings::AutoloadInfo &info = E->get();
+
+ if (!info.is_singleton) {
+ // Skip non-singletons since we don't have a scene tree here anyway.
+ continue;
+ }
+
+ RES res = ResourceLoader::load(info.path);
+ ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path);
+ Node *n = nullptr;
+ if (res->is_class("PackedScene")) {
+ Ref<PackedScene> ps = res;
+ n = ps->instance();
+ } else if (res->is_class("Script")) {
+ Ref<Script> script_res = res;
+ StringName ibt = script_res->get_instance_base_type();
+ bool valid_type = ClassDB::is_parent_class(ibt, "Node");
+ ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path);
+
+ Object *obj = ClassDB::instance(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);
+ }
+
+ ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path);
+ n->set_name(info.name);
+
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->add_global_constant(info.name, n);
+ }
+ }
+}
+
+void test(TestType p_type) {
+ List<String> cmdlargs = OS::get_singleton()->get_cmdline_args();
+
+ if (cmdlargs.empty()) {
+ return;
+ }
+
+ String test = cmdlargs.back()->get();
+ if (!test.ends_with(".gd")) {
+ print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test);
+ return;
+ }
+
+ FileAccessRef fa = FileAccess::open(test, FileAccess::READ);
+ ERR_FAIL_COND_MSG(!fa, "Could not open file: " + test);
+
+ // Init PackedData since it's used by ProjectSettings.
+ PackedData *packed_data = memnew(PackedData);
+
+ // Setup project settings since it's needed by the languages to get the global scripts.
+ // This also sets up the base resource path.
+ Error err = ProjectSettings::get_singleton()->setup(fa->get_path_absolute().get_base_dir(), String(), true);
+ if (err) {
+ print_line("Could not load project settings.");
+ // Keep going since some scripts still work without this.
+ }
+
+ // Initialize the language for the test routine.
+ ScriptServer::init_languages();
+ init_autoloads();
+
+ Vector<uint8_t> buf;
+ int flen = fa->get_len();
+ buf.resize(fa->get_len() + 1);
+ fa->get_buffer(buf.ptrw(), flen);
+ buf.write[flen] = 0;
+
+ String code;
+ code.parse_utf8((const char *)&buf[0]);
+
+ Vector<String> lines;
+ int last = 0;
+ for (int i = 0; i <= code.length(); i++) {
+ if (code[i] == '\n' || code[i] == 0) {
+ lines.push_back(code.substr(last, i - last));
+ last = i + 1;
+ }
+ }
+
+ switch (p_type) {
+ case TEST_TOKENIZER:
+ test_tokenizer(code, lines);
+ break;
+ case TEST_PARSER:
+ test_parser(code, test, lines);
+ break;
+ case TEST_COMPILER:
+ test_compiler(code, test, lines);
+ break;
+ case TEST_BYTECODE:
+ print_line("Not implemented.");
+ }
+
+ // Destroy stuff we set up earlier.
+ ScriptServer::finish_languages();
+ memdelete(packed_data);
+}
+} // namespace TestGDScript
diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h
new file mode 100644
index 0000000000..6182629802
--- /dev/null
+++ b/modules/gdscript/tests/test_gdscript.h
@@ -0,0 +1,46 @@
+/*************************************************************************/
+/* test_gdscript.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_GDSCRIPT_H
+#define TEST_GDSCRIPT_H
+
+namespace TestGDScript {
+
+enum TestType {
+ TEST_TOKENIZER,
+ TEST_PARSER,
+ TEST_COMPILER,
+ TEST_BYTECODE,
+};
+
+void test(TestType p_type);
+} // namespace TestGDScript
+
+#endif // TEST_GDSCRIPT_H