diff options
Diffstat (limited to 'modules/gdscript')
35 files changed, 14620 insertions, 14834 deletions
diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py index 185a10bcb2..6fc227e7f5 100644 --- a/modules/gdscript/config.py +++ b/modules/gdscript/config.py @@ -11,7 +11,6 @@ def get_doc_classes(): "@GDScript", "GDScript", "GDScriptFunctionState", - "GDScriptNativeClass", ] diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 9324691df5..9e40a69712 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -52,7 +52,7 @@ <argument index="0" name="s" type="float"> </argument> <description> - Returns the absolute value of parameter [code]s[/code] (i.e. unsigned value, works for integer and float). + Returns the absolute value of parameter [code]s[/code] (i.e. positive value). [codeblock] # a is 1 a = abs(-1) @@ -112,7 +112,7 @@ </argument> <description> Returns the arc tangent of [code]s[/code] in radians. Use it to get the angle from an angle's tangent in trigonometry: [code]atan(tan(angle)) == angle[/code]. - The method cannot know in which quadrant the angle should fall. See [method atan2] if you always want an exact angle. + The method cannot know in which quadrant the angle should fall. See [method atan2] if you have both [code]y[/code] and [code]x[/code]. [codeblock] a = atan(0.5) # a is 0.463648 [/codeblock] @@ -127,6 +127,7 @@ </argument> <description> Returns the arc tangent of [code]y/x[/code] in radians. Use to get the angle of tangent [code]y/x[/code]. To compute the value, the method takes into account the sign of both arguments in order to determine the quadrant. + Important note: The Y coordinate comes first, by convention. [codeblock] a = atan2(0, -1) # a is 3.141593 [/codeblock] @@ -161,7 +162,7 @@ <argument index="0" name="s" type="float"> </argument> <description> - Rounds [code]s[/code] upward, returning the smallest integral value that is not less than [code]s[/code]. + Rounds [code]s[/code] upward (towards positive infinity), returning the smallest whole number that is not less than [code]s[/code]. [codeblock] i = ceil(1.45) # i is 2 i = ceil(1.001) # i is 2 @@ -283,7 +284,7 @@ <argument index="0" name="deg" type="float"> </argument> <description> - Returns degrees converted to radians. + Converts an angle expressed in degrees to radians. [codeblock] # r is 3.141593 r = deg2rad(180) @@ -307,7 +308,7 @@ <argument index="1" name="curve" type="float"> </argument> <description> - Easing function, based on exponent. 0 is constant, 1 is linear, 0 to 1 is ease-in, 1+ is ease out. Negative values are in-out/out in. + Easing function, based on exponent. The curve values are: 0 is constant, 1 is linear, 0 to 1 is ease-in, 1+ is ease out. Negative values are in-out/out in. </description> </method> <method name="exp"> @@ -317,7 +318,7 @@ </argument> <description> The natural exponential function. It raises the mathematical constant [b]e[/b] to the power of [code]s[/code] and returns it. - [b]e[/b] has an approximate value of 2.71828. + [b]e[/b] has an approximate value of 2.71828, and can be obtained with [code]exp(1)[/code]. For exponents to other bases use the method [method pow]. [codeblock] a = exp(2) # Approximately 7.39 @@ -330,7 +331,7 @@ <argument index="0" name="s" type="float"> </argument> <description> - Rounds [code]s[/code] to the closest smaller integer and returns it. + Rounds [code]s[/code] downward (towards negative infinity), returning the largest whole number that is not more than [code]s[/code]. [codeblock] # a is 2.0 a = floor(2.99) @@ -504,6 +505,8 @@ </argument> <description> Returns [code]true[/code] if [code]a[/code] and [code]b[/code] are approximately equal to each other. + Here, approximately equal means that [code]a[/code] and [code]b[/code] are within a small internal epsilon of each other, which scales with the magnitude of the numbers. + Infinity values of the same sign are considered equal. </description> </method> <method name="is_inf"> @@ -530,7 +533,7 @@ <argument index="0" name="s" type="float"> </argument> <description> - Returns whether [code]s[/code] is a NaN (Not-A-Number) value. + Returns whether [code]s[/code] is a NaN ("Not a Number" or invalid) value. </description> </method> <method name="is_zero_approx"> @@ -540,6 +543,7 @@ </argument> <description> Returns [code]true[/code] if [code]s[/code] is zero or almost zero. + This method is faster than using [method is_equal_approx] with one value as zero. </description> </method> <method name="len"> @@ -568,7 +572,7 @@ <description> Linearly interpolates between two values by a normalized value. This is the opposite of [method inverse_lerp]. If the [code]from[/code] and [code]to[/code] arguments are of type [int] or [float], the return value is a [float]. - If both are of the same vector type ([Vector2], [Vector3] or [Color]), the return value will be of the same type ([code]lerp[/code] then calls the vector type's [code]linear_interpolate[/code] method). + If both are of the same vector type ([Vector2], [Vector3] or [Color]), the return value will be of the same type ([code]lerp[/code] then calls the vector type's [code]lerp[/code] method). [codeblock] lerp(0, 4, 0.75) # Returns 3.0 lerp(Vector2(1, 5), Vector2(3, 2), 0.5) # Returns Vector2(2, 3.5) @@ -619,11 +623,11 @@ <argument index="0" name="path" type="String"> </argument> <description> - Loads a resource from the filesystem located at [code]path[/code]. - [b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the FileSystem dock and choosing [b]Copy Path[/b]. + Loads a resource from the filesystem located at [code]path[/code]. The resource is loaded on the method call (unless it's referenced already elsewhere, e.g. in another script or in the scene), which might cause slight delay, especially when loading scenes. To avoid unnecessary delays when loading something multiple times, either store the resource in a variable or use [method preload]. + [b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the FileSystem dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. [codeblock] - # Load a scene called main located in the root of the project directory. - var main = load("res://main.tscn") + # Load a scene called main located in the root of the project directory and cache it in a variable. + 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]. </description> @@ -639,6 +643,7 @@ [codeblock] log(10) # Returns 2.302585 [/codeblock] + [b]Note:[/b] The logarithm of [code]0[/code] returns [code]-inf[/code], while negative values return [code]-nan[/code]. </description> </method> <method name="max"> @@ -684,7 +689,9 @@ Moves [code]from[/code] toward [code]to[/code] by the [code]delta[/code] value. Use a negative [code]delta[/code] value to move away. [codeblock] + move_toward(5, 10, 4) # Returns 9 move_toward(10, 5, 4) # Returns 6 + move_toward(10, 5, -1.5) # Returns 11.5 [/codeblock] </description> </method> @@ -694,12 +701,17 @@ <argument index="0" name="value" type="int"> </argument> <description> - Returns the nearest larger power of 2 for integer [code]value[/code]. + Returns the nearest equal or larger power of 2 for integer [code]value[/code]. + In other words, returns the smallest value [code]a[/code] where [code]a = pow(2, n)[/code] such that [code]value <= a[/code] for some non-negative integer [code]n[/code]. [codeblock] nearest_po2(3) # Returns 4 nearest_po2(4) # Returns 4 nearest_po2(5) # Returns 8 + + nearest_po2(0) # Returns 0 (this may not be what you expect) + nearest_po2(-1) # Returns 0 (this may not be what you expect) [/codeblock] + [b]WARNING:[/b] Due to the way it is implemented, this function returns [code]0[/code] rather than [code]1[/code] for non-positive values of [code]value[/code] (in reality, 1 is the smallest integer power of 2). </description> </method> <method name="ord"> @@ -723,16 +735,17 @@ <argument index="0" name="json" type="String"> </argument> <description> - Parse JSON text to a Variant (use [method typeof] to check if it is what you expect). - Be aware that the JSON specification does not define integer or float types, but only a number type. Therefore, parsing a JSON text will convert all numerical values to [float] types. - Note that JSON objects do not preserve key order like Godot dictionaries, thus you should not rely on keys being in a certain order if a dictionary is constructed from JSON. In contrast, JSON arrays retain the order of their elements: + Parse JSON text to a Variant. (Use [method typeof] to check if the Variant's type is what you expect.) + [b]Note:[/b] The JSON specification does not define integer or float types, but only a [i]number[/i] type. Therefore, parsing a JSON text will convert all numerical values to [float] types. + [b]Note:[/b] JSON objects do not preserve key order like Godot dictionaries, thus, you should not rely on keys being in a certain order if a dictionary is constructed from JSON. In contrast, JSON arrays retain the order of their elements: [codeblock] - p = parse_json('["a", "b", "c"]') - if typeof(p) == TYPE_ARRAY: - print(p[0]) # Prints a + var p = JSON.parse('["hello", "world", "!"]') + if typeof(p.result) == TYPE_ARRAY: + print(p.result[0]) # Prints "hello" else: - print("unexpected results") + push_error("Unexpected results.") [/codeblock] + See also [JSON] for an alternative way to parse JSON text. </description> </method> <method name="polar2cartesian"> @@ -797,11 +810,11 @@ <argument index="0" name="path" type="String"> </argument> <description> - Returns a resource from the filesystem that is loaded during script parsing. - [b]Note:[/b] Resource paths can be obtained by right clicking on a resource in the Assets Panel and choosing "Copy Path". + Returns a [Resource] from the filesystem located at [code]path[/code]. The resource is loaded during script parsing, i.e. is loaded with the script and [method preload] effectively acts as a reference to that resource. Note that the method requires a constant path. If you want to load a resource from a dynamic/variable path, use [method load]. + [b]Note:[/b] Resource paths can be obtained by right clicking on a resource in the Assets Panel and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. [codeblock] - # Load a scene called main located in the root of the project directory. - var main = preload("res://main.tscn") + # Instance a scene. + var diamond = preload("res://diamond.tscn").instance() [/codeblock] </description> </method> @@ -907,7 +920,7 @@ <argument index="0" name="rad" type="float"> </argument> <description> - Converts from radians to degrees. + Converts an angle expressed in radians to degrees. [codeblock] rad2deg(0.523599) # Returns 30 [/codeblock] @@ -1026,7 +1039,7 @@ <argument index="0" name="s" type="float"> </argument> <description> - Returns the integral value that is nearest to [code]s[/code], with halfway cases rounded away from zero. + Rounds [code]s[/code] to the nearest whole number, with halfway cases rounded away from zero. [codeblock] round(2.6) # Returns 3 [/codeblock] @@ -1091,12 +1104,15 @@ </argument> <argument index="1" name="to" type="float"> </argument> - <argument index="2" name="weight" type="float"> + <argument index="2" name="s" type="float"> </argument> <description> - Returns a number smoothly interpolated between the [code]from[/code] and [code]to[/code], based on the [code]weight[/code]. Similar to [method lerp], but interpolates faster at the beginning and slower at the end. + Returns the result of smoothly interpolating the value of [code]s[/code] between [code]0[/code] and [code]1[/code], based on the where [code]s[/code] lies with respect to the edges [code]from[/code] and [code]to[/code]. + The return value is [code]0[/code] if [code]s <= from[/code], and [code]1[/code] if [code]s >= to[/code]. If [code]s[/code] lies between [code]from[/code] and [code]to[/code], the returned value follows an S-shaped curve that maps [code]s[/code] between [code]0[/code] and [code]1[/code]. + 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, 0.5) # Returns 0.15 + 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 [/codeblock] @@ -1108,10 +1124,11 @@ <argument index="0" name="s" type="float"> </argument> <description> - Returns the square root of [code]s[/code]. + Returns the square root of [code]s[/code], where [code]s[/code] is a non-negative number. [codeblock] sqrt(9) # Returns 3 [/codeblock] + [b]Note:[/b]Negative values of [code]s[/code] return NaN. If you need negative inputs, use [code]System.Numerics.Complex[/code] in C#. </description> </method> <method name="step_decimals"> @@ -1204,12 +1221,16 @@ <argument index="0" name="var" type="Variant"> </argument> <description> - Converts a Variant [code]var[/code] to JSON text and return the result. Useful for serializing data to store or send over the network. + Converts a [Variant] [code]var[/code] to JSON text and return the result. Useful for serializing data to store or send over the network. [codeblock] + # Both numbers below are integers. a = { "a": 1, "b": 2 } b = to_json(a) print(b) # {"a":1, "b":2} + # Both numbers above are floats, even if they display without any decimal places. [/codeblock] + [b]Note:[/b] The JSON specification does not define integer or float types, but only a [i]number[/i] type. Therefore, converting a [Variant] to JSON text will convert all numerical values to [float] types. + See also [JSON] for an alternative way to convert a [Variant] to JSON text. </description> </method> <method name="type_exists"> @@ -1252,9 +1273,9 @@ j = to_json([1, 2, 3]) v = validate_json(j) if not v: - print("valid") + print("Valid JSON.") else: - prints("invalid", v) + push_error("Invalid JSON: " + v) [/codeblock] </description> </method> @@ -1312,27 +1333,19 @@ Wraps float [code]value[/code] between [code]min[/code] and [code]max[/code]. Usable for creating loop-alike behavior or infinite surfaces. [codeblock] - # a is 0.5 - a = wrapf(10.5, 0.0, 10.0) - [/codeblock] - [codeblock] - # a is 9.5 - a = wrapf(-0.5, 0.0, 10.0) - [/codeblock] - [codeblock] - # Infinite loop between 0.0 and 0.99 - f = wrapf(f + 0.1, 0.0, 1.0) + # Infinite loop between 5.0 and 9.9 + value = wrapf(value + 0.1, 5.0, 10.0) [/codeblock] [codeblock] # Infinite rotation (in radians) angle = wrapf(angle + 0.1, 0.0, TAU) [/codeblock] - [b]Note:[/b] If you just want to wrap between 0.0 and [code]n[/code] (where [code]n[/code] is a positive floating-point value), it is better for performance to use the [method fmod] method like [code]fmod(number, n)[/code]. - [code]wrapf[/code] is more flexible than using the [method fmod] approach by giving the user a simple control over the minimum value. It also fully supports negative numbers, e.g. [codeblock] # Infinite rotation (in radians) angle = wrapf(angle + 0.1, -PI, PI) [/codeblock] + [b]Note:[/b] If [code]min[/code] is [code]0[/code], this is equivalent to [method fposmod], so prefer using that instead. + [code]wrapf[/code] is more flexible than using the [method fposmod] approach by giving the user control over the minimum value. </description> </method> <method name="wrapi"> @@ -1348,75 +1361,30 @@ Wraps integer [code]value[/code] between [code]min[/code] and [code]max[/code]. Usable for creating loop-alike behavior or infinite surfaces. [codeblock] - # a is 0 - a = wrapi(10, 0, 10) + # Infinite loop between 5 and 9 + frame = wrapi(frame + 1, 5, 10) [/codeblock] [codeblock] - # a is 9 - a = wrapi(-1, 0, 10) - [/codeblock] - [codeblock] - # Infinite loop between 0 and 9 - frame = wrapi(frame + 1, 0, 10) - [/codeblock] - [b]Note:[/b] If you just want to wrap between 0 and [code]n[/code] (where [code]n[/code] is a positive integer value), it is better for performance to use the modulo operator like [code]number % n[/code]. - [code]wrapi[/code] is more flexible than using the modulo approach by giving the user a simple control over the minimum value. It also fully supports negative numbers, e.g. - [codeblock] # result is -2 var result = wrapi(-6, -5, -1) [/codeblock] - </description> - </method> - <method name="yield"> - <return type="GDScriptFunctionState"> - </return> - <argument index="0" name="object" type="Object" default="null"> - </argument> - <argument index="1" name="signal" type="String" default=""""> - </argument> - <description> - Stops the function execution and returns the current suspended state to the calling function. - From the caller, call [method GDScriptFunctionState.resume] on the state to resume execution. This invalidates the state. Within the resumed function, [code]yield()[/code] returns whatever was passed to the [code]resume()[/code] function call. - If passed an object and a signal, the execution is resumed when the object emits the given signal. In this case, [code]yield()[/code] returns the argument passed to [code]emit_signal()[/code] if the signal takes only one argument, or an array containing all the arguments passed to [code]emit_signal()[/code] if the signal takes multiple arguments. - You can also use [code]yield[/code] to wait for a function to finish: - [codeblock] - func _ready(): - yield(countdown(), "completed") # waiting for the countdown() function to complete - print('Ready') - - func countdown(): - yield(get_tree(), "idle_frame") # returns a GDScriptFunctionState object to _ready() - print(3) - yield(get_tree().create_timer(1.0), "timeout") - print(2) - yield(get_tree().create_timer(1.0), "timeout") - print(1) - yield(get_tree().create_timer(1.0), "timeout") - - # prints: - # 3 - # 2 - # 1 - # Ready - [/codeblock] - When yielding on a function, the [code]completed[/code] signal will be emitted automatically when the function returns. It can, therefore, be used as the [code]signal[/code] parameter of the [code]yield[/code] method to resume. - In order to yield on a function, the resulting function should also return a [code]GDScriptFunctionState[/code]. Notice [code]yield(get_tree(), "idle_frame")[/code] from the above example. + [b]Note:[/b] If [code]min[/code] is [code]0[/code], this is equivalent to [method posmod], so prefer using that instead. + [code]wrapi[/code] is more flexible than using the [method posmod] approach by giving the user control over the minimum value. </description> </method> </methods> <constants> <constant name="PI" value="3.141593"> - Constant that represents how many times the diameter of a circle fits around its perimeter. + Constant that represents how many times the diameter of a circle fits around its perimeter. This is equivalent to [code]TAU / 2[/code]. </constant> <constant name="TAU" value="6.283185"> - The circle constant, the circumference of the unit circle. + The circle constant, the circumference of the unit circle in radians. </constant> <constant name="INF" value="inf"> - A positive infinity. (For negative infinity, use -INF). + Positive infinity. For negative infinity, use -INF. </constant> <constant name="NAN" value="nan"> - Macro constant that expands to an expression of type float that represents a NaN. - The NaN values are used to identify undefined or non-representable values for floating-point elements, such as the square root of negative numbers or the result of 0/0. + "Not a Number", an invalid value. [code]NaN[/code] has special properties, including that it is not equal to itself. It is output by some invalid operations, such as dividing zero by zero. </constant> </constants> </class> diff --git a/modules/gdscript/doc_classes/GDScriptFunctionState.xml b/modules/gdscript/doc_classes/GDScriptFunctionState.xml index 9a73764646..5e369b32d9 100644 --- a/modules/gdscript/doc_classes/GDScriptFunctionState.xml +++ b/modules/gdscript/doc_classes/GDScriptFunctionState.xml @@ -4,7 +4,8 @@ State of a function call after yielding. </brief_description> <description> - Calling [method @GDScript.yield] 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. + 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> @@ -26,7 +27,7 @@ </argument> <description> Resume execution of the yielded function call. - If handed an argument, return the argument from the [method @GDScript.yield] call in the yielded function call. You can pass e.g. an [Array] to hand multiple arguments. + 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> diff --git a/modules/gdscript/doc_classes/GDScriptNativeClass.xml b/modules/gdscript/doc_classes/GDScriptNativeClass.xml deleted file mode 100644 index 0a8982de8e..0000000000 --- a/modules/gdscript/doc_classes/GDScriptNativeClass.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="GDScriptNativeClass" inherits="Reference" version="4.0"> - <brief_description> - </brief_description> - <description> - </description> - <tutorials> - </tutorials> - <methods> - <method name="new"> - <return type="Variant"> - </return> - <description> - </description> - </method> - </methods> - <constants> - </constants> -</class> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 0aca4dbc5e..ae1f2893f1 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -29,29 +29,14 @@ /*************************************************************************/ #include "gdscript_highlighter.h" +#include "../gdscript.h" #include "../gdscript_tokenizer.h" #include "editor/editor_settings.h" -#include "scene/gui/text_edit.h" - -inline bool _is_symbol(CharType c) { - - return is_symbol(c); -} - -static bool _is_text_char(CharType c) { - - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; -} static bool _is_char(CharType c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } -static bool _is_number(CharType c) { - return (c >= '0' && c <= '9'); -} - static bool _is_hex_symbol(CharType c) { return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } @@ -60,8 +45,8 @@ static bool _is_bin_symbol(CharType c) { return (c == '0' || c == '1'); } -Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) { - Map<int, TextEdit::HighlighterInfo> color_map; +Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) { + Dictionary color_map; Type next_type = NONE; Type current_type = NONE; @@ -85,36 +70,140 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ Color keyword_color; Color color; - int in_region = text_editor->_is_line_in_region(p_line); - int deregion = 0; + color_region_cache[p_line] = -1; + int in_region = -1; + if (p_line != 0) { + if (!color_region_cache.has(p_line - 1)) { + get_line_syntax_highlighting(p_line - 1); + } + in_region = color_region_cache[p_line - 1]; + } - const Map<int, TextEdit::Text::ColorRegionInfo> cri_map = text_editor->_get_line_color_region_info(p_line); - const String &str = text_editor->get_line(p_line); + const String &str = text_edit->get_line(p_line); + const int line_length = str.length(); Color prev_color; + + if (in_region != -1 && str.length() == 0) { + color_region_cache[p_line] = in_region; + } for (int j = 0; j < str.length(); j++) { - TextEdit::HighlighterInfo highlighter_info; + Dictionary highlighter_info; - if (deregion > 0) { - deregion--; - if (deregion == 0) { - in_region = -1; + color = font_color; + bool is_char = !is_symbol(str[j]); + bool is_a_symbol = is_symbol(str[j]); + bool is_number = (str[j] >= '0' && str[j] <= '9'); + + /* color regions */ + if (is_a_symbol || in_region != -1) { + int from = j; + for (; from < line_length; from++) { + if (str[from] == '\\') { + from++; + continue; + } + break; } - } - if (deregion != 0) { - if (color != prev_color) { - prev_color = color; - highlighter_info.color = color; - color_map[j] = highlighter_info; - } - continue; - } + if (from != line_length) { + /* check if we are in entering a region */ + if (in_region == -1) { + for (int c = 0; c < color_regions.size(); c++) { + /* check there is enough room */ + int chars_left = line_length - from; + int start_key_length = color_regions[c].start_key.length(); + int end_key_length = color_regions[c].end_key.length(); + if (chars_left < start_key_length) { + continue; + } + + /* search the line */ + bool match = true; + const CharType *start_key = color_regions[c].start_key.c_str(); + for (int k = 0; k < start_key_length; k++) { + if (start_key[k] != str[from + k]) { + match = false; + break; + } + } + if (!match) { + continue; + } + in_region = c; + from += start_key_length; + + /* check if it's the whole line */ + if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) { + prev_color = color_regions[in_region].color; + highlighter_info["color"] = color_regions[c].color; + color_map[j] = highlighter_info; + + j = line_length; + if (!color_regions[c].line_only) { + color_region_cache[p_line] = c; + } + } + break; + } - color = font_color; + if (j == line_length) { + continue; + } + } + + /* 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(); + for (; from < line_length; from++) { + if (!is_a_symbol) { + continue; + } + + if (str[from] == '\\') { + from++; + continue; + } + + for (int k = 0; k < end_key_length; k++) { + if (end_key[k] == str[from + k]) { + region_end_index = from; + break; + } + } + + if (region_end_index != -1) { + break; + } + } + + prev_color = color_regions[in_region].color; + highlighter_info["color"] = color_regions[in_region].color; + color_map[j] = highlighter_info; + + previous_type = REGION; + previous_text = ""; + previous_column = j; + j = from; + if (region_end_index == -1) { + color_region_cache[p_line] = in_region; + } - bool is_char = _is_text_char(str[j]); - bool is_symbol = _is_symbol(str[j]); - bool is_number = _is_number(str[j]); + in_region = -1; + prev_is_char = false; + prev_is_number = false; + continue; + } + } + } // allow ABCDEF in hex notation if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) { @@ -136,7 +225,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ // check for dot or underscore or 'x' for hex notation in floating point number or 'e' for scientific notation if ((str[j] == '.' || str[j] == 'x' || str[j] == 'b' || str[j] == '_' || str[j] == 'e') && !in_word && prev_is_number && !is_number) { is_number = true; - is_symbol = false; + is_a_symbol = false; is_char = false; if (str[j] == 'x' && str[j - 1] == '0') { @@ -154,43 +243,26 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ is_number = false; } - if (is_symbol && str[j] != '.' && in_word) { + if (is_a_symbol && str[j] != '.' && in_word) { in_word = false; } - if (is_symbol && cri_map.has(j)) { - const TextEdit::Text::ColorRegionInfo &cri = cri_map[j]; - - if (in_region == -1) { - if (!cri.end) { - in_region = cri.region; - } - } else { - TextEdit::ColorRegion cr = text_editor->_get_color_region(cri.region); - if (in_region == cri.region && !cr.line_only) { //ignore otherwise - if (cri.end || cr.eq) { - deregion = cr.eq ? cr.begin_key.length() : cr.end_key.length(); - } - } - } - } - if (!is_char) { in_keyword = false; } - if (in_region == -1 && !in_keyword && is_char && !prev_is_char) { - + if (!in_keyword && is_char && !prev_is_char) { int to = j; - while (to < str.length() && _is_text_char(str[to])) + while (to < str.length() && !is_symbol(str[to])) { to++; + } String word = str.substr(j, to - j); Color col = Color(); - if (text_editor->has_keyword_color(word)) { - col = text_editor->get_keyword_color(word); - } else if (text_editor->has_member_color(word)) { - col = text_editor->get_member_color(word); + if (keywords.has(word)) { + col = keywords[word]; + } else if (member_keywords.has(word)) { + col = member_keywords[word]; for (int k = j - 1; k >= 0; k--) { if (str[k] == '.') { col = Color(); //member indexing not allowed @@ -208,9 +280,8 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ } if (!in_function_name && in_word && !in_keyword) { - int k = j; - while (k < str.length() && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { + while (k < str.length() && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { k++; } @@ -221,14 +292,14 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ if (str[k] == '(') { in_function_name = true; - } else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_VAR)) { + } else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR)) { in_variable_declaration = true; } } if (!in_function_name && !in_member_variable && !in_keyword && !is_number && in_word) { int k = j; - while (k > 0 && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { + while (k > 0 && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { k--; } @@ -237,8 +308,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ } } - if (is_symbol) { - + if (is_a_symbol) { if (in_function_name) { in_function_args = true; } @@ -275,14 +345,11 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ if (!in_node_path && in_region == -1 && str[j] == '$') { in_node_path = true; - } else if (in_region != -1 || (is_symbol && str[j] != '/')) { + } else if (in_region != -1 || (is_a_symbol && str[j] != '/')) { in_node_path = false; } - if (in_region >= 0) { - next_type = REGION; - color = text_editor->_get_color_region(in_region).color; - } else if (in_node_path) { + if (in_node_path) { next_type = NODE_PATH; color = node_path_color; } else if (in_keyword) { @@ -294,12 +361,12 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ } else if (in_function_name) { next_type = FUNCTION; - if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_FUNCTION)) { + if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) { color = function_definition_color; } else { color = function_color; } - } else if (is_symbol) { + } else if (is_a_symbol) { next_type = SYMBOL; color = symbol_color; } else if (is_number) { @@ -340,32 +407,133 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ if (color != prev_color) { prev_color = color; - highlighter_info.color = color; + highlighter_info["color"] = color; color_map[j] = highlighter_info; } } return color_map; } -String GDScriptSyntaxHighlighter::get_name() const { +String GDScriptSyntaxHighlighter::_get_name() const { return "GDScript"; } -List<String> GDScriptSyntaxHighlighter::get_supported_languages() { - List<String> languages; +Array GDScriptSyntaxHighlighter::_get_supported_languages() const { + Array languages; languages.push_back("GDScript"); return languages; } void GDScriptSyntaxHighlighter::_update_cache() { - font_color = text_editor->get_theme_color("font_color"); - symbol_color = text_editor->get_theme_color("symbol_color"); - function_color = text_editor->get_theme_color("function_color"); - number_color = text_editor->get_theme_color("number_color"); - member_color = text_editor->get_theme_color("member_variable_color"); + keywords.clear(); + member_keywords.clear(); + color_regions.clear(); + color_region_cache.clear(); + + font_color = text_edit->get_theme_color("font_color"); + symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color"); + function_color = EDITOR_GET("text_editor/highlighting/function_color"); + number_color = EDITOR_GET("text_editor/highlighting/number_color"); + member_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); + + /* Engine types. */ + const Color types_color = EDITOR_GET("text_editor/highlighting/engine_type_color"); + List<StringName> types; + ClassDB::get_class_list(&types); + for (List<StringName>::Element *E = types.front(); E; E = E->next()) { + String n = E->get(); + if (n.begins_with("_")) { + n = n.substr(1, n.length()); + } + keywords[n] = types_color; + } + + /* User types. */ + const Color usertype_color = EDITOR_GET("text_editor/highlighting/user_type_color"); + List<StringName> global_classes; + ScriptServer::get_global_class_list(&global_classes); + for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) { + keywords[String(E->get())] = usertype_color; + } + + /* Autoloads. */ + Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->value(); + if (info.is_singleton) { + keywords[info.name] = usertype_color; + } + } + + const GDScriptLanguage *gdscript = GDScriptLanguage::get_singleton(); + + /* Core types. */ + const Color basetype_color = EDITOR_GET("text_editor/highlighting/base_type_color"); + List<String> core_types; + gdscript->get_core_type_words(&core_types); + for (List<String>::Element *E = core_types.front(); E; E = E->next()) { + keywords[E->get()] = basetype_color; + } + + /* Reserved words. */ + const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); + List<String> keyword_list; + gdscript->get_reserved_words(&keyword_list); + for (List<String>::Element *E = keyword_list.front(); E; E = E->next()) { + keywords[E->get()] = keyword_color; + } + + /* Comments */ + const Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color"); + List<String> comments; + gdscript->get_comment_delimiters(&comments); + for (List<String>::Element *E = comments.front(); E; E = E->next()) { + String comment = E->get(); + String beg = comment.get_slice(" ", 0); + String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String(); + add_color_region(beg, end, comment_color, end == ""); + } + + /* Strings */ + const Color string_color = EDITOR_GET("text_editor/highlighting/string_color"); + List<String> strings; + gdscript->get_string_delimiters(&strings); + for (List<String>::Element *E = strings.front(); E; E = E->next()) { + String string = E->get(); + String beg = string.get_slice(" ", 0); + String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String(); + add_color_region(beg, end, string_color, end == ""); + } + + const Ref<Script> script = _get_edited_resource(); + if (script.is_valid()) { + /* Member types. */ + const Color member_variable_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); + StringName instance_base = script->get_instance_base_type(); + if (instance_base != StringName()) { + List<PropertyInfo> plist; + ClassDB::get_property_list(instance_base, &plist); + for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) { + String name = E->get().name; + if (E->get().usage & PROPERTY_USAGE_CATEGORY || E->get().usage & PROPERTY_USAGE_GROUP || E->get().usage & PROPERTY_USAGE_SUBGROUP) { + continue; + } + if (name.find("/") != -1) { + continue; + } + member_keywords[name] = member_variable_color; + } + + List<String> clist; + ClassDB::get_integer_constant_list(instance_base, &clist); + for (List<String>::Element *E = clist.front(); E; E = E->next()) { + member_keywords[E->get()] = member_variable_color; + } + } + } - const String text_editor_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); - const bool default_theme = text_editor_color_theme == "Default"; + const String text_edit_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); + const bool default_theme = text_edit_color_theme == "Default"; if (default_theme || EditorSettings::get_singleton()->is_dark_theme()) { function_definition_color = Color(0.4, 0.9, 1.0); @@ -377,7 +545,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", function_definition_color); EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", node_path_color); - if (text_editor_color_theme == "Adaptive" || default_theme) { + if (text_edit_color_theme == "Adaptive" || default_theme) { EditorSettings::get_singleton()->set_initial_value( "text_editor/highlighting/gdscript/function_definition_color", function_definition_color, @@ -393,6 +561,31 @@ void GDScriptSyntaxHighlighter::_update_cache() { type_color = EDITOR_GET("text_editor/highlighting/base_type_color"); } -SyntaxHighlighter *GDScriptSyntaxHighlighter::create() { - return memnew(GDScriptSyntaxHighlighter); +void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) { + for (int i = 0; i < p_start_key.length(); i++) { + ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "color regions must start with a symbol"); + } + + if (p_end_key.length() > 0) { + for (int i = 0; i < p_end_key.length(); i++) { + ERR_FAIL_COND_MSG(!is_symbol(p_end_key[i]), "color regions must end with a symbol"); + } + } + + 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."); + } + + ColorRegion color_region; + color_region.color = p_color; + 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); +} + +Ref<EditorSyntaxHighlighter> GDScriptSyntaxHighlighter::_create() const { + Ref<GDScriptSyntaxHighlighter> syntax_highlighter; + syntax_highlighter.instance(); + return syntax_highlighter; } diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index e652fb1471..49357f3d2e 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -31,10 +31,25 @@ #ifndef GDSCRIPT_HIGHLIGHTER_H #define GDSCRIPT_HIGHLIGHTER_H +#include "editor/plugins/script_editor_plugin.h" #include "scene/gui/text_edit.h" -class GDScriptSyntaxHighlighter : public SyntaxHighlighter { +class GDScriptSyntaxHighlighter : public EditorSyntaxHighlighter { + GDCLASS(GDScriptSyntaxHighlighter, EditorSyntaxHighlighter) + private: + struct ColorRegion { + Color color; + String start_key; + String end_key; + bool line_only; + }; + Vector<ColorRegion> color_regions; + Map<int, int> color_region_cache; + + Dictionary keywords; + Dictionary member_keywords; + enum Type { NONE, REGION, @@ -59,14 +74,16 @@ private: Color node_path_color; Color type_color; + void add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false); + public: - static SyntaxHighlighter *create(); + virtual void _update_cache() override; + virtual Dictionary _get_line_syntax_highlighting(int p_line) override; - virtual void _update_cache(); - virtual Map<int, TextEdit::HighlighterInfo> _get_line_syntax_highlighting(int p_line); + virtual String _get_name() const override; + virtual Array _get_supported_languages() const override; - virtual String get_name() const; - virtual List<String> get_supported_languages(); + virtual Ref<EditorSyntaxHighlighter> _create() const override; }; #endif // GDSCRIPT_HIGHLIGHTER_H diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp new file mode 100644 index 0000000000..6d454e43f2 --- /dev/null +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -0,0 +1,155 @@ +/*************************************************************************/ +/* gdscript_translation_parser_plugin.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_translation_parser_plugin.h" + +#include "core/io/resource_loader.h" +#include "modules/gdscript/gdscript.h" + +void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const { + 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 err; + RES loaded_res = ResourceLoader::load(p_path, "", false, &err); + if (err) { + ERR_PRINT("Failed to load " + p_path); + return err; + } + + 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)); + } + + r_extracted_strings->append_array(parsed_strings); + + 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::_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); + } + } + } +} + +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\\\\]*\\)"); +} diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h new file mode 100644 index 0000000000..9fa4b69f01 --- /dev/null +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* gdscript_translation_parser_plugin.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_TRANSLATION_PARSER_PLUGIN_H +#define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H + +#include "editor/editor_translation_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; + + 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); + +public: + virtual Error parse_file(const String &p_path, Vector<String> *r_extracted_strings) override; + virtual void get_recognized_extensions(List<String> *r_extensions) const override; + + GDScriptEditorTranslationParserPlugin(); +}; + +#endif // GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 9a4fa5cc86..9170255c02 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -39,17 +39,19 @@ #include "core/os/file_access.h" #include "core/os/os.h" #include "core/project_settings.h" +#include "gdscript_analyzer.h" +#include "gdscript_cache.h" #include "gdscript_compiler.h" +#include "gdscript_parser.h" +#include "gdscript_warning.h" /////////////////////////// GDScriptNativeClass::GDScriptNativeClass(const StringName &p_name) { - name = p_name; } bool GDScriptNativeClass::_get(const StringName &p_name, Variant &r_ret) const { - bool ok; int v = ClassDB::get_integer_constant(name, p_name, &ok); @@ -62,12 +64,10 @@ bool GDScriptNativeClass::_get(const StringName &p_name, Variant &r_ret) const { } void GDScriptNativeClass::_bind_methods() { - ClassDB::bind_method(D_METHOD("new"), &GDScriptNativeClass::_new); } Variant GDScriptNativeClass::_new() { - Object *o = instance(); ERR_FAIL_COND_V_MSG(!o, Variant(), "Class type: '" + String(name) + "' is not instantiable."); @@ -80,12 +80,21 @@ Variant GDScriptNativeClass::_new() { } Object *GDScriptNativeClass::instance() { - return ClassDB::instance(name); } -GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error) { +void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) { + GDScript *base = p_script->_base; + if (base != nullptr) { + _super_implicit_constructor(base, p_instance, r_error); + if (r_error.error != Callable::CallError::CALL_OK) { + return; + } + } + p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error); +} +GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error) { /* STEP 1, CREATE */ GDScriptInstance *instance = memnew(GDScriptInstance); @@ -103,15 +112,12 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco instance->owner->set_script_instance(instance); /* STEP 2, INITIALIZE AND CONSTRUCT */ - { MutexLock lock(GDScriptLanguage::singleton->lock); - instances.insert(instance->owner); } - initializer->call(instance, p_args, p_argcount, r_error); - + _super_implicit_constructor(this, instance, r_error); if (r_error.error != Callable::CallError::CALL_OK) { instance->script = Ref<GDScript>(); instance->owner->set_script_instance(nullptr); @@ -119,16 +125,29 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco MutexLock lock(GDScriptLanguage::singleton->lock); instances.erase(p_owner); } - - ERR_FAIL_COND_V(r_error.error != Callable::CallError::CALL_OK, nullptr); //error constructing + ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance."); } + if (p_argcount < 0) { + return instance; + } + if (initializer != nullptr) { + initializer->call(instance, p_args, p_argcount, r_error); + if (r_error.error != Callable::CallError::CALL_OK) { + instance->script = Ref<GDScript>(); + instance->owner->set_script_instance(nullptr); + { + MutexLock lock(GDScriptLanguage::singleton->lock); + instances.erase(p_owner); + } + ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance."); + } + } //@TODO make thread safe return instance; } Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - /* STEP 1, CREATE */ if (!valid) { @@ -174,7 +193,6 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr } bool GDScript::can_instance() const { - #ifdef TOOLS_ENABLED return valid && (tool || ScriptServer::is_scripting_enabled()); #else @@ -183,7 +201,6 @@ bool GDScript::can_instance() const { } Ref<Script> GDScript::get_base_script() const { - if (_base) { return Ref<GDScript>(_base); } else { @@ -192,16 +209,16 @@ Ref<Script> GDScript::get_base_script() const { } StringName GDScript::get_instance_base_type() const { - - if (native.is_valid()) + if (native.is_valid()) { return native->get_name(); - if (base.is_valid() && base->is_valid()) + } + if (base.is_valid() && base->is_valid()) { return base->get_instance_base_type(); + } return StringName(); } struct _GDScriptMemberSort { - int index; StringName name; _FORCE_INLINE_ bool operator<(const _GDScriptMemberSort &p_member) const { return index < p_member.index; } @@ -210,13 +227,11 @@ struct _GDScriptMemberSort { #ifdef TOOLS_ENABLED void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { - placeholders.erase(p_placeholder); } #endif void GDScript::get_script_method_list(List<MethodInfo> *p_list) const { - const GDScript *current = this; while (current) { for (const Map<StringName, GDScriptFunction *>::Element *E = current->member_functions.front(); E; E = E->next()) { @@ -236,15 +251,12 @@ void GDScript::get_script_method_list(List<MethodInfo> *p_list) const { } void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const { - const GDScript *sptr = this; List<PropertyInfo> props; while (sptr) { - Vector<_GDScriptMemberSort> msort; for (Map<StringName, PropertyInfo>::Element *E = sptr->member_info.front(); E; E = E->next()) { - _GDScriptMemberSort ms; ERR_CONTINUE(!sptr->member_indices.has(E->key())); ms.index = sptr->member_indices[E->key()].index; @@ -255,7 +267,6 @@ void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const { msort.sort(); msort.invert(); for (int i = 0; i < msort.size(); i++) { - props.push_front(sptr->member_info[msort[i].name]); } @@ -268,15 +279,14 @@ void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const { } bool GDScript::has_method(const StringName &p_method) const { - return member_functions.has(p_method); } MethodInfo GDScript::get_method_info(const StringName &p_method) const { - const Map<StringName, GDScriptFunction *>::Element *E = member_functions.find(p_method); - if (!E) + if (!E) { return MethodInfo(); + } GDScriptFunction *func = E->get(); MethodInfo mi; @@ -290,7 +300,6 @@ MethodInfo GDScript::get_method_info(const StringName &p_method) const { } bool GDScript::get_property_default_value(const StringName &p_property, Variant &r_value) const { - #ifdef TOOLS_ENABLED const Map<StringName, Variant>::Element *E = member_default_values_cache.find(p_property); @@ -307,14 +316,13 @@ bool GDScript::get_property_default_value(const StringName &p_property, Variant } ScriptInstance *GDScript::instance_create(Object *p_this) { - GDScript *top = this; - while (top->_base) + while (top->_base) { top = top->_base; + } if (top->native.is_valid()) { if (!ClassDB::is_parent_class(p_this->get_class_name(), top->native->get_name())) { - if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); } @@ -338,24 +346,23 @@ PlaceHolderScriptInstance *GDScript::placeholder_instance_create(Object *p_this) } bool GDScript::instance_has(const Object *p_this) const { - MutexLock lock(GDScriptLanguage::singleton->lock); return instances.has((Object *)p_this); } bool GDScript::has_source_code() const { - return source != ""; } -String GDScript::get_source_code() const { +String GDScript::get_source_code() const { return source; } -void GDScript::set_source_code(const String &p_code) { - if (source == p_code) +void GDScript::set_source_code(const String &p_code) { + if (source == p_code) { return; + } source = p_code; #ifdef TOOLS_ENABLED source_changed_cache = true; @@ -364,7 +371,6 @@ void GDScript::set_source_code(const String &p_code) { #ifdef TOOLS_ENABLED void GDScript::_update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames) { - if (base_cache.is_valid()) { base_cache->_update_exports_values(values, propnames); } @@ -379,10 +385,15 @@ void GDScript::_update_exports_values(Map<StringName, Variant> &values, List<Pro } #endif -bool GDScript::_update_exports() { - +bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) { #ifdef TOOLS_ENABLED + static Vector<GDScript *> base_caches; + if (!p_recursive_call) { + base_caches.clear(); + } + base_caches.append(this); + bool changed = false; if (source_changed_cache) { @@ -391,21 +402,20 @@ bool GDScript::_update_exports() { String basedir = path; - if (basedir == "") + if (basedir == "") { basedir = get_path(); + } - if (basedir != "") + if (basedir != "") { basedir = basedir.get_base_dir(); + } GDScriptParser parser; - Error err = parser.parse(source, basedir, true, path); + GDScriptAnalyzer analyzer(&parser); + Error err = parser.parse(source, path, false); - if (err == OK) { - - const GDScriptParser::Node *root = parser.get_parse_tree(); - ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, false); - - const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(root); + if (err == OK && analyzer.analyze() == OK) { + const GDScriptParser::ClassNode *c = parser.get_tree(); if (base_cache.is_valid()) { base_cache->inheriters_cache.erase(get_instance_id()); @@ -414,32 +424,29 @@ bool GDScript::_update_exports() { if (c->extends_used) { String path = ""; - if (String(c->extends_file) != "" && String(c->extends_file) != get_path()) { - path = c->extends_file; + if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) { + path = c->extends_path; if (path.is_rel_path()) { - String base = get_path(); if (base == "" || base.is_rel_path()) { - ERR_PRINT(("Could not resolve relative path for parent class: " + path).utf8().get_data()); } else { path = base.get_base_dir().plus_file(path); } } - } else if (c->extends_class.size() != 0) { - String base = c->extends_class[0]; + } else if (c->extends.size() != 0) { + const StringName &base = c->extends[0]; - if (ScriptServer::is_global_class(base)) + if (ScriptServer::is_global_class(base)) { path = ScriptServer::get_global_class_path(base); + } } if (path != "") { if (path != get_path()) { - Ref<GDScript> bf = ResourceLoader::load(path); if (bf.is_valid()) { - base_cache = bf; bf->inheriters_cache.insert(get_instance_id()); } @@ -451,19 +458,36 @@ bool GDScript::_update_exports() { members_cache.clear(); member_default_values_cache.clear(); + _signals.clear(); - for (int i = 0; i < c->variables.size(); i++) { - if (c->variables[i]._export.type == Variant::NIL) - continue; - - members_cache.push_back(c->variables[i]._export); - member_default_values_cache[c->variables[i].identifier] = c->variables[i].default_value; - } + for (int i = 0; i < c->members.size(); i++) { + const GDScriptParser::ClassNode::Member &member = c->members[i]; - _signals.clear(); + switch (member.type) { + case GDScriptParser::ClassNode::Member::VARIABLE: { + if (!member.variable->exported) { + continue; + } - for (int i = 0; i < c->_signals.size(); i++) { - _signals[c->_signals[i].name] = c->_signals[i].arguments; + members_cache.push_back(member.variable->export_info); + Variant default_value; + if (member.variable->initializer && member.variable->initializer->is_constant) { + default_value = member.variable->initializer->reduced_value; + } + member_default_values_cache[member.variable->identifier->name] = default_value; + } break; + case GDScriptParser::ClassNode::Member::SIGNAL: { + // TODO: Cache this in parser to avoid loops like this. + Vector<StringName> parameters_names; + parameters_names.resize(member.signal->parameters.size()); + for (int j = 0; j < member.signal->parameters.size(); j++) { + parameters_names.write[j] = member.signal->parameters[j]->identifier->name; + } + _signals[member.signal->identifier->name] = parameters_names; + } break; + default: + break; // Nothing. + } } } else { placeholder_fallback_enabled = true; @@ -476,7 +500,24 @@ bool GDScript::_update_exports() { placeholder_fallback_enabled = false; if (base_cache.is_valid() && base_cache->is_valid()) { - if (base_cache->_update_exports()) { + for (int i = 0; i < base_caches.size(); i++) { + if (base_caches[i] == base_cache.ptr()) { + if (r_err) { + *r_err = true; + } + valid = false; // to show error in the editor + base_cache->valid = false; + base_cache->inheriters_cache.clear(); // to prevent future stackoverflows + base_cache.unref(); + base.unref(); + _base = nullptr; + ERR_FAIL_V_MSG(false, "Cyclic inheritance in script class."); + } + } + if (base_cache->_update_exports(r_err, true)) { + if (r_err && *r_err) { + return false; + } changed = true; } } @@ -501,18 +542,22 @@ bool GDScript::_update_exports() { } void GDScript::update_exports() { - #ifdef TOOLS_ENABLED - _update_exports(); + bool cyclic_error = false; + _update_exports(&cyclic_error); + if (cyclic_error) { + return; + } Set<ObjectID> copy = inheriters_cache; //might get modified for (Set<ObjectID>::Element *E = copy.front(); E; E = E->next()) { Object *id = ObjectDB::get_instance(E->get()); GDScript *s = Object::cast_to<GDScript>(id); - if (!s) + if (!s) { continue; + } s->update_exports(); } @@ -520,16 +565,13 @@ void GDScript::update_exports() { } void GDScript::_set_subclass_path(Ref<GDScript> &p_sc, const String &p_path) { - p_sc->path = p_path; for (Map<StringName, Ref<GDScript>>::Element *E = p_sc->subclasses.front(); E; E = E->next()) { - _set_subclass_path(E->get(), p_path); } } Error GDScript::reload(bool p_keep_state) { - bool has_instances; { MutexLock lock(GDScriptLanguage::singleton->lock); @@ -541,11 +583,13 @@ Error GDScript::reload(bool p_keep_state) { String basedir = path; - if (basedir == "") + if (basedir == "") { basedir = get_path(); + } - if (basedir != "") + if (basedir != "") { basedir = basedir.get_base_dir(); + } if (source.find("%BASE%") != -1) { //loading a template, don't parse @@ -554,22 +598,34 @@ Error GDScript::reload(bool p_keep_state) { valid = false; GDScriptParser parser; - Error err = parser.parse(source, basedir, false, path); + Error err = parser.parse(source, path, false); if (err) { if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_error_line(), "Parser Error: " + parser.get_error()); + GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); } - _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_error_line(), ("Parse Error: " + parser.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT); + // TODO: Show all error messages. + _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); ERR_FAIL_V(ERR_PARSE_ERROR); } - bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool_script(); + GDScriptAnalyzer analyzer(&parser); + err = analyzer.analyze(); + + if (err) { + if (EngineDebugger::is_active()) { + GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); + } + // TODO: Show all error messages. + _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); + ERR_FAIL_V(ERR_PARSE_ERROR); + } + + bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool(); GDScriptCompiler compiler; err = compiler.compile(&parser, this, p_keep_state); if (err) { - if (can_run) { if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error()); @@ -585,7 +641,7 @@ Error GDScript::reload(bool p_keep_state) { const GDScriptWarning &warning = E->get(); if (EngineDebugger::is_active()) { Vector<ScriptLanguage::StackInfo> si; - EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si); + EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.start_line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si); } } #endif @@ -593,7 +649,6 @@ Error GDScript::reload(bool p_keep_state) { valid = true; for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) { - _set_subclass_path(E->get(), path); } @@ -603,12 +658,10 @@ Error GDScript::reload(bool p_keep_state) { } ScriptLanguage *GDScript::get_language() const { - return GDScriptLanguage::get_singleton(); } void GDScript::get_constants(Map<StringName, Variant> *p_constants) { - if (p_constants) { for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { (*p_constants)[E->key()] = E->value(); @@ -638,12 +691,16 @@ uint16_t GDScript::get_rpc_method_id(const StringName &p_method) const { } StringName GDScript::get_rpc_method(const uint16_t p_rpc_method_id) const { - ERR_FAIL_COND_V(p_rpc_method_id >= rpc_functions.size(), StringName()); + if (p_rpc_method_id >= rpc_functions.size()) { + return StringName(); + } return rpc_functions[p_rpc_method_id].name; } MultiplayerAPI::RPCMode GDScript::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const { - ERR_FAIL_COND_V(p_rpc_method_id >= rpc_functions.size(), MultiplayerAPI::RPC_MODE_DISABLED); + if (p_rpc_method_id >= rpc_functions.size()) { + return MultiplayerAPI::RPC_MODE_DISABLED; + } return rpc_functions[p_rpc_method_id].mode; } @@ -665,12 +722,16 @@ uint16_t GDScript::get_rset_property_id(const StringName &p_variable) const { } StringName GDScript::get_rset_property(const uint16_t p_rset_member_id) const { - ERR_FAIL_COND_V(p_rset_member_id >= rpc_variables.size(), StringName()); + if (p_rset_member_id >= rpc_variables.size()) { + return StringName(); + } return rpc_variables[p_rset_member_id].name; } MultiplayerAPI::RPCMode GDScript::get_rset_mode_by_id(const uint16_t p_rset_member_id) const { - ERR_FAIL_COND_V(p_rset_member_id >= rpc_variables.size(), MultiplayerAPI::RPC_MODE_DISABLED); + if (p_rset_member_id >= rpc_variables.size()) { + return MultiplayerAPI::RPC_MODE_DISABLED; + } return rpc_variables[p_rset_member_id].mode; } @@ -679,13 +740,10 @@ MultiplayerAPI::RPCMode GDScript::get_rset_mode(const StringName &p_variable) co } Variant GDScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - GDScript *top = this; while (top) { - Map<StringName, GDScriptFunction *>::Element *E = top->member_functions.find(p_method); if (E) { - ERR_FAIL_COND_V_MSG(!E->get()->is_static(), Variant(), "Can't call non-static function '" + String(p_method) + "' in script."); return E->get()->call(nullptr, p_args, p_argcount, r_error); @@ -699,16 +757,12 @@ Variant GDScript::call(const StringName &p_method, const Variant **p_args, int p } bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { - { - const GDScript *top = this; while (top) { - { const Map<StringName, Variant>::Element *E = top->constants.find(p_name); if (E) { - r_ret = E->get(); return true; } @@ -717,7 +771,6 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { { const Map<StringName, Ref<GDScript>>::Element *E = subclasses.find(p_name); if (E) { - r_ret = E->get(); return true; } @@ -726,7 +779,6 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { } if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) { - r_ret = get_source_code(); return true; } @@ -734,120 +786,42 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { return false; } -bool GDScript::_set(const StringName &p_name, const Variant &p_value) { +bool GDScript::_set(const StringName &p_name, const Variant &p_value) { if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) { - set_source_code(p_value); reload(); - } else + } else { return false; + } return true; } void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const { - p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); } void GDScript::_bind_methods() { - ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo("new")); ClassDB::bind_method(D_METHOD("get_as_byte_code"), &GDScript::get_as_byte_code); } Vector<uint8_t> GDScript::get_as_byte_code() const { - - GDScriptTokenizerBuffer tokenizer; - return tokenizer.parse_code_string(source); + return Vector<uint8_t>(); }; +// TODO: Fully remove this. There's not this kind of "bytecode" anymore. Error GDScript::load_byte_code(const String &p_path) { - - Vector<uint8_t> bytecode; - - if (p_path.ends_with("gde")) { - - FileAccess *fa = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V(!fa, ERR_CANT_OPEN); - - FileAccessEncrypted *fae = memnew(FileAccessEncrypted); - ERR_FAIL_COND_V(!fae, ERR_CANT_OPEN); - - Vector<uint8_t> key; - key.resize(32); - for (int i = 0; i < key.size(); i++) { - key.write[i] = script_encryption_key[i]; - } - - Error err = fae->open_and_parse(fa, key, FileAccessEncrypted::MODE_READ); - - if (err) { - fa->close(); - memdelete(fa); - memdelete(fae); - - ERR_FAIL_COND_V(err, err); - } - - bytecode.resize(fae->get_len()); - fae->get_buffer(bytecode.ptrw(), bytecode.size()); - fae->close(); - memdelete(fae); - - } else { - - bytecode = FileAccess::get_file_as_array(p_path); - } - - ERR_FAIL_COND_V(bytecode.size() == 0, ERR_PARSE_ERROR); - path = p_path; - - String basedir = path; - - if (basedir == "") - basedir = get_path(); - - if (basedir != "") - basedir = basedir.get_base_dir(); - - valid = false; - GDScriptParser parser; - Error err = parser.parse_bytecode(bytecode, basedir, get_path()); - if (err) { - _err_print_error("GDScript::load_byte_code", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_error_line(), ("Parse Error: " + parser.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT); - ERR_FAIL_V(ERR_PARSE_ERROR); - } - - GDScriptCompiler compiler; - err = compiler.compile(&parser, this); - - if (err) { - _err_print_error("GDScript::load_byte_code", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT); - ERR_FAIL_V(ERR_COMPILATION_FAILED); - } - - valid = true; - - for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) { - - _set_subclass_path(E->get(), path); - } - - _init_rpc_methods_properties(); - - return OK; + return ERR_COMPILATION_FAILED; } Error GDScript::load_source_code(const String &p_path) { - Vector<uint8_t> sourcef; Error err; FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err); if (err) { - ERR_FAIL_COND_V(err, err); } @@ -862,7 +836,6 @@ Error GDScript::load_source_code(const String &p_path) { String s; if (s.parse_utf8((const char *)w)) { - ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode."); } @@ -875,29 +848,45 @@ Error GDScript::load_source_code(const String &p_path) { } const Map<StringName, GDScriptFunction *> &GDScript::debug_get_member_functions() const { - return member_functions; } StringName GDScript::debug_get_member_by_index(int p_idx) const { - for (const Map<StringName, MemberInfo>::Element *E = member_indices.front(); E; E = E->next()) { - - if (E->get().index == p_idx) + if (E->get().index == p_idx) { return E->key(); + } } return "<error>"; } Ref<GDScript> GDScript::get_base() const { - return base; } +bool GDScript::inherits_script(const Ref<Script> &p_script) const { + Ref<GDScript> gd = p_script; + if (gd.is_null()) { + return false; + } + + const GDScript *s = this; + + while (s) { + if (s == p_script.ptr()) { + return true; + } + s = s->_base; + } + + return false; +} + bool GDScript::has_script_signal(const StringName &p_signal) const { - if (_signals.has(p_signal)) + if (_signals.has(p_signal)) { return true; + } if (base.is_valid()) { return base->has_script_signal(p_signal); } @@ -908,10 +897,9 @@ bool GDScript::has_script_signal(const StringName &p_signal) const { #endif return false; } -void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { +void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { for (const Map<StringName, Vector<StringName>>::Element *E = _signals.front(); E; E = E->next()) { - MethodInfo mi; mi.name = E->key(); for (int i = 0; i < E->get().size(); i++) { @@ -935,7 +923,6 @@ void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { GDScript::GDScript() : script_list(this) { - valid = false; subclass_count = 0; initializer = nullptr; @@ -980,8 +967,9 @@ void GDScript::_save_orphaned_subclasses() { for (int i = 0; i < weak_subclasses.size(); i++) { ClassRefWithName subclass = weak_subclasses[i]; Object *obj = ObjectDB::get_instance(subclass.id); - if (!obj) + if (!obj) { continue; + } // subclass is not released GDScriptLanguage::get_singleton()->add_orphan_subclass(subclass.fully_qualified_name, subclass.id); } @@ -1022,13 +1010,15 @@ void GDScript::_init_rpc_methods_properties() { } } - if (cscript != this) + if (cscript != this) { sub_E = sub_E->next(); + } - if (sub_E) + if (sub_E) { cscript = sub_E->get().ptr(); - else + } else { cscript = nullptr; + } } // Sort so we are 100% that they are always the same. @@ -1037,10 +1027,21 @@ void GDScript::_init_rpc_methods_properties() { } GDScript::~GDScript() { + { + MutexLock lock(GDScriptLanguage::get_singleton()->lock); + + while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { + E->self()->_clear_stack(); + pending_func_states.remove(E); + } + } + for (Map<StringName, GDScriptFunction *>::Element *E = member_functions.front(); E; E = E->next()) { memdelete(E->get()); } + GDScriptCache::remove_script(get_path()); + _save_orphaned_subclasses(); #ifdef DEBUG_ENABLED @@ -1057,7 +1058,6 @@ GDScript::~GDScript() { ////////////////////////////// bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { - //member { const Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.find(p_name); @@ -1092,17 +1092,16 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { GDScript *sptr = script.ptr(); while (sptr) { - Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set); if (E) { - Variant name = p_name; const Variant *args[2] = { &name, &p_value }; Callable::CallError err; Variant ret = E->get()->call(this, (const Variant **)args, 2, err); - if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) + if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) { return true; + } } sptr = sptr->_base; } @@ -1111,10 +1110,8 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { } bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { - const GDScript *sptr = script.ptr(); while (sptr) { - { const Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.find(p_name); if (E) { @@ -1131,7 +1128,6 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } { - const GDScript *sl = sptr; while (sl) { const Map<StringName, Variant>::Element *E = sl->constants.find(p_name); @@ -1144,9 +1140,34 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } { + // Signals. + const GDScript *sl = sptr; + while (sl) { + const Map<StringName, Vector<StringName>>::Element *E = sl->_signals.find(p_name); + if (E) { + r_ret = Signal(this->owner, E->key()); + return true; //index found + } + sl = sl->_base; + } + } + + { + // Methods. + const GDScript *sl = sptr; + while (sl) { + const Map<StringName, GDScriptFunction *>::Element *E = sl->member_functions.find(p_name); + if (E) { + r_ret = Callable(this->owner, E->key()); + return true; //index found + } + sl = sl->_base; + } + } + + { const Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get); if (E) { - Variant name = p_name; const Variant *args[1] = { &name }; @@ -1165,20 +1186,20 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const { - const GDScript *sptr = script.ptr(); while (sptr) { - if (sptr->member_info.has(p_name)) { - if (r_is_valid) + if (r_is_valid) { *r_is_valid = true; + } return sptr->member_info[p_name].type; } sptr = sptr->_base; } - if (r_is_valid) + if (r_is_valid) { *r_is_valid = false; + } return Variant::NIL; } @@ -1189,19 +1210,15 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const List<PropertyInfo> props; while (sptr) { - const Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list); if (E) { - Callable::CallError err; Variant ret = const_cast<GDScriptFunction *>(E->get())->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err); if (err.error == Callable::CallError::CALL_OK) { - ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries."); Array arr = ret; for (int i = 0; i < arr.size(); i++) { - Dictionary d = arr[i]; ERR_CONTINUE(!d.has("name")); ERR_CONTINUE(!d.has("type")); @@ -1210,12 +1227,15 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX); pinfo.name = d["name"]; ERR_CONTINUE(pinfo.name == ""); - if (d.has("hint")) + if (d.has("hint")) { pinfo.hint = PropertyHint(d["hint"].operator int()); - if (d.has("hint_string")) + } + if (d.has("hint_string")) { pinfo.hint_string = d["hint_string"]; - if (d.has("usage")) + } + if (d.has("usage")) { pinfo.usage = d["usage"]; + } props.push_back(pinfo); } @@ -1226,7 +1246,6 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const Vector<_GDScriptMemberSort> msort; for (Map<StringName, PropertyInfo>::Element *F = sptr->member_info.front(); F; F = F->next()) { - _GDScriptMemberSort ms; ERR_CONTINUE(!sptr->member_indices.has(F->key())); ms.index = sptr->member_indices[F->key()].index; @@ -1237,7 +1256,6 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const msort.sort(); msort.invert(); for (int i = 0; i < msort.size(); i++) { - props.push_front(sptr->member_info[msort[i].name]); } @@ -1245,23 +1263,20 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const } for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - p_properties->push_back(E->get()); } } void GDScriptInstance::get_method_list(List<MethodInfo> *p_list) const { - const GDScript *sptr = script.ptr(); while (sptr) { - for (Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.front(); E; E = E->next()) { - MethodInfo mi; mi.name = E->key(); mi.flags |= METHOD_FLAG_FROM_SCRIPT; - for (int i = 0; i < E->get()->get_argument_count(); i++) + for (int i = 0; i < E->get()->get_argument_count(); i++) { mi.arguments.push_back(PropertyInfo(Variant::NIL, "arg" + itos(i))); + } p_list->push_back(mi); } sptr = sptr->_base; @@ -1269,19 +1284,19 @@ void GDScriptInstance::get_method_list(List<MethodInfo> *p_list) const { } bool GDScriptInstance::has_method(const StringName &p_method) const { - const GDScript *sptr = script.ptr(); while (sptr) { const Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method); - if (E) + if (E) { return true; + } sptr = sptr->_base; } return false; } -Variant GDScriptInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +Variant GDScriptInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { GDScript *sptr = script.ptr(); while (sptr) { Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method); @@ -1294,42 +1309,7 @@ Variant GDScriptInstance::call(const StringName &p_method, const Variant **p_arg return Variant(); } -void GDScriptInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) { - - GDScript *sptr = script.ptr(); - Callable::CallError ce; - - while (sptr) { - Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method); - if (E) { - E->get()->call(this, p_args, p_argcount, ce); - } - sptr = sptr->_base; - } -} - -void GDScriptInstance::_ml_call_reversed(GDScript *sptr, const StringName &p_method, const Variant **p_args, int p_argcount) { - - if (sptr->_base) - _ml_call_reversed(sptr->_base, p_method, p_args, p_argcount); - - Callable::CallError ce; - - Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method); - if (E) { - E->get()->call(this, p_args, p_argcount, ce); - } -} - -void GDScriptInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) { - - if (script.ptr()) { - _ml_call_reversed(script.ptr(), p_method, p_args, p_argcount); - } -} - void GDScriptInstance::notification(int p_notification) { - //notification is not virtual, it gets called at ALL levels just like in C. Variant value = p_notification; const Variant *args[1] = { &value }; @@ -1354,27 +1334,28 @@ String GDScriptInstance::to_string(bool *r_valid) { Variant ret = call(CoreStringNames::get_singleton()->_to_string, nullptr, 0, ce); if (ce.error == Callable::CallError::CALL_OK) { if (ret.get_type() != Variant::STRING) { - if (r_valid) + if (r_valid) { *r_valid = false; + } ERR_FAIL_V_MSG(String(), "Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String."); } - if (r_valid) + if (r_valid) { *r_valid = true; + } return ret.operator String(); } } - if (r_valid) + if (r_valid) { *r_valid = false; + } return String(); } Ref<Script> GDScriptInstance::get_script() const { - return script; } ScriptLanguage *GDScriptInstance::get_language() { - return GDScriptLanguage::get_singleton(); } @@ -1419,7 +1400,6 @@ MultiplayerAPI::RPCMode GDScriptInstance::get_rset_mode(const StringName &p_vari } void GDScriptInstance::reload_members() { - #ifdef DEBUG_ENABLED members.resize(script->member_indices.size()); //resize @@ -1429,7 +1409,6 @@ void GDScriptInstance::reload_members() { //pass the values to the new indices for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) { - if (member_indices_cache.has(E->key())) { Variant value = members[member_indices_cache[E->key()]]; new_members.write[E->get().index] = value; @@ -1442,7 +1421,6 @@ void GDScriptInstance::reload_members() { //pass the values to the new indices member_indices_cache.clear(); for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) { - member_indices_cache[E->key()] = E->get().index; } @@ -1455,9 +1433,14 @@ GDScriptInstance::GDScriptInstance() { } GDScriptInstance::~GDScriptInstance() { - if (script.is_valid() && owner) { - MutexLock lock(GDScriptLanguage::singleton->lock); + MutexLock lock(GDScriptLanguage::get_singleton()->lock); + while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { + E->self()->_clear_stack(); + pending_func_states.remove(E); + } + + if (script.is_valid() && owner) { script->instances.erase(owner); } } @@ -1467,14 +1450,12 @@ GDScriptInstance::~GDScriptInstance() { GDScriptLanguage *GDScriptLanguage::singleton = nullptr; String GDScriptLanguage::get_name() const { - return "GDScript"; } /* LANGUAGE FUNCTIONS */ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_value) { - if (globals.has(p_name)) { //overwrite existing global_array.write[globals[p_name]] = p_value; @@ -1486,7 +1467,6 @@ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_va } void GDScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) { - _add_global(p_variable, p_value); } @@ -1500,11 +1480,9 @@ void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) { } void GDScriptLanguage::init() { - //populate global constants int gcc = GlobalConstants::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)); } @@ -1518,14 +1496,15 @@ void GDScriptLanguage::init() { List<StringName> class_list; ClassDB::get_class_list(&class_list); for (List<StringName>::Element *E = class_list.front(); E; E = E->next()) { - StringName n = E->get(); String s = String(n); - if (s.begins_with("_")) + if (s.begins_with("_")) { n = s.substr(1, s.length()); + } - if (globals.has(n)) + if (globals.has(n)) { continue; + } Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(E->get())); _add_global(n, nc); } @@ -1535,29 +1514,27 @@ void GDScriptLanguage::init() { List<Engine::Singleton> singletons; Engine::get_singleton()->get_singletons(&singletons); for (List<Engine::Singleton>::Element *E = singletons.front(); E; E = E->next()) { - _add_global(E->get().name, E->get().ptr); } } String GDScriptLanguage::get_type() const { - return "GDScript"; } -String GDScriptLanguage::get_extension() const { +String GDScriptLanguage::get_extension() const { return "gd"; } -Error GDScriptLanguage::execute_file(const String &p_path) { +Error GDScriptLanguage::execute_file(const String &p_path) { // ?? return OK; } + void GDScriptLanguage::finish() { } void GDScriptLanguage::profiling_start() { - #ifdef DEBUG_ENABLED MutexLock lock(this->lock); @@ -1580,7 +1557,6 @@ void GDScriptLanguage::profiling_start() { } void GDScriptLanguage::profiling_stop() { - #ifdef DEBUG_ENABLED MutexLock lock(this->lock); @@ -1589,7 +1565,6 @@ void GDScriptLanguage::profiling_stop() { } int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) { - int current = 0; #ifdef DEBUG_ENABLED @@ -1597,8 +1572,9 @@ int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr, SelfList<GDScriptFunction> *elem = function_list.first(); while (elem) { - if (current >= p_info_max) + if (current >= p_info_max) { break; + } p_info_arr[current].call_count = elem->self()->profile.call_count; p_info_arr[current].self_time = elem->self()->profile.self_time; p_info_arr[current].total_time = elem->self()->profile.total_time; @@ -1612,7 +1588,6 @@ int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr, } int GDScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) { - int current = 0; #ifdef DEBUG_ENABLED @@ -1620,8 +1595,9 @@ int GDScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_ SelfList<GDScriptFunction> *elem = function_list.first(); while (elem) { - if (current >= p_info_max) + if (current >= p_info_max) { break; + } if (elem->self()->profile.last_frame_call_count > 0) { p_info_arr[current].call_count = elem->self()->profile.last_frame_call_count; p_info_arr[current].self_time = elem->self()->profile.last_frame_self_time; @@ -1637,12 +1613,11 @@ int GDScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_ } struct GDScriptDepSort { - //must support sorting so inheritance works properly (parent must be reloaded first) bool operator()(const Ref<GDScript> &A, const Ref<GDScript> &B) const { - - if (A == B) + if (A == B) { return false; //shouldn't happen but.. + } const GDScript *I = B->get_base().ptr(); while (I) { if (I == A.ptr()) { @@ -1658,7 +1633,6 @@ struct GDScriptDepSort { }; void GDScriptLanguage::reload_all_scripts() { - #ifdef DEBUG_ENABLED print_verbose("GDScript: Reloading all scripts"); List<Ref<GDScript>> scripts; @@ -1680,7 +1654,6 @@ void GDScriptLanguage::reload_all_scripts() { scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order for (List<Ref<GDScript>>::Element *E = scripts.front(); E; E = E->next()) { - print_verbose("GDScript: Reloading: " + E->get()->get_path()); E->get()->load_source_code(E->get()->get_path()); E->get()->reload(true); @@ -1689,7 +1662,6 @@ void GDScriptLanguage::reload_all_scripts() { } void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { - #ifdef DEBUG_ENABLED List<Ref<GDScript>> scripts; @@ -1699,7 +1671,6 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so SelfList<GDScript> *elem = script_list.first(); while (elem) { if (elem->self()->get_path().is_resource_file()) { - scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } elem = elem->next(); @@ -1715,16 +1686,15 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order for (List<Ref<GDScript>>::Element *E = scripts.front(); E; E = E->next()) { - bool reload = E->get() == p_script || to_reload.has(E->get()->get_base()); - if (!reload) + if (!reload) { continue; + } to_reload.insert(E->get(), Map<ObjectID, List<Pair<StringName, Variant>>>()); if (!p_soft_reload) { - //save state and remove script from instances Map<ObjectID, List<Pair<StringName, Variant>>> &map = to_reload[E->get()]; @@ -1733,7 +1703,6 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so //save instance info List<Pair<StringName, Variant>> state; if (obj->get_script_instance()) { - obj->get_script_instance()->get_property_state(state); map[obj->get_instance_id()] = state; obj->set_script(Variant()); @@ -1748,7 +1717,6 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so //save instance info if (obj->get_script_instance()) { - map.insert(obj->get_instance_id(), List<Pair<StringName, Variant>>()); List<Pair<StringName, Variant>> &state = map[obj->get_instance_id()]; obj->get_script_instance()->get_property_state(state); @@ -1768,18 +1736,17 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so } for (Map<Ref<GDScript>, Map<ObjectID, List<Pair<StringName, Variant>>>>::Element *E = to_reload.front(); E; E = E->next()) { - Ref<GDScript> scr = E->key(); scr->reload(p_soft_reload); //restore state if saved for (Map<ObjectID, List<Pair<StringName, Variant>>>::Element *F = E->get().front(); F; F = F->next()) { - List<Pair<StringName, Variant>> &saved_state = F->get(); Object *obj = ObjectDB::get_instance(F->key()); - if (!obj) + if (!obj) { continue; + } if (!p_soft_reload) { //clear it just in case (may be a pending reload state) @@ -1818,7 +1785,6 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so } void GDScriptLanguage::frame() { - calls = 0; #ifdef DEBUG_ENABLED @@ -1842,7 +1808,7 @@ void GDScriptLanguage::frame() { /* EDITOR FUNCTIONS */ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { - + // TODO: Add annotations here? static const char *_reserved_words[] = { // operators "and", @@ -1865,6 +1831,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { // functions "as", "assert", + "await", "breakpoint", "class", "class_name", @@ -1872,15 +1839,13 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "is", "func", "preload", - "setget", "signal", - "tool", + "super", + "trait", "yield", // var "const", "enum", - "export", - "onready", "static", "var", // control flow @@ -1894,19 +1859,12 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "return", "match", "while", - "remote", - "master", - "puppet", - "remotesync", - "mastersync", - "puppetsync", nullptr }; const char **w = _reserved_words; while (*w) { - p_words->push_back(*w); w++; } @@ -1917,12 +1875,10 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { } bool GDScriptLanguage::handles_global_class_type(const String &p_type) const { - return p_type == "GDScript"; } String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const { - Vector<uint8_t> sourcef; Error err; FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); @@ -1933,33 +1889,33 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b String source = f->get_as_utf8_string(); GDScriptParser parser; - parser.parse(source, p_path.get_base_dir(), true, p_path, false, nullptr, true); - - if (parser.get_parse_tree() && parser.get_parse_tree()->type == GDScriptParser::Node::TYPE_CLASS) { + err = parser.parse(source, p_path, false); - const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(parser.get_parse_tree()); + // TODO: Simplify this code by using the analyzer to get full inheritance. + if (err == OK) { + const GDScriptParser::ClassNode *c = parser.get_tree(); if (r_icon_path) { - if (c->icon_path.empty() || c->icon_path.is_abs_path()) + if (c->icon_path.empty() || c->icon_path.is_abs_path()) { *r_icon_path = c->icon_path; - else if (c->icon_path.is_rel_path()) + } else if (c->icon_path.is_rel_path()) { *r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path(); + } } if (r_base_type) { - const GDScriptParser::ClassNode *subclass = c; String path = p_path; GDScriptParser subparser; while (subclass) { if (subclass->extends_used) { - if (subclass->extends_file) { - if (subclass->extends_class.size() == 0) { - get_global_class_name(subclass->extends_file, r_base_type); + if (!subclass->extends_path.empty()) { + if (subclass->extends.size() == 0) { + get_global_class_name(subclass->extends_path, r_base_type); subclass = nullptr; break; } else { - Vector<StringName> extend_classes = subclass->extends_class; + Vector<StringName> extend_classes = subclass->extends; - FileAccessRef subfile = FileAccess::open(subclass->extends_file, FileAccess::READ); + FileAccessRef subfile = FileAccess::open(subclass->extends_path, FileAccess::READ); if (!subfile) { break; } @@ -1968,25 +1924,26 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b if (subsource.empty()) { break; } - String subpath = subclass->extends_file; + String subpath = subclass->extends_path; if (subpath.is_rel_path()) { subpath = path.get_base_dir().plus_file(subpath).simplify_path(); } - if (OK != subparser.parse(subsource, subpath.get_base_dir(), true, subpath, false, nullptr, true)) { + if (OK != subparser.parse(subsource, subpath, false)) { break; } path = subpath; - if (!subparser.get_parse_tree() || subparser.get_parse_tree()->type != GDScriptParser::Node::TYPE_CLASS) { - break; - } - subclass = static_cast<const GDScriptParser::ClassNode *>(subparser.get_parse_tree()); + subclass = subparser.get_tree(); while (extend_classes.size() > 0) { bool found = false; - for (int i = 0; i < subclass->subclasses.size(); i++) { - const GDScriptParser::ClassNode *inner_class = subclass->subclasses[i]; - if (inner_class->name == extend_classes[0]) { + for (int i = 0; i < subclass->members.size(); i++) { + if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { + continue; + } + + const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class; + if (inner_class->identifier->name == extend_classes[0]) { extend_classes.remove(0); found = true; subclass = inner_class; @@ -1999,8 +1956,8 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b } } } - } else if (subclass->extends_class.size() == 1) { - *r_base_type = subclass->extends_class[0]; + } else if (subclass->extends.size() == 1) { + *r_base_type = subclass->extends[0]; subclass = nullptr; } else { break; @@ -2011,183 +1968,13 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b } } } - return c->name; + return c->identifier != nullptr ? String(c->identifier->name) : String(); } return String(); } -#ifdef DEBUG_ENABLED -String GDScriptWarning::get_message() const { - -#define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String()); - - switch (code) { - case UNASSIGNED_VARIABLE_OP_ASSIGN: { - CHECK_SYMBOLS(1); - return "Using assignment with operation but the variable '" + symbols[0] + "' was not previously assigned a value."; - } break; - case UNASSIGNED_VARIABLE: { - CHECK_SYMBOLS(1); - return "The variable '" + symbols[0] + "' was used but never assigned a value."; - } break; - case UNUSED_VARIABLE: { - CHECK_SYMBOLS(1); - return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'"; - } break; - case SHADOWED_VARIABLE: { - CHECK_SYMBOLS(2); - return "The local variable '" + symbols[0] + "' is shadowing an already-defined variable at line " + symbols[1] + "."; - } break; - case UNUSED_CLASS_VARIABLE: { - CHECK_SYMBOLS(1); - return "The class variable '" + symbols[0] + "' is declared but never used in the script."; - } break; - case UNUSED_ARGUMENT: { - CHECK_SYMBOLS(2); - return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'"; - } break; - case UNREACHABLE_CODE: { - CHECK_SYMBOLS(1); - return "Unreachable code (statement after return) in function '" + symbols[0] + "()'."; - } break; - case STANDALONE_EXPRESSION: { - return "Standalone expression (the line has no effect)."; - } break; - case VOID_ASSIGNMENT: { - CHECK_SYMBOLS(1); - return "Assignment operation, but the function '" + symbols[0] + "()' returns void."; - } break; - case NARROWING_CONVERSION: { - return "Narrowing conversion (float is converted to int and loses precision)."; - } break; - case FUNCTION_MAY_YIELD: { - CHECK_SYMBOLS(1); - return "Assigned variable is typed but the function '" + symbols[0] + "()' may yield and return a GDScriptFunctionState instead."; - } break; - case VARIABLE_CONFLICTS_FUNCTION: { - CHECK_SYMBOLS(1); - return "Variable declaration of '" + symbols[0] + "' conflicts with a function of the same name."; - } break; - case FUNCTION_CONFLICTS_VARIABLE: { - CHECK_SYMBOLS(1); - return "Function declaration of '" + symbols[0] + "()' conflicts with a variable of the same name."; - } break; - case FUNCTION_CONFLICTS_CONSTANT: { - CHECK_SYMBOLS(1); - return "Function declaration of '" + symbols[0] + "()' conflicts with a constant of the same name."; - } break; - case INCOMPATIBLE_TERNARY: { - return "Values of the ternary conditional are not mutually compatible."; - } break; - case UNUSED_SIGNAL: { - CHECK_SYMBOLS(1); - return "The signal '" + symbols[0] + "' is declared but never emitted."; - } break; - case RETURN_VALUE_DISCARDED: { - CHECK_SYMBOLS(1); - return "The function '" + symbols[0] + "()' returns a value, but this value is never used."; - } break; - case PROPERTY_USED_AS_FUNCTION: { - CHECK_SYMBOLS(2); - return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a property with the same name. Did you mean to access it?"; - } break; - case CONSTANT_USED_AS_FUNCTION: { - CHECK_SYMBOLS(2); - return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a constant with the same name. Did you mean to access it?"; - } break; - case FUNCTION_USED_AS_PROPERTY: { - CHECK_SYMBOLS(2); - return "The property '" + symbols[0] + "' was not found in base '" + symbols[1] + "' but there's a method with the same name. Did you mean to call it?"; - } break; - case INTEGER_DIVISION: { - return "Integer division, decimal part will be discarded."; - } break; - case UNSAFE_PROPERTY_ACCESS: { - CHECK_SYMBOLS(2); - return "The property '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype)."; - } break; - case UNSAFE_METHOD_ACCESS: { - CHECK_SYMBOLS(2); - return "The method '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype)."; - } break; - case UNSAFE_CAST: { - CHECK_SYMBOLS(1); - return "The value is cast to '" + symbols[0] + "' but has an unknown type."; - } break; - case UNSAFE_CALL_ARGUMENT: { - CHECK_SYMBOLS(4); - return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided"; - } break; - case DEPRECATED_KEYWORD: { - CHECK_SYMBOLS(2); - return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'."; - } break; - case STANDALONE_TERNARY: { - return "Standalone ternary conditional operator: the return value is being discarded."; - } - case WARNING_MAX: break; // Can't happen, but silences warning - } - ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + "."); - -#undef CHECK_SYMBOLS -} - -String GDScriptWarning::get_name() const { - return get_name_from_code(code); -} - -String GDScriptWarning::get_name_from_code(Code p_code) { - ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String()); - - static const char *names[] = { - "UNASSIGNED_VARIABLE", - "UNASSIGNED_VARIABLE_OP_ASSIGN", - "UNUSED_VARIABLE", - "SHADOWED_VARIABLE", - "UNUSED_CLASS_VARIABLE", - "UNUSED_ARGUMENT", - "UNREACHABLE_CODE", - "STANDALONE_EXPRESSION", - "VOID_ASSIGNMENT", - "NARROWING_CONVERSION", - "FUNCTION_MAY_YIELD", - "VARIABLE_CONFLICTS_FUNCTION", - "FUNCTION_CONFLICTS_VARIABLE", - "FUNCTION_CONFLICTS_CONSTANT", - "INCOMPATIBLE_TERNARY", - "UNUSED_SIGNAL", - "RETURN_VALUE_DISCARDED", - "PROPERTY_USED_AS_FUNCTION", - "CONSTANT_USED_AS_FUNCTION", - "FUNCTION_USED_AS_PROPERTY", - "INTEGER_DIVISION", - "UNSAFE_PROPERTY_ACCESS", - "UNSAFE_METHOD_ACCESS", - "UNSAFE_CAST", - "UNSAFE_CALL_ARGUMENT", - "DEPRECATED_KEYWORD", - "STANDALONE_TERNARY", - nullptr - }; - - return names[(int)p_code]; -} - -GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) { - for (int i = 0; i < WARNING_MAX; i++) { - if (get_name_from_code((Code)i) == p_name) { - return (Code)i; - } - } - - ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name); -} - -#endif // DEBUG_ENABLED - GDScriptLanguage::GDScriptLanguage() { - calls = 0; ERR_FAIL_COND(singleton); singleton = this; @@ -2225,14 +2012,13 @@ GDScriptLanguage::GDScriptLanguage() { GLOBAL_DEF("debug/gdscript/completion/autocomplete_setters_and_getters", false); for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); - bool default_enabled = !warning.begins_with("unsafe_") && i != GDScriptWarning::UNUSED_CLASS_VARIABLE; + bool default_enabled = !warning.begins_with("unsafe_"); GLOBAL_DEF("debug/gdscript/warnings/" + warning, default_enabled); } #endif // DEBUG_ENABLED } GDScriptLanguage::~GDScriptLanguage() { - if (_call_stack) { memdelete_arr(_call_stack); } @@ -2245,71 +2031,63 @@ void GDScriptLanguage::add_orphan_subclass(const String &p_qualified_name, const Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_name) { Map<String, ObjectID>::Element *orphan_subclass_element = orphan_subclasses.find(p_qualified_name); - if (!orphan_subclass_element) + if (!orphan_subclass_element) { return Ref<GDScript>(); + } ObjectID orphan_subclass = orphan_subclass_element->get(); Object *obj = ObjectDB::get_instance(orphan_subclass); orphan_subclasses.erase(orphan_subclass_element); - if (!obj) + if (!obj) { return Ref<GDScript>(); + } return Ref<GDScript>(Object::cast_to<GDScript>(obj)); } /*************** RESOURCE ***************/ -RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress) { - - if (r_error) +RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { + if (r_error) { *r_error = ERR_FILE_CANT_OPEN; + } - GDScript *script = memnew(GDScript); - - Ref<GDScript> scriptres(script); - - if (p_path.ends_with(".gde") || p_path.ends_with(".gdc")) { - - script->set_script_path(p_original_path); // script needs this. - script->set_path(p_original_path); - Error err = script->load_byte_code(p_path); - ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load byte code from file '" + p_path + "'."); - - } else { - Error err = script->load_source_code(p_path); - ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load source code from file '" + p_path + "'."); + Error err; + Ref<GDScript> script = GDScriptCache::get_full_script(p_path, err); - script->set_script_path(p_original_path); // script needs this. - script->set_path(p_original_path); + // TODO: Reintroduce binary and encrypted scripts. - script->reload(); + if (script.is_null()) { + // Don't fail loading because of parsing error. + script.instance(); } - if (r_error) + + if (r_error) { *r_error = OK; + } - return scriptres; + return script; } void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const { - p_extensions->push_back("gd"); - p_extensions->push_back("gdc"); - p_extensions->push_back("gde"); + // TODO: Reintroduce binary and encrypted scripts. + // p_extensions->push_back("gdc"); + // p_extensions->push_back("gde"); } bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const { - return (p_type == "Script" || p_type == "GDScript"); } String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const { - String el = p_path.get_extension().to_lower(); - if (el == "gd" || el == "gdc" || el == "gde") + // TODO: Reintroduce binary and encrypted scripts. + if (el == "gd" /*|| el == "gdc" || el == "gde"*/) { return "GDScript"; + } return ""; } void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) { - FileAccessRef file = FileAccess::open(p_path, FileAccess::READ); ERR_FAIL_COND_MSG(!file, "Cannot open file '" + p_path + "'."); @@ -2319,7 +2097,7 @@ void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<S } GDScriptParser parser; - if (OK != parser.parse(source, p_path.get_base_dir(), true, p_path, false, nullptr, true)) { + if (OK != parser.parse(source, p_path, false)) { return; } @@ -2329,7 +2107,6 @@ void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<S } Error ResourceFormatSaverGDScript::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { - Ref<GDScript> sqscr = p_resource; ERR_FAIL_COND_V(sqscr.is_null(), ERR_INVALID_PARAMETER); @@ -2356,12 +2133,11 @@ Error ResourceFormatSaverGDScript::save(const String &p_path, const RES &p_resou } void ResourceFormatSaverGDScript::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { - if (Object::cast_to<GDScript>(*p_resource)) { p_extensions->push_back("gd"); } } -bool ResourceFormatSaverGDScript::recognize(const RES &p_resource) const { +bool ResourceFormatSaverGDScript::recognize(const RES &p_resource) const { return Object::cast_to<GDScript>(*p_resource) != nullptr; } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 2c5876372b..9906b4014d 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -39,7 +39,6 @@ #include "gdscript_function.h" class GDScriptNativeClass : public Reference { - GDCLASS(GDScriptNativeClass, Reference); StringName name; @@ -56,7 +55,6 @@ public: }; class GDScript : public Script { - GDCLASS(GDScript, Script); bool tool; bool valid; @@ -106,7 +104,8 @@ class GDScript : public Script { #endif Map<StringName, PropertyInfo> member_info; - GDScriptFunction *initializer; //direct pointer to _init , faster to locate + GDScriptFunction *implicit_initializer = nullptr; + GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate int subclass_count; Set<Object *> instances; @@ -117,6 +116,9 @@ class GDScript : public Script { String fully_qualified_name; SelfList<GDScript> script_list; + SelfList<GDScriptFunctionState>::List pending_func_states; + + void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error); GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error); void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path); @@ -124,7 +126,7 @@ class GDScript : public Script { #ifdef TOOLS_ENABLED Set<PlaceHolderScriptInstance *> placeholders; //void _update_placeholder(PlaceHolderScriptInstance *p_placeholder); - virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); + virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override; #endif #ifdef DEBUG_ENABLED @@ -133,7 +135,7 @@ class GDScript : public Script { #endif - bool _update_exports(); + bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false); void _save_orphaned_subclasses(); void _init_rpc_methods_properties(); @@ -143,13 +145,14 @@ protected: bool _set(const StringName &p_name, const Variant &p_value); void _get_property_list(List<PropertyInfo> *p_properties) const; - Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); - //void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount); + Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; static void _bind_methods(); public: - virtual bool is_valid() const { return valid; } + virtual bool is_valid() const override { return valid; } + + bool inherits_script(const Ref<Script> &p_script) const override; const Map<StringName, Ref<GDScript>> &get_subclasses() const { return subclasses; } const Map<StringName, Variant> &get_constants() const { return constants; } @@ -162,10 +165,10 @@ public: const Ref<GDScriptNativeClass> &get_native() const { return native; } const String &get_script_class_name() const { return name; } - virtual bool has_script_signal(const StringName &p_signal) const; - virtual void get_script_signal_list(List<MethodInfo> *r_signals) const; + virtual bool has_script_signal(const StringName &p_signal) const override; + virtual void get_script_signal_list(List<MethodInfo> *r_signals) const override; - bool is_tool() const { return tool; } + bool is_tool() const override { return tool; } Ref<GDScript> get_base() const; const Map<StringName, MemberInfo> &debug_get_member_indices() const { return member_indices; } @@ -173,21 +176,21 @@ public: StringName debug_get_member_by_index(int p_idx) const; Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error); - virtual bool can_instance() const; + virtual bool can_instance() const override; - virtual Ref<Script> get_base_script() const; + virtual Ref<Script> get_base_script() const override; - virtual StringName get_instance_base_type() const; // this may not work in all scripts, will return empty if so - virtual ScriptInstance *instance_create(Object *p_this); - virtual PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this); - virtual bool instance_has(const Object *p_this) const; + virtual StringName get_instance_base_type() const override; // this may not work in all scripts, will return empty if so + virtual ScriptInstance *instance_create(Object *p_this) override; + virtual PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this) override; + virtual bool instance_has(const Object *p_this) const override; - virtual bool has_source_code() const; - virtual String get_source_code() const; - virtual void set_source_code(const String &p_code); - virtual void update_exports(); + virtual bool has_source_code() const override; + virtual String get_source_code() const override; + virtual void set_source_code(const String &p_code) override; + virtual void update_exports() override; - virtual Error reload(bool p_keep_state = false); + 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... Error load_source_code(const String &p_path); @@ -195,42 +198,42 @@ public: Vector<uint8_t> get_as_byte_code() const; - bool get_property_default_value(const StringName &p_property, Variant &r_value) const; + bool get_property_default_value(const StringName &p_property, Variant &r_value) const override; - virtual void get_script_method_list(List<MethodInfo> *p_list) const; - virtual bool has_method(const StringName &p_method) const; - virtual MethodInfo get_method_info(const StringName &p_method) const; + virtual void get_script_method_list(List<MethodInfo> *p_list) const override; + virtual bool has_method(const StringName &p_method) const override; + virtual MethodInfo get_method_info(const StringName &p_method) const override; - virtual void get_script_property_list(List<PropertyInfo> *p_list) const; + virtual void get_script_property_list(List<PropertyInfo> *p_list) const override; - virtual ScriptLanguage *get_language() const; + virtual ScriptLanguage *get_language() const override; - virtual int get_member_line(const StringName &p_member) const { + virtual int get_member_line(const StringName &p_member) const override { #ifdef TOOLS_ENABLED - if (member_lines.has(p_member)) + if (member_lines.has(p_member)) { return member_lines[p_member]; - else + } #endif - return -1; + return -1; } - virtual void get_constants(Map<StringName, Variant> *p_constants); - virtual void get_members(Set<StringName> *p_members); + virtual void get_constants(Map<StringName, Variant> *p_constants) override; + virtual void get_members(Set<StringName> *p_members) override; - virtual Vector<ScriptNetData> get_rpc_methods() const; - virtual uint16_t get_rpc_method_id(const StringName &p_method) const; - virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const; - virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const; - virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const; + virtual Vector<ScriptNetData> get_rpc_methods() const override; + virtual uint16_t get_rpc_method_id(const StringName &p_method) const override; + virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const override; + virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const override; + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const override; - virtual Vector<ScriptNetData> get_rset_properties() const; - virtual uint16_t get_rset_property_id(const StringName &p_variable) const; - virtual StringName get_rset_property(const uint16_t p_variable_id) const; - virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const; - virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const; + virtual Vector<ScriptNetData> get_rset_properties() const override; + virtual uint16_t get_rset_property_id(const StringName &p_variable) const override; + virtual StringName get_rset_property(const uint16_t p_variable_id) const override; + virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const override; + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const override; #ifdef TOOLS_ENABLED - virtual bool is_placeholder_fallback_enabled() const { return placeholder_fallback_enabled; } + virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } #endif GDScript(); @@ -252,7 +255,7 @@ class GDScriptInstance : public ScriptInstance { Vector<Variant> members; bool base_ref; - void _ml_call_reversed(GDScript *sptr, const StringName &p_method, const Variant **p_args, int p_argcount); + SelfList<GDScriptFunctionState>::List pending_func_states; public: virtual Object *get_owner() { return owner; } @@ -265,8 +268,6 @@ public: virtual void get_method_list(List<MethodInfo> *p_list) const; virtual bool has_method(const StringName &p_method) const; virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); - virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount); - virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount); Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; } @@ -297,53 +298,8 @@ public: ~GDScriptInstance(); }; -#ifdef DEBUG_ENABLED -struct GDScriptWarning { - enum Code { - UNASSIGNED_VARIABLE, // Variable used but never assigned - UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc) - UNUSED_VARIABLE, // Local variable is declared but never used - SHADOWED_VARIABLE, // Variable name shadowed by other variable - UNUSED_CLASS_VARIABLE, // Class variable is declared but never used in the file - UNUSED_ARGUMENT, // Function argument is never used - UNREACHABLE_CODE, // Code after a return statement - STANDALONE_EXPRESSION, // Expression not assigned to a variable - VOID_ASSIGNMENT, // Function returns void but it's assigned to a variable - NARROWING_CONVERSION, // Float value into an integer slot, precision is lost - FUNCTION_MAY_YIELD, // Typed assign of function call that yields (it may return a function state) - VARIABLE_CONFLICTS_FUNCTION, // Variable has the same name of a function - FUNCTION_CONFLICTS_VARIABLE, // Function has the same name of a variable - FUNCTION_CONFLICTS_CONSTANT, // Function has the same name of a constant - INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible - UNUSED_SIGNAL, // Signal is defined but never emitted - RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used - PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name - CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name - FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name - INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded - UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes) - UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes) - UNSAFE_CAST, // Cast used in an unknown type - UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument - DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced - STANDALONE_TERNARY, // Return value of ternary expression is discarded - WARNING_MAX, - } code; - Vector<String> symbols; - int line; - - String get_name() const; - String get_message() const; - static String get_name_from_code(Code p_code); - static Code get_code_from_name(const String &p_name); - - GDScriptWarning() : - code(WARNING_MAX), - line(-1) {} -}; -#endif // DEBUG_ENABLED - class GDScriptLanguage : public ScriptLanguage { + friend class GDScriptFunctionState; static GDScriptLanguage *singleton; @@ -353,7 +309,6 @@ class GDScriptLanguage : public ScriptLanguage { Map<StringName, Variant> named_globals; struct CallLevel { - Variant *stack; GDScriptFunction *function; GDScriptInstance *instance; @@ -392,12 +347,13 @@ public: bool debug_break_parse(const String &p_file, int p_line, const String &p_error); _FORCE_INLINE_ void enter_function(GDScriptInstance *p_instance, GDScriptFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) { - - if (Thread::get_main_id() != Thread::get_caller_id()) + if (Thread::get_main_id() != Thread::get_caller_id()) { return; //no support for other threads than main for now + } - if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) + if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) { EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() + 1); + } if (_debug_call_stack_pos >= _debug_max_call_stack) { //stack overflow @@ -415,15 +371,15 @@ public: } _FORCE_INLINE_ void exit_function() { - - if (Thread::get_main_id() != Thread::get_caller_id()) + if (Thread::get_main_id() != Thread::get_caller_id()) { return; //no support for other threads than main for now + } - if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) + if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) { EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() - 1); + } if (_debug_call_stack_pos == 0) { - _debug_error = "Stack Underflow (Engine Bug)"; EngineDebugger::get_script_debugger()->debug(this); return; @@ -433,8 +389,9 @@ public: } virtual Vector<StackInfo> debug_get_current_stack_info() { - if (Thread::get_main_id() != Thread::get_caller_id()) + if (Thread::get_main_id() != Thread::get_caller_id()) { return Vector<StackInfo>(); + } Vector<StackInfo> csi; csi.resize(_debug_call_stack_pos); @@ -449,7 +406,6 @@ public: } struct { - StringName _init; StringName _notification; StringName _set; @@ -545,7 +501,7 @@ public: class ResourceFormatLoaderGDScript : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp new file mode 100644 index 0000000000..561cdbbda4 --- /dev/null +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -0,0 +1,3109 @@ +/*************************************************************************/ +/* gdscript_analyzer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_analyzer.h" + +#include "core/class_db.h" +#include "core/hash_map.h" +#include "core/io/resource_loader.h" +#include "core/project_settings.h" +#include "core/script_language.h" +#include "gdscript.h" + +// TODO: Move this to a central location (maybe core?). +static HashMap<StringName, StringName> underscore_map; +static const char *underscore_classes[] = { + "ClassDB", + "Directory", + "Engine", + "File", + "Geometry", + "GodotSharp", + "JSON", + "Marshalls", + "Mutex", + "OS", + "ResourceLoader", + "ResourceSaver", + "Semaphore", + "Thread", + "VisualScriptEditor", + nullptr, +}; +static StringName get_real_class_name(const StringName &p_source) { + if (underscore_map.empty()) { + const char **class_name = underscore_classes; + while (*class_name != nullptr) { + underscore_map[*class_name] = String("_") + *class_name; + class_name++; + } + } + if (underscore_map.has(p_source)) { + return underscore_map[p_source]; + } + return p_source; +} + +void GDScriptAnalyzer::cleanup() { + underscore_map.clear(); +} + +static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::BUILTIN; + type.builtin_type = Variant::CALLABLE; + type.is_constant = true; + type.method_info = p_info; + return type; +} + +static GDScriptParser::DataType make_signal_type(const MethodInfo &p_info) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::BUILTIN; + type.builtin_type = Variant::SIGNAL; + type.is_constant = true; + type.method_info = p_info; + return type; +} + +static GDScriptParser::DataType make_native_meta_type(const StringName &p_class_name) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::NATIVE; + type.builtin_type = Variant::OBJECT; + type.is_constant = true; + type.native_type = p_class_name; + type.is_meta_type = true; + return type; +} + +static GDScriptParser::DataType make_native_enum_type(const StringName &p_native_class, const StringName &p_enum_name) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::ENUM; + type.builtin_type = Variant::OBJECT; + type.is_constant = true; + type.is_meta_type = true; + + List<StringName> enum_values; + StringName real_native_name = get_real_class_name(p_native_class); + ClassDB::get_enum_constants(real_native_name, p_enum_name, &enum_values); + + for (const List<StringName>::Element *E = enum_values.front(); E != nullptr; E = E->next()) { + type.enum_values[E->get()] = ClassDB::get_integer_constant(real_native_name, E->get()); + } + + return type; +} + +static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::BUILTIN; + type.builtin_type = p_type; + type.is_constant = true; + type.is_meta_type = true; + return type; +} + +Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { + if (p_class->base_type.is_set()) { + // Already resolved + return OK; + } + + if (p_class == parser->head) { + if (p_class->identifier) { + p_class->fqcn = p_class->identifier->name; + } else { + p_class->fqcn = parser->script_path; + } + } else { + p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); + } + + GDScriptParser::DataType result; + + // Set datatype for class. + GDScriptParser::DataType class_type; + class_type.is_constant = true; + class_type.is_meta_type = true; + class_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + class_type.kind = GDScriptParser::DataType::CLASS; + class_type.class_type = p_class; + class_type.script_path = parser->script_path; + p_class->set_datatype(class_type); + + if (!p_class->extends_used) { + result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + result.kind = GDScriptParser::DataType::NATIVE; + result.native_type = "Reference"; + } else { + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + + GDScriptParser::DataType base; + + int extends_index = 0; + + if (!p_class->extends_path.empty()) { + Ref<GDScriptParserRef> parser = get_parser_for(p_class->extends_path); + if (parser.is_null()) { + push_error(vformat(R"(Could not resolve super class path "%s".)", p_class->extends_path), p_class); + return ERR_PARSE_ERROR; + } + + Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + if (err != OK) { + push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class); + return err; + } + + base = parser->get_parser()->head->get_datatype(); + } else { + if (p_class->extends.empty()) { + return ERR_PARSE_ERROR; + } + const StringName &name = p_class->extends[extends_index++]; + base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + + if (ScriptServer::is_global_class(name)) { + String base_path = ScriptServer::get_global_class_path(name); + + if (base_path == parser->script_path) { + base = parser->head->get_datatype(); + } else { + Ref<GDScriptParserRef> parser = get_parser_for(base_path); + if (parser.is_null()) { + push_error(vformat(R"(Could not resolve super class "%s".)", name), p_class); + return ERR_PARSE_ERROR; + } + + Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + if (err != OK) { + push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); + return err; + } + } + } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { + const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name); + if (info.path.get_extension().to_lower() != ".gd") { + push_error(vformat(R"(Singleton %s is not a GDScript.)", info.name), p_class); + return ERR_PARSE_ERROR; + } + + Ref<GDScriptParserRef> parser = get_parser_for(info.path); + if (parser.is_null()) { + push_error(vformat(R"(Could not parse singleton from "%s".)", info.path), p_class); + return ERR_PARSE_ERROR; + } + + Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + if (err != OK) { + push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); + return err; + } + } else if (class_exists(name) && ClassDB::can_instance(get_real_class_name(name))) { + base.kind = GDScriptParser::DataType::NATIVE; + base.native_type = name; + } else { + // Look for other classes in script. + GDScriptParser::ClassNode *look_class = p_class; + bool found = false; + while (look_class != nullptr) { + if (look_class->identifier && look_class->identifier->name == name) { + if (!look_class->get_datatype().is_set()) { + Error err = resolve_inheritance(look_class, false); + if (err) { + return err; + } + } + base = look_class->get_datatype(); + found = true; + break; + } + if (look_class->members_indices.has(name) && look_class->get_member(name).type == GDScriptParser::ClassNode::Member::CLASS) { + GDScriptParser::ClassNode::Member member = look_class->get_member(name); + if (!member.m_class->get_datatype().is_set()) { + Error err = resolve_inheritance(member.m_class, false); + if (err) { + return err; + } + } + base = member.m_class->get_datatype(); + found = true; + break; + } + look_class = look_class->outer; + } + + if (!found) { + push_error(vformat(R"(Could not find base class "%s".)", name), p_class); + return ERR_PARSE_ERROR; + } + } + } + + for (int index = extends_index; index < p_class->extends.size(); index++) { + if (base.kind != GDScriptParser::DataType::CLASS) { + push_error(R"(Super type "%s" is not a GDScript. Cannot get nested types.)", p_class); + return ERR_PARSE_ERROR; + } + + // TODO: Extends could use identifier nodes. That way errors can be pointed out properly and it can be used here. + GDScriptParser::IdentifierNode *id = parser->alloc_node<GDScriptParser::IdentifierNode>(); + id->name = p_class->extends[index]; + + reduce_identifier_from_base(id, &base); + + GDScriptParser::DataType id_type = id->get_datatype(); + if (!id_type.is_set()) { + push_error(vformat(R"(Could not find type "%s" under base "%s".)", id->name, base.to_string()), p_class); + } + + base = id_type; + } + + result = base; + } + + if (!result.is_set()) { + // TODO: More specific error messages. + push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->identifier == nullptr ? "<main>" : p_class->identifier->name), p_class); + return ERR_PARSE_ERROR; + } + + p_class->base_type = result; + class_type.native_type = result.native_type; + p_class->set_datatype(class_type); + + if (p_recursive) { + for (int i = 0; i < p_class->members.size(); i++) { + if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { + resolve_inheritance(p_class->members[i].m_class, true); + } + } + } + + return OK; +} + +GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::TypeNode *p_type) { + GDScriptParser::DataType result; + + if (p_type == nullptr) { + result.kind = GDScriptParser::DataType::VARIANT; + return result; + } + + result.type_source = result.ANNOTATED_EXPLICIT; + result.builtin_type = Variant::OBJECT; + + if (p_type->type_chain.empty()) { + // void. + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::NIL; + p_type->set_datatype(result); + return result; + } + + StringName first = p_type->type_chain[0]->name; + + if (first == "Variant") { + result.kind = GDScriptParser::DataType::VARIANT; + if (p_type->type_chain.size() > 1) { + push_error(R"("Variant" type don't contain nested types.)", p_type->type_chain[1]); + return GDScriptParser::DataType(); + } + return result; + } + + if (first == "Object") { + result.kind = GDScriptParser::DataType::NATIVE; + result.native_type = "Object"; + if (p_type->type_chain.size() > 1) { + push_error(R"("Object" type don't contain nested types.)", p_type->type_chain[1]); + return GDScriptParser::DataType(); + } + return result; + } + + if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { + // Built-in types. + if (p_type->type_chain.size() > 1) { + push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]); + return GDScriptParser::DataType(); + } + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = GDScriptParser::get_builtin_type(first); + } else if (class_exists(first)) { + // Native engine classes. + result.kind = GDScriptParser::DataType::NATIVE; + result.native_type = first; + } else if (ScriptServer::is_global_class(first)) { + if (parser->script_path == ScriptServer::get_global_class_path(first)) { + result = parser->head->get_datatype(); + } else { + Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(first)); + if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { + push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type); + return GDScriptParser::DataType(); + } + result = ref->get_parser()->head->get_datatype(); + } + } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) { + const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first); + Ref<GDScriptParserRef> ref = get_parser_for(autoload.path); + if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { + push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type); + return GDScriptParser::DataType(); + } + result = ref->get_parser()->head->get_datatype(); + } else if (ClassDB::has_enum(get_real_class_name(parser->current_class->base_type.native_type), first)) { + // Native enum in current class. + result = make_native_enum_type(parser->current_class->base_type.native_type, first); + } else { + // Classes in current scope. + GDScriptParser::ClassNode *script_class = parser->current_class; + bool found = false; + while (!found && script_class != nullptr) { + if (script_class->identifier && script_class->identifier->name == first) { + result = script_class->get_datatype(); + found = true; + break; + } + if (script_class->members_indices.has(first)) { + GDScriptParser::ClassNode::Member member = script_class->members[script_class->members_indices[first]]; + switch (member.type) { + case GDScriptParser::ClassNode::Member::CLASS: + result = member.m_class->get_datatype(); + found = true; + break; + case GDScriptParser::ClassNode::Member::ENUM: + result = member.m_enum->get_datatype(); + found = true; + break; + case GDScriptParser::ClassNode::Member::CONSTANT: + if (member.constant->get_datatype().is_meta_type) { + result = member.constant->get_datatype(); + found = true; + break; + } + [[fallthrough]]; + default: + push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type); + return GDScriptParser::DataType(); + } + } + script_class = script_class->outer; + } + } + if (!result.is_set()) { + push_error(vformat(R"("%s" was not found in the current scope.)", first), p_type); + result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. + return result; + } + + if (p_type->type_chain.size() > 1) { + if (result.kind == GDScriptParser::DataType::CLASS) { + for (int i = 1; i < p_type->type_chain.size(); i++) { + GDScriptParser::DataType base = result; + reduce_identifier_from_base(p_type->type_chain[i], &base); + result = p_type->type_chain[i]->get_datatype(); + if (!result.is_set()) { + push_error(vformat(R"(Could not find type "%s" under base "%s".)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]); + result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. + return result; + } else if (!result.is_meta_type) { + push_error(vformat(R"(Member "%s" under base "%s" is not a valid type.)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]); + result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. + return result; + } + } + } else if (result.kind == GDScriptParser::DataType::NATIVE) { + // Only enums allowed for native. + if (ClassDB::has_enum(get_real_class_name(result.native_type), p_type->type_chain[1]->name)) { + if (p_type->type_chain.size() > 2) { + push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]); + } else { + result = make_native_enum_type(result.native_type, p_type->type_chain[1]->name); + } + } + } else { + push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]); + result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. + return result; + } + } + + p_type->set_datatype(result); + return result; +} + +void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_class) { + if (p_class->resolved_interface) { + return; + } + p_class->resolved_interface = true; + + GDScriptParser::ClassNode *previous_class = parser->current_class; + parser->current_class = p_class; + + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + + switch (member.type) { + case GDScriptParser::ClassNode::Member::VARIABLE: { + GDScriptParser::DataType datatype; + datatype.kind = GDScriptParser::DataType::VARIANT; + datatype.type_source = GDScriptParser::DataType::UNDETECTED; + + if (member.variable->initializer != nullptr) { + member.variable->set_datatype(datatype); // Allow recursive usage. + reduce_expression(member.variable->initializer); + datatype = member.variable->initializer->get_datatype(); + } + + if (member.variable->datatype_specifier != nullptr) { + datatype = resolve_datatype(member.variable->datatype_specifier); + datatype.is_meta_type = false; + + if (member.variable->initializer != nullptr) { + if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); + } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { +#ifdef DEBUG_ENABLED + parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif + } + if (member.variable->initializer->get_datatype().is_variant()) { + // TODO: Warn unsafe assign. + mark_node_unsafe(member.variable->initializer); + } + } + } else if (member.variable->infer_datatype) { + if (member.variable->initializer == nullptr) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because there's no default value.)", member.variable->identifier->name), member.variable->identifier); + } else if (!datatype.is_set() || datatype.has_no_type()) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value doesn't have a set type.)", member.variable->identifier->name), member.variable->initializer); + } else if (datatype.is_variant()) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is Variant. Use explicit "Variant" type if this is intended.)", member.variable->identifier->name), member.variable->initializer); + } else if (datatype.builtin_type == Variant::NIL) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is "null".)", member.variable->identifier->name), member.variable->initializer); + } + } + + datatype.is_constant = false; + member.variable->set_datatype(datatype); + if (!datatype.has_no_type()) { + // TODO: Move this out into a routine specific to validate annotations. + if (member.variable->export_info.hint == PROPERTY_HINT_TYPE_STRING) { + // @export annotation. + switch (datatype.kind) { + case GDScriptParser::DataType::BUILTIN: + member.variable->export_info.hint_string = Variant::get_type_name(datatype.builtin_type); + break; + case GDScriptParser::DataType::NATIVE: + if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) { + member.variable->export_info.hint_string = get_real_class_name(datatype.native_type); + } else { + push_error(R"(Export type can only be built-in or a resource.)", member.variable); + } + break; + default: + // TODO: Allow custom user resources. + push_error(R"(Export type can only be built-in or a resource.)", member.variable); + break; + } + } + } + } break; + case GDScriptParser::ClassNode::Member::CONSTANT: { + reduce_expression(member.constant->initializer); + + GDScriptParser::DataType datatype = member.constant->get_datatype(); + if (member.constant->initializer) { + if (!member.constant->initializer->is_constant) { + push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer); + } + + if (member.constant->datatype_specifier != nullptr) { + datatype = resolve_datatype(member.constant->datatype_specifier); + datatype.is_meta_type = false; + + if (!is_type_compatible(datatype, member.constant->initializer->get_datatype(), true)) { + push_error(vformat(R"(Value of type "%s" cannot be initialized to constant of type "%s".)", member.constant->initializer->get_datatype().to_string(), datatype.to_string()), member.constant->initializer); + } else if (datatype.builtin_type == Variant::INT && member.constant->initializer->get_datatype().builtin_type == Variant::FLOAT) { +#ifdef DEBUG_ENABLED + parser->push_warning(member.constant->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif + } + } + } + datatype.is_constant = true; + + member.constant->set_datatype(datatype); + } break; + case GDScriptParser::ClassNode::Member::SIGNAL: { + for (int j = 0; j < member.signal->parameters.size(); j++) { + GDScriptParser::DataType signal_type = resolve_datatype(member.signal->parameters[j]->datatype_specifier); + signal_type.is_meta_type = false; + member.signal->parameters[j]->set_datatype(signal_type); + } + // TODO: Make MethodInfo from signal. + GDScriptParser::DataType signal_type; + signal_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + signal_type.kind = GDScriptParser::DataType::BUILTIN; + signal_type.builtin_type = Variant::SIGNAL; + + member.signal->set_datatype(signal_type); + } break; + case GDScriptParser::ClassNode::Member::ENUM: { + GDScriptParser::DataType enum_type; + enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + enum_type.kind = GDScriptParser::DataType::ENUM; + enum_type.builtin_type = Variant::DICTIONARY; + enum_type.enum_type = member.m_enum->identifier->name; + enum_type.native_type = p_class->fqcn + "." + member.m_enum->identifier->name; + enum_type.is_meta_type = true; + enum_type.is_constant = true; + + for (int j = 0; j < member.m_enum->values.size(); j++) { + enum_type.enum_values[member.m_enum->values[j].identifier->name] = member.m_enum->values[j].value; + } + + member.m_enum->set_datatype(enum_type); + } break; + case GDScriptParser::ClassNode::Member::FUNCTION: + resolve_function_signature(member.function); + break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: + break; // Nothing to do, type and value set in parser. + case GDScriptParser::ClassNode::Member::CLASS: + break; // Done later. + case GDScriptParser::ClassNode::Member::UNDEFINED: + ERR_PRINT("Trying to resolve undefined member."); + break; + } + } + + // Recurse nested classes. + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type != GDScriptParser::ClassNode::Member::CLASS) { + continue; + } + + resolve_class_interface(member.m_class); + } + + parser->current_class = previous_class; +} + +void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { + if (p_class->resolved_body) { + return; + } + p_class->resolved_body = true; + + GDScriptParser::ClassNode *previous_class = parser->current_class; + parser->current_class = p_class; + + // Do functions now. + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type != GDScriptParser::ClassNode::Member::FUNCTION) { + continue; + } + + resolve_function_body(member.function); + } + + parser->current_class = previous_class; + + // Recurse nested classes. + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type != GDScriptParser::ClassNode::Member::CLASS) { + continue; + } + + resolve_class_body(member.m_class); + } + + // Check unused variables. + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) { + continue; + } +#ifdef DEBUG_ENABLED + if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) { + parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name); + } +#endif + } +} + +void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { + ERR_FAIL_COND_MSG(p_node == nullptr, "Trying to resolve type of a null node."); + + switch (p_node->type) { + case GDScriptParser::Node::NONE: + break; // Unreachable. + case GDScriptParser::Node::CLASS: + resolve_class_interface(static_cast<GDScriptParser::ClassNode *>(p_node)); + resolve_class_body(static_cast<GDScriptParser::ClassNode *>(p_node)); + break; + case GDScriptParser::Node::CONSTANT: + resolve_constant(static_cast<GDScriptParser::ConstantNode *>(p_node)); + break; + case GDScriptParser::Node::FOR: + resolve_for(static_cast<GDScriptParser::ForNode *>(p_node)); + break; + case GDScriptParser::Node::FUNCTION: + resolve_function_signature(static_cast<GDScriptParser::FunctionNode *>(p_node)); + resolve_function_body(static_cast<GDScriptParser::FunctionNode *>(p_node)); + break; + case GDScriptParser::Node::IF: + resolve_if(static_cast<GDScriptParser::IfNode *>(p_node)); + break; + case GDScriptParser::Node::SUITE: + resolve_suite(static_cast<GDScriptParser::SuiteNode *>(p_node)); + break; + case GDScriptParser::Node::VARIABLE: + resolve_variable(static_cast<GDScriptParser::VariableNode *>(p_node)); + break; + case GDScriptParser::Node::WHILE: + resolve_while(static_cast<GDScriptParser::WhileNode *>(p_node)); + break; + case GDScriptParser::Node::ANNOTATION: + resolve_annotation(static_cast<GDScriptParser::AnnotationNode *>(p_node)); + break; + case GDScriptParser::Node::ASSERT: + resolve_assert(static_cast<GDScriptParser::AssertNode *>(p_node)); + break; + case GDScriptParser::Node::MATCH: + resolve_match(static_cast<GDScriptParser::MatchNode *>(p_node)); + break; + case GDScriptParser::Node::MATCH_BRANCH: + resolve_match_branch(static_cast<GDScriptParser::MatchBranchNode *>(p_node), nullptr); + break; + case GDScriptParser::Node::PARAMETER: + resolve_pararameter(static_cast<GDScriptParser::ParameterNode *>(p_node)); + break; + case GDScriptParser::Node::PATTERN: + resolve_match_pattern(static_cast<GDScriptParser::PatternNode *>(p_node), nullptr); + break; + case GDScriptParser::Node::RETURN: + resolve_return(static_cast<GDScriptParser::ReturnNode *>(p_node)); + break; + case GDScriptParser::Node::TYPE: + resolve_datatype(static_cast<GDScriptParser::TypeNode *>(p_node)); + break; + // Resolving expression is the same as reducing them. + case GDScriptParser::Node::ARRAY: + case GDScriptParser::Node::ASSIGNMENT: + case GDScriptParser::Node::AWAIT: + case GDScriptParser::Node::BINARY_OPERATOR: + case GDScriptParser::Node::CALL: + case GDScriptParser::Node::CAST: + case GDScriptParser::Node::DICTIONARY: + case GDScriptParser::Node::GET_NODE: + case GDScriptParser::Node::IDENTIFIER: + case GDScriptParser::Node::LITERAL: + case GDScriptParser::Node::PRELOAD: + case GDScriptParser::Node::SELF: + case GDScriptParser::Node::SUBSCRIPT: + case GDScriptParser::Node::TERNARY_OPERATOR: + case GDScriptParser::Node::UNARY_OPERATOR: + reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node)); + break; + case GDScriptParser::Node::BREAK: + case GDScriptParser::Node::BREAKPOINT: + case GDScriptParser::Node::CONTINUE: + case GDScriptParser::Node::ENUM: + case GDScriptParser::Node::PASS: + case GDScriptParser::Node::SIGNAL: + // Nothing to do. + break; + } +} + +void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) { + // TODO: Add second validation function for annotations, so they can use checked types. +} + +void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function) { + if (p_function->resolved_signature) { + return; + } + p_function->resolved_signature = true; + + GDScriptParser::FunctionNode *previous_function = parser->current_function; + parser->current_function = p_function; + + for (int i = 0; i < p_function->parameters.size(); i++) { + resolve_pararameter(p_function->parameters[i]); +#ifdef DEBUG_ENABLED + if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) { + parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, p_function->identifier->name, p_function->parameters[i]->identifier->name); + } + is_shadowing(p_function->parameters[i]->identifier, "function parameter"); +#endif + } + + if (p_function->identifier->name == "_init") { + // Constructor. + GDScriptParser::DataType return_type = parser->current_class->get_datatype(); + return_type.is_meta_type = false; + p_function->set_datatype(return_type); + if (p_function->return_type) { + push_error("Constructor cannot have an explicit return type.", p_function->return_type); + } + } else { + GDScriptParser::DataType return_type = resolve_datatype(p_function->return_type); + p_function->set_datatype(return_type); + } + + parser->current_function = previous_function; +} + +void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function) { + if (p_function->resolved_body) { + return; + } + p_function->resolved_body = true; + + GDScriptParser::FunctionNode *previous_function = parser->current_function; + parser->current_function = p_function; + + resolve_suite(p_function->body); + + GDScriptParser::DataType return_type = p_function->body->get_datatype(); + + if (p_function->get_datatype().has_no_type() && return_type.is_set()) { + // Use the suite inferred type if return isn't explicitly set. + return_type.type_source = GDScriptParser::DataType::INFERRED; + p_function->set_datatype(p_function->body->get_datatype()); + } else if (p_function->get_datatype().is_hard_type() && (p_function->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_function->get_datatype().builtin_type != Variant::NIL)) { + if (!p_function->body->has_return && p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init) { + push_error(R"(Not all code paths return a value.)", p_function); + } + } + + parser->current_function = previous_function; +} + +void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement) { + if (p_statement == nullptr) { + return; + } + switch (p_statement->type) { + case GDScriptParser::Node::IF: + case GDScriptParser::Node::FOR: + case GDScriptParser::Node::MATCH: + case GDScriptParser::Node::PATTERN: + case GDScriptParser::Node::RETURN: + case GDScriptParser::Node::WHILE: + // Use return or nested suite type as this suite type. + if (p_suite->get_datatype().is_set() && (p_suite->get_datatype() != p_statement->get_datatype())) { + // Mixed types. + // TODO: This could use the common supertype instead. + p_suite->datatype.kind = GDScriptParser::DataType::VARIANT; + p_suite->datatype.type_source = GDScriptParser::DataType::UNDETECTED; + } else { + p_suite->set_datatype(p_statement->get_datatype()); + } + break; + default: + break; + } +} + +void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { + for (int i = 0; i < p_suite->statements.size(); i++) { + GDScriptParser::Node *stmt = p_suite->statements[i]; + resolve_node(stmt); + decide_suite_type(p_suite, stmt); + } +} + +void GDScriptAnalyzer::resolve_if(GDScriptParser::IfNode *p_if) { + reduce_expression(p_if->condition); + + resolve_suite(p_if->true_block); + p_if->set_datatype(p_if->true_block->get_datatype()); + + if (p_if->false_block != nullptr) { + resolve_suite(p_if->false_block); + decide_suite_type(p_if, p_if->false_block); + } +} + +void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { + bool list_resolved = false; + + // Optimize constant range() call to not allocate an array. + // Use int, Vector2, Vector3 instead, which also can be used as range iterators. + if (p_for->list && p_for->list->type == GDScriptParser::Node::CALL) { + GDScriptParser::CallNode *call = static_cast<GDScriptParser::CallNode *>(p_for->list); + 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; + if (call->arguments.size() < 1) { + push_error(R"*(Invalid call for "range()" function. Expected at least 1 argument, none given.)*", call->callee); + } else if (call->arguments.size() > 3) { + push_error(vformat(R"*(Invalid call for "range()" function. Expected at most 3 arguments, %d given.)*", call->arguments.size()), call->callee); + } else { + // Now we can optimize it. + bool all_is_constant = true; + Vector<Variant> args; + args.resize(call->arguments.size()); + for (int i = 0; i < call->arguments.size(); i++) { + reduce_expression(call->arguments[i]); + + if (!call->arguments[i]->is_constant) { + all_is_constant = false; + } else { + args.write[i] = call->arguments[i]->reduced_value; + } + + GDScriptParser::DataType arg_type = call->arguments[i]->get_datatype(); + if (arg_type.kind != GDScriptParser::DataType::BUILTIN) { + all_is_constant = false; + push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]); + } else if (arg_type.builtin_type != Variant::INT && arg_type.builtin_type != Variant::FLOAT) { + all_is_constant = false; + push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]); + } + } + + Variant reduced; + + if (all_is_constant) { + switch (args.size()) { + case 1: + reduced = args[0]; + break; + case 2: + reduced = Vector2i(args[0], args[1]); + break; + case 3: + reduced = Vector3i(args[0], args[1], args[2]); + break; + } + p_for->list->is_constant = true; + p_for->list->reduced_value = reduced; + } + } + + GDScriptParser::DataType list_type; + list_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + list_type.kind = GDScriptParser::DataType::BUILTIN; + list_type.builtin_type = Variant::ARRAY; + p_for->list->set_datatype(list_type); + } + } + } + + if (!list_resolved) { + resolve_node(p_for->list); + } + + // TODO: If list is a typed array, the variable should be an element. + // Also applicable for constant range() (so variable is int or float). + + resolve_suite(p_for->loop); + p_for->set_datatype(p_for->loop->get_datatype()); +#ifdef DEBUG_ENABLED + if (p_for->variable) { + is_shadowing(p_for->variable, R"("for" iterator variable)"); + } +#endif +} + +void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) { + resolve_node(p_while->condition); + + resolve_suite(p_while->loop); + p_while->set_datatype(p_while->loop->get_datatype()); +} + +void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable) { + GDScriptParser::DataType type; + type.kind = GDScriptParser::DataType::VARIANT; // By default. + + if (p_variable->initializer != nullptr) { + reduce_expression(p_variable->initializer); + type = p_variable->initializer->get_datatype(); + + if (p_variable->infer_datatype) { + type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + + if (type.has_no_type()) { + push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value does not have a set type.)", p_variable->identifier->name), p_variable->initializer); + } else if (type.is_variant()) { + push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is a variant. Use explicit "Variant" type if this is intended.)", p_variable->identifier->name), p_variable->initializer); + } else if (type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { + push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_variable->identifier->name), p_variable->initializer); + } + } else { + type.type_source = GDScriptParser::DataType::INFERRED; + } +#ifdef DEBUG_ENABLED + if (p_variable->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { + parser->push_warning(p_variable->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_variable->initializer)->function_name); + } +#endif + } + + if (p_variable->datatype_specifier != nullptr) { + type = resolve_datatype(p_variable->datatype_specifier); + type.is_meta_type = false; + + if (p_variable->initializer != nullptr) { + if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); +#ifdef DEBUG_ENABLED + } else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { + parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif + } + if (p_variable->initializer->get_datatype().is_variant()) { + // TODO: Warn unsafe assign. + mark_node_unsafe(p_variable->initializer); + } + } + } else if (p_variable->infer_datatype) { + if (type.has_no_type()) { + push_error(vformat(R"(Cannot infer the type of variable "%s" because the initial value doesn't have a set type.)", p_variable->identifier->name), p_variable->identifier); + } + type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + } + + type.is_constant = false; + p_variable->set_datatype(type); + +#ifdef DEBUG_ENABLED + if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) { + parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name); + } else if (p_variable->assignments == 0) { + parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name); + } + + is_shadowing(p_variable->identifier, "variable"); +#endif +} + +void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant) { + GDScriptParser::DataType type; + + reduce_expression(p_constant->initializer); + + if (!p_constant->initializer->is_constant) { + push_error(vformat(R"(Assigned value for constant "%s" isn't a constant expression.)", p_constant->identifier->name), p_constant->initializer); + } + + type = p_constant->initializer->get_datatype(); + +#ifdef DEBUG_ENABLED + if (p_constant->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { + parser->push_warning(p_constant->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_constant->initializer)->function_name); + } +#endif + + if (p_constant->datatype_specifier != nullptr) { + GDScriptParser::DataType explicit_type = resolve_datatype(p_constant->datatype_specifier); + explicit_type.is_meta_type = false; + if (!is_type_compatible(explicit_type, type)) { + push_error(vformat(R"(Assigned value for constant "%s" has type %s which is not compatible with defined type %s.)", p_constant->identifier->name, type.to_string(), explicit_type.to_string()), p_constant->initializer); +#ifdef DEBUG_ENABLED + } else if (explicit_type.builtin_type == Variant::INT && type.builtin_type == Variant::FLOAT) { + parser->push_warning(p_constant->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif + } + type = explicit_type; + } else if (p_constant->infer_datatype) { + if (type.has_no_type()) { + push_error(vformat(R"(Cannot infer the type of constant "%s" because the initial value doesn't have a set type.)", p_constant->identifier->name), p_constant->identifier); + } + type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + } + + type.is_constant = true; + p_constant->set_datatype(type); + +#ifdef DEBUG_ENABLED + if (p_constant->usages == 0) { + parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); + } + + is_shadowing(p_constant->identifier, "constant"); +#endif +} + +void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) { + reduce_expression(p_assert->condition); + if (p_assert->message != nullptr) { + reduce_literal(p_assert->message); + } + + p_assert->set_datatype(p_assert->condition->get_datatype()); + +#ifdef DEBUG_ENABLED + if (p_assert->condition->is_constant) { + if (p_assert->condition->reduced_value.booleanize()) { + parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_TRUE); + } else { + parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_FALSE); + } + } +#endif +} + +void GDScriptAnalyzer::resolve_match(GDScriptParser::MatchNode *p_match) { + reduce_expression(p_match->test); + + for (int i = 0; i < p_match->branches.size(); i++) { + resolve_match_branch(p_match->branches[i], p_match->test); + + decide_suite_type(p_match, p_match->branches[i]); + } +} + +void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test) { + for (int i = 0; i < p_match_branch->patterns.size(); i++) { + resolve_match_pattern(p_match_branch->patterns[i], p_match_test); + } + + resolve_suite(p_match_branch->block); + + decide_suite_type(p_match_branch, p_match_branch->block); +} + +void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test) { + if (p_match_pattern == nullptr) { + return; + } + + GDScriptParser::DataType result; + + switch (p_match_pattern->pattern_type) { + case GDScriptParser::PatternNode::PT_LITERAL: + if (p_match_pattern->literal) { + reduce_literal(p_match_pattern->literal); + result = p_match_pattern->literal->get_datatype(); + } + break; + case GDScriptParser::PatternNode::PT_EXPRESSION: + if (p_match_pattern->expression) { + reduce_expression(p_match_pattern->expression); + if (!p_match_pattern->expression->is_constant) { + push_error(R"(Expression in match pattern must be a constant.)", p_match_pattern->expression); + } + result = p_match_pattern->expression->get_datatype(); + } + break; + case GDScriptParser::PatternNode::PT_BIND: + if (p_match_test != nullptr) { + result = p_match_test->get_datatype(); + } else { + result.kind = GDScriptParser::DataType::VARIANT; + } + p_match_pattern->bind->set_datatype(result); +#ifdef DEBUG_ENABLED + is_shadowing(p_match_pattern->bind, "pattern bind"); + if (p_match_pattern->bind->usages == 0) { + parser->push_warning(p_match_pattern->bind, GDScriptWarning::UNASSIGNED_VARIABLE, p_match_pattern->bind->name); + } +#endif + break; + case GDScriptParser::PatternNode::PT_ARRAY: + for (int i = 0; i < p_match_pattern->array.size(); i++) { + resolve_match_pattern(p_match_pattern->array[i], nullptr); + decide_suite_type(p_match_pattern, p_match_pattern->array[i]); + } + result = p_match_pattern->get_datatype(); + break; + case GDScriptParser::PatternNode::PT_DICTIONARY: + for (int i = 0; i < p_match_pattern->dictionary.size(); i++) { + if (p_match_pattern->dictionary[i].key) { + reduce_expression(p_match_pattern->dictionary[i].key); + if (!p_match_pattern->dictionary[i].key->is_constant) { + push_error(R"(Expression in dictionary pattern key must be a constant.)", p_match_pattern->expression); + } + } + + if (p_match_pattern->dictionary[i].value_pattern) { + resolve_match_pattern(p_match_pattern->dictionary[i].value_pattern, nullptr); + decide_suite_type(p_match_pattern, p_match_pattern->dictionary[i].value_pattern); + } + } + result = p_match_pattern->get_datatype(); + break; + case GDScriptParser::PatternNode::PT_WILDCARD: + case GDScriptParser::PatternNode::PT_REST: + result.kind = GDScriptParser::DataType::VARIANT; + break; + } + + p_match_pattern->set_datatype(result); +} + +void GDScriptAnalyzer::resolve_pararameter(GDScriptParser::ParameterNode *p_parameter) { + GDScriptParser::DataType result; + result.kind = GDScriptParser::DataType::VARIANT; + + if (p_parameter->default_value != nullptr) { + reduce_expression(p_parameter->default_value); + result = p_parameter->default_value->get_datatype(); + result.type_source = GDScriptParser::DataType::INFERRED; + } + + if (p_parameter->datatype_specifier != nullptr) { + resolve_datatype(p_parameter->datatype_specifier); + result = p_parameter->datatype_specifier->get_datatype(); + result.is_meta_type = false; + + if (p_parameter->default_value != nullptr) { + if (!is_type_compatible(result, p_parameter->default_value->get_datatype())) { + push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with paremeter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value); + } else if (p_parameter->default_value->get_datatype().is_variant()) { + mark_node_unsafe(p_parameter); + } + } + } + + p_parameter->set_datatype(result); +} + +void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { + GDScriptParser::DataType result; + + if (p_return->return_value != nullptr) { + reduce_expression(p_return->return_value); + result = p_return->return_value->get_datatype(); + } else { + // Return type is null by default. + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::NIL; + result.is_constant = true; + } + + GDScriptParser::DataType function_type = parser->current_function->get_datatype(); + function_type.is_meta_type = false; + if (function_type.is_hard_type()) { + if (!is_type_compatible(function_type, result)) { + // Try other way. Okay but not safe. + if (!is_type_compatible(result, function_type)) { + push_error(vformat(R"(Cannot return value of type "%s" because the function return type is "%s".)", result.to_string(), function_type.to_string()), p_return); + } else { + // TODO: Add warning. + mark_node_unsafe(p_return); + } +#ifdef DEBUG_ENABLED + } else if (function_type.builtin_type == Variant::INT && result.builtin_type == Variant::FLOAT) { + parser->push_warning(p_return, GDScriptWarning::NARROWING_CONVERSION); + } else if (result.is_variant()) { + mark_node_unsafe(p_return); +#endif + } + } + + p_return->set_datatype(result); +} + +void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expression) { + // This one makes some magic happen. + + if (p_expression == nullptr) { + return; + } + + if (p_expression->reduced) { + // Don't do this more than once. + return; + } + + p_expression->reduced = true; + + switch (p_expression->type) { + case GDScriptParser::Node::ARRAY: + reduce_array(static_cast<GDScriptParser::ArrayNode *>(p_expression)); + break; + case GDScriptParser::Node::ASSIGNMENT: + reduce_assignment(static_cast<GDScriptParser::AssignmentNode *>(p_expression)); + break; + case GDScriptParser::Node::AWAIT: + reduce_await(static_cast<GDScriptParser::AwaitNode *>(p_expression)); + break; + case GDScriptParser::Node::BINARY_OPERATOR: + reduce_binary_op(static_cast<GDScriptParser::BinaryOpNode *>(p_expression)); + break; + case GDScriptParser::Node::CALL: + reduce_call(static_cast<GDScriptParser::CallNode *>(p_expression)); + break; + case GDScriptParser::Node::CAST: + reduce_cast(static_cast<GDScriptParser::CastNode *>(p_expression)); + break; + case GDScriptParser::Node::DICTIONARY: + reduce_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_expression)); + break; + case GDScriptParser::Node::GET_NODE: + reduce_get_node(static_cast<GDScriptParser::GetNodeNode *>(p_expression)); + break; + case GDScriptParser::Node::IDENTIFIER: + reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_expression)); + break; + case GDScriptParser::Node::LITERAL: + reduce_literal(static_cast<GDScriptParser::LiteralNode *>(p_expression)); + break; + case GDScriptParser::Node::PRELOAD: + reduce_preload(static_cast<GDScriptParser::PreloadNode *>(p_expression)); + break; + case GDScriptParser::Node::SELF: + reduce_self(static_cast<GDScriptParser::SelfNode *>(p_expression)); + break; + case GDScriptParser::Node::SUBSCRIPT: + reduce_subscript(static_cast<GDScriptParser::SubscriptNode *>(p_expression)); + break; + case GDScriptParser::Node::TERNARY_OPERATOR: + reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression)); + break; + case GDScriptParser::Node::UNARY_OPERATOR: + reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression)); + break; + // Non-expressions. Here only to make sure new nodes aren't forgotten. + case GDScriptParser::Node::NONE: + case GDScriptParser::Node::ANNOTATION: + case GDScriptParser::Node::ASSERT: + case GDScriptParser::Node::BREAK: + case GDScriptParser::Node::BREAKPOINT: + case GDScriptParser::Node::CLASS: + case GDScriptParser::Node::CONSTANT: + case GDScriptParser::Node::CONTINUE: + case GDScriptParser::Node::ENUM: + case GDScriptParser::Node::FOR: + case GDScriptParser::Node::FUNCTION: + case GDScriptParser::Node::IF: + case GDScriptParser::Node::MATCH: + case GDScriptParser::Node::MATCH_BRANCH: + case GDScriptParser::Node::PARAMETER: + case GDScriptParser::Node::PASS: + case GDScriptParser::Node::PATTERN: + case GDScriptParser::Node::RETURN: + case GDScriptParser::Node::SIGNAL: + case GDScriptParser::Node::SUITE: + case GDScriptParser::Node::TYPE: + case GDScriptParser::Node::VARIABLE: + case GDScriptParser::Node::WHILE: + ERR_FAIL_MSG("Reaching unreachable case"); + } +} + +void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) { + bool all_is_constant = true; + + for (int i = 0; i < p_array->elements.size(); i++) { + GDScriptParser::ExpressionNode *element = p_array->elements[i]; + reduce_expression(element); + all_is_constant = all_is_constant && element->is_constant; + } + + if (all_is_constant) { + Array array; + array.resize(p_array->elements.size()); + for (int i = 0; i < p_array->elements.size(); i++) { + array[i] = p_array->elements[i]->reduced_value; + } + p_array->is_constant = true; + p_array->reduced_value = array; + } + + // It's array in any case. + GDScriptParser::DataType arr_type; + arr_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + arr_type.kind = GDScriptParser::DataType::BUILTIN; + arr_type.builtin_type = Variant::ARRAY; + arr_type.is_constant = true; + + p_array->set_datatype(arr_type); +} + +void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) { + reduce_expression(p_assignment->assignee); + reduce_expression(p_assignment->assigned_value); + + if (p_assignment->assigned_value == nullptr || p_assignment->assignee == nullptr) { + return; + } + + if (p_assignment->assignee->get_datatype().is_constant) { + push_error("Cannot assign a new value to a constant.", p_assignment->assignee); + } + + if (!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 (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); + } + + 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); + } else { + // TODO: Warning in this case. + } + } + } else { + push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", p_assignment->assignee->get_datatype().to_string(), p_assignment->assigned_value->get_datatype().to_string()), p_assignment); + } + } + + if (p_assignment->assignee->get_datatype().has_no_type() || p_assignment->assigned_value->get_datatype().is_variant()) { + mark_node_unsafe(p_assignment); + } + + if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { + // Change source type so it's not wrongly detected later. + GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee); + + switch (identifier->source) { + case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: { + GDScriptParser::DataType id_type = identifier->variable_source->get_datatype(); + if (!id_type.is_hard_type()) { + id_type.kind = GDScriptParser::DataType::VARIANT; + id_type.type_source = GDScriptParser::DataType::UNDETECTED; + identifier->variable_source->set_datatype(id_type); + } + } break; + case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: { + GDScriptParser::DataType id_type = identifier->variable_source->get_datatype(); + if (!id_type.is_hard_type()) { + id_type = p_assignment->assigned_value->get_datatype(); + id_type.type_source = GDScriptParser::DataType::INFERRED; + id_type.is_constant = false; + identifier->variable_source->set_datatype(id_type); + } + } break; + case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: { + GDScriptParser::DataType id_type = identifier->bind_source->get_datatype(); + if (!id_type.is_hard_type()) { + id_type = p_assignment->assigned_value->get_datatype(); + id_type.type_source = GDScriptParser::DataType::INFERRED; + id_type.is_constant = false; + identifier->variable_source->set_datatype(id_type); + } + } break; + default: + // Nothing to do. + break; + } + } + + GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); + GDScriptParser::DataType assigned_type = p_assignment->assigned_value->get_datatype(); +#ifdef DEBUG_ENABLED + if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_type.kind == GDScriptParser::DataType::BUILTIN && assigned_type.builtin_type == Variant::NIL) { + parser->push_warning(p_assignment->assigned_value, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value)->function_name); + } else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_type.builtin_type == Variant::FLOAT) { + parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION); + } +#endif +} + +void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) { + if (p_await->to_await == nullptr) { + GDScriptParser::DataType await_type; + await_type.kind = GDScriptParser::DataType::VARIANT; + p_await->set_datatype(await_type); + return; + } + if (p_await->to_await->type == GDScriptParser::Node::CALL) { + reduce_call(static_cast<GDScriptParser::CallNode *>(p_await->to_await), true); + } else { + reduce_expression(p_await->to_await); + } + + p_await->is_constant = p_await->to_await->is_constant; + p_await->reduced_value = p_await->to_await->reduced_value; + + GDScriptParser::DataType awaiting_type = p_await->to_await->get_datatype(); + + p_await->set_datatype(awaiting_type); + +#ifdef DEBUG_ENABLED + if (!awaiting_type.is_coroutine && awaiting_type.builtin_type != Variant::SIGNAL) { + parser->push_warning(p_await, GDScriptWarning::REDUNDANT_AWAIT); + } +#endif +} + +void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op) { + reduce_expression(p_binary_op->left_operand); + + if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST && p_binary_op->right_operand && p_binary_op->right_operand->type == GDScriptParser::Node::IDENTIFIER) { + reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_binary_op->right_operand), true); + } else { + reduce_expression(p_binary_op->right_operand); + } + // TODO: Right operand must be a valid type with the `is` operator. Need to check here. + + GDScriptParser::DataType left_type; + if (p_binary_op->left_operand) { + left_type = p_binary_op->left_operand->get_datatype(); + } + GDScriptParser::DataType right_type; + if (p_binary_op->right_operand) { + right_type = p_binary_op->right_operand->get_datatype(); + } + + if (!left_type.is_set() || !right_type.is_set()) { + return; + } + +#ifdef DEBUG_ENABLED + if (p_binary_op->variant_op == Variant::OP_DIVIDE && left_type.builtin_type == Variant::INT && right_type.builtin_type == Variant::INT) { + parser->push_warning(p_binary_op, GDScriptWarning::INTEGER_DIVISION); + } +#endif + + if (p_binary_op->left_operand->is_constant && p_binary_op->right_operand->is_constant) { + p_binary_op->is_constant = true; + if (p_binary_op->variant_op < Variant::OP_MAX) { + bool valid = false; + Variant::evaluate(p_binary_op->variant_op, p_binary_op->left_operand->reduced_value, p_binary_op->right_operand->reduced_value, p_binary_op->reduced_value, valid); + if (!valid) { + 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.".)", + 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())), + p_binary_op); + } + } + } else { + if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { + GDScriptParser::DataType test_type = right_type; + test_type.is_meta_type = false; + + if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand); + p_binary_op->reduced_value = false; + } else { + p_binary_op->reduced_value = true; + } + } else { + ERR_PRINT("Parser bug: unknown binary operation."); + } + } + p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value)); + + return; + } + + GDScriptParser::DataType result; + + if (left_type.is_variant() || right_type.is_variant()) { + // Cannot infer type because one operand can be anything. + result.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_binary_op); + } else { + if (p_binary_op->variant_op < Variant::OP_MAX) { + bool valid = false; + result = get_operation_type(p_binary_op->variant_op, p_binary_op->left_operand->get_datatype(), right_type, valid); + + if (!valid) { + push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", p_binary_op->left_operand->get_datatype().to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op); + } + } else { + if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { + GDScriptParser::DataType test_type = right_type; + test_type.is_meta_type = false; + + if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + // Test reverse as well to consider for subtypes. + if (!is_type_compatible(p_binary_op->left_operand->get_datatype(), test_type, false)) { + if (p_binary_op->left_operand->get_datatype().is_hard_type()) { + push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", p_binary_op->left_operand->get_datatype().to_string(), test_type.to_string()), p_binary_op->left_operand); + } else { + // TODO: Warning. + mark_node_unsafe(p_binary_op); + } + } + } + + // "is" operator is always a boolean anyway. + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::BOOL; + } else { + ERR_PRINT("Parser bug: unknown binary operation."); + } + } + } + + p_binary_op->set_datatype(result); +} + +void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_await) { + bool all_is_constant = true; + for (int i = 0; i < p_call->arguments.size(); i++) { + reduce_expression(p_call->arguments[i]); + all_is_constant = all_is_constant && p_call->arguments[i]->is_constant; + } + + GDScriptParser::Node::Type callee_type = p_call->get_callee_type(); + GDScriptParser::DataType call_type; + + 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); + GDScriptFunctions::Function builtin_function = GDScriptParser::get_builtin_function(function_name); + + if (builtin_type < Variant::VARIANT_MAX) { + // Is a builtin constructor. + call_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + call_type.kind = GDScriptParser::DataType::BUILTIN; + call_type.builtin_type = builtin_type; + + if (builtin_type == Variant::OBJECT) { + call_type.kind = GDScriptParser::DataType::NATIVE; + call_type.native_type = function_name; // "Object". + } + + if (all_is_constant) { + // Construct here. + Vector<const Variant *> args; + for (int i = 0; i < p_call->arguments.size(); i++) { + args.push_back(&(p_call->arguments[i]->reduced_value)); + } + + Callable::CallError err; + Variant value = Variant::construct(builtin_type, (const Variant **)args.ptr(), args.size(), err); + + switch (err.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be %s but is %s.)", Variant::get_type_name(builtin_type), err.argument + 1, + Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()), + p_call->arguments[err.argument]); + break; + case Callable::CallError::CALL_ERROR_INVALID_METHOD: { + String signature = Variant::get_type_name(builtin_type) + "("; + for (int i = 0; i < p_call->arguments.size(); i++) { + if (i > 0) { + signature += ", "; + } + signature += p_call->arguments[i]->get_datatype().to_string(); + } + push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee); + } break; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + push_error(vformat(R"(Too many arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + break; + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + push_error(vformat(R"(Too few arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + break; + case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: + break; // Can't happen in a builtin constructor. + case Callable::CallError::CALL_OK: + p_call->is_constant = true; + p_call->reduced_value = value; + break; + } + } else { + // TODO: Check constructors without constants. + + // If there's one argument, try to use copy constructor (those aren't explicitly defined). + if (p_call->arguments.size() == 1) { + GDScriptParser::DataType arg_type = p_call->arguments[0]->get_datatype(); + if (arg_type.is_variant()) { + mark_node_unsafe(p_call->arguments[0]); + } else { + if (arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == builtin_type) { + // Okay. + p_call->set_datatype(call_type); + return; + } + } + } + List<MethodInfo> constructors; + Variant::get_constructor_list(builtin_type, &constructors); + bool match = false; + + for (const List<MethodInfo>::Element *E = constructors.front(); E != nullptr; E = E->next()) { + const MethodInfo &info = E->get(); + + if (p_call->arguments.size() < info.arguments.size() - info.default_arguments.size()) { + continue; + } + if (p_call->arguments.size() > info.arguments.size()) { + continue; + } + + bool types_match = true; + + for (int i = 0; i < p_call->arguments.size(); i++) { + GDScriptParser::DataType par_type = type_from_property(info.arguments[i]); + + if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype())) { + types_match = false; + break; +#ifdef DEBUG_ENABLED + } else { + if (par_type.builtin_type == Variant::INT && p_call->arguments[i]->get_datatype().builtin_type == Variant::FLOAT) { + parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + } +#endif + } + } + + if (types_match) { + match = true; + call_type = type_from_property(info.return_val); + break; + } + } + + if (!match) { + String signature = Variant::get_type_name(builtin_type) + "("; + for (int i = 0; i < p_call->arguments.size(); i++) { + if (i > 0) { + signature += ", "; + } + signature += p_call->arguments[i]->get_datatype().to_string(); + } + push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call); + } + } + p_call->set_datatype(call_type); + return; + } else if (builtin_function < GDScriptFunctions::FUNC_MAX) { + MethodInfo function_info = GDScriptFunctions::get_info(builtin_function); + + if (all_is_constant && GDScriptFunctions::is_deterministic(builtin_function)) { + // Can call on compilation. + Vector<const Variant *> args; + for (int i = 0; i < p_call->arguments.size(); i++) { + args.push_back(&(p_call->arguments[i]->reduced_value)); + } + + Variant value; + Callable::CallError err; + GDScriptFunctions::call(builtin_function, (const Variant **)args.ptr(), args.size(), value, err); + + switch (err.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { + PropertyInfo wrong_arg = function_info.arguments[err.argument]; + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", GDScriptFunctions::get_func_name(builtin_function), err.argument + 1, + type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), + p_call->arguments[err.argument]); + } break; + case Callable::CallError::CALL_ERROR_INVALID_METHOD: + push_error(vformat(R"(Invalid call for function "%s".)", GDScriptFunctions::get_func_name(builtin_function)), p_call); + break; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call); + break; + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call); + break; + case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: + break; // Can't happen in a builtin constructor. + case Callable::CallError::CALL_OK: + p_call->is_constant = true; + p_call->reduced_value = value; + break; + } + } else { + validate_call_arg(function_info, p_call); + } + p_call->set_datatype(type_from_property(function_info.return_val)); + return; + } + } + + GDScriptParser::DataType base_type; + call_type.kind = GDScriptParser::DataType::VARIANT; + bool is_self = false; + + if (p_call->is_super) { + base_type = parser->current_class->base_type; + is_self = true; + } else if (callee_type == GDScriptParser::Node::IDENTIFIER) { + base_type = parser->current_class->get_datatype(); + is_self = true; + } 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. + // TODO: Could check if Callable here. + p_call->set_datatype(call_type); + mark_node_unsafe(p_call); + return; + } + reduce_expression(subscript->base); + + base_type = subscript->base->get_datatype(); + } else { + // Invalid call. Error already sent in parser. + // TODO: Could check if Callable here too. + p_call->set_datatype(call_type); + mark_node_unsafe(p_call); + return; + } + + bool is_static = false; + bool is_vararg = false; + int default_arg_count = 0; + GDScriptParser::DataType return_type; + List<GDScriptParser::DataType> par_types; + + if (get_function_signature(p_call, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) { + validate_call_arg(par_types, default_arg_count, is_vararg, p_call); + + if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { + push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee); + } + + call_type = return_type; + } else { + // Check if the name exists as something else. + bool found = false; + if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) { + GDScriptParser::IdentifierNode *callee_id; + if (callee_type == GDScriptParser::Node::IDENTIFIER) { + callee_id = static_cast<GDScriptParser::IdentifierNode *>(p_call->callee); + } else { + // Can only be attribute. + callee_id = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee)->attribute; + } + if (callee_id) { + reduce_identifier_from_base(callee_id, &base_type); + GDScriptParser::DataType callee_datatype = callee_id->get_datatype(); + if (callee_datatype.is_set() && !callee_datatype.is_variant()) { + found = true; + 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_datatype.to_string()), p_call->callee); + } +#ifdef DEBUG_ENABLED + } else if (!is_self) { + parser->push_warning(p_call, GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->function_name, base_type.to_string()); + mark_node_unsafe(p_call); +#endif + } + } + } + if (!found && is_self) { + String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string(); + push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee); + } + } + + if (call_type.is_coroutine && !is_await) { + push_error(vformat(R"*(Function "%s()" is a coroutine, so it must be called with "await".)*", p_call->function_name), p_call->callee); + } + + p_call->set_datatype(call_type); +} + +void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { + reduce_expression(p_cast->operand); + + GDScriptParser::DataType cast_type = resolve_datatype(p_cast->cast_type); + + if (!cast_type.is_set()) { + return; + } + + cast_type.is_meta_type = false; // The casted value won't be a type name. + p_cast->set_datatype(cast_type); + + if (!cast_type.is_variant()) { + GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); + if (!op_type.is_variant()) { + bool valid = false; + if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { + valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type); + } else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) { + valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type); + } + + if (!valid) { + push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type); + } + } + } else { + mark_node_unsafe(p_cast); + } +#ifdef DEBUG_ENABLED + if (p_cast->operand->get_datatype().is_variant()) { + parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string()); + mark_node_unsafe(p_cast); + } +#endif + + // TODO: Perform cast on constants. +} + +void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { + bool all_is_constant = true; + + for (int i = 0; i < p_dictionary->elements.size(); i++) { + const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + if (p_dictionary->style == GDScriptParser::DictionaryNode::PYTHON_DICT) { + reduce_expression(element.key); + } + reduce_expression(element.value); + all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; + } + + if (all_is_constant) { + Dictionary dict; + for (int i = 0; i < p_dictionary->elements.size(); i++) { + const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + dict[element.key->reduced_value] = element.value->reduced_value; + } + p_dictionary->is_constant = true; + p_dictionary->reduced_value = dict; + } + + // It's dictionary in any case. + GDScriptParser::DataType dict_type; + dict_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + dict_type.kind = GDScriptParser::DataType::BUILTIN; + dict_type.builtin_type = Variant::DICTIONARY; + dict_type.is_constant = true; + + p_dictionary->set_datatype(dict_type); +} + +void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) { + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::NATIVE; + result.native_type = "Node"; + result.builtin_type = Variant::OBJECT; + + if (!ClassDB::is_parent_class(get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) { + push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node); + } + + p_get_node->set_datatype(result); +} + +GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const StringName &p_class_name) { + Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(p_class_name)); + ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::CLASS; + type.builtin_type = Variant::OBJECT; + type.native_type = ScriptServer::get_global_class_native_base(p_class_name); + type.class_type = ref->get_parser()->head; + type.script_path = ref->get_parser()->script_path; + type.is_constant = true; + type.is_meta_type = true; + return type; +} + +void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base) { + GDScriptParser::DataType base; + if (p_base == nullptr) { + base = type_from_metatype(parser->current_class->get_datatype()); + } else { + base = *p_base; + } + + const StringName &name = p_identifier->name; + + if (base.kind == GDScriptParser::DataType::BUILTIN) { + if (base.is_meta_type) { + bool valid = true; + Variant result = Variant::get_constant_value(base.builtin_type, name, &valid); + if (valid) { + p_identifier->is_constant = true; + p_identifier->reduced_value = result; + p_identifier->set_datatype(type_from_variant(result)); + } else { + push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier); + } + } else { + Callable::CallError temp; + Variant dummy = Variant::construct(base.builtin_type, nullptr, 0, temp); + List<PropertyInfo> properties; + dummy.get_property_list(&properties); + for (const List<PropertyInfo>::Element *E = properties.front(); E != nullptr; E = E->next()) { + const PropertyInfo &prop = E->get(); + if (prop.name == name) { + p_identifier->set_datatype(type_from_property(prop)); + return; + } + } + push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); + } + return; + } + + if (base.kind == GDScriptParser::DataType::ENUM) { + if (base.is_meta_type) { + if (base.enum_values.has(name)) { + p_identifier->is_constant = true; + p_identifier->reduced_value = base.enum_values[name]; + + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::ENUM_VALUE; + result.native_type = base.native_type; + result.enum_type = name; + p_identifier->set_datatype(result); + } else { + push_error(vformat(R"(Cannot find value "%s" in "%s".)", name, base.to_string()), p_identifier); + } + } else { + push_error(R"(Cannot get property from enum value.)", p_identifier); + } + return; + } + + GDScriptParser::ClassNode *base_class = base.class_type; + + // TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls). + while (base_class != nullptr) { + if (base_class->identifier && base_class->identifier->name == name) { + p_identifier->set_datatype(base_class->get_datatype()); + return; + } + if (base_class->has_member(name)) { + const GDScriptParser::ClassNode::Member &member = base_class->get_member(name); + p_identifier->set_datatype(member.get_datatype()); + switch (member.type) { + case GDScriptParser::ClassNode::Member::CONSTANT: + // For out-of-order resolution: + reduce_expression(member.constant->initializer); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + p_identifier->set_datatype(member.constant->initializer->get_datatype()); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; + p_identifier->constant_source = member.constant; + break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: + p_identifier->is_constant = true; + p_identifier->reduced_value = member.enum_value.value; + break; + case GDScriptParser::ClassNode::Member::VARIABLE: + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; + p_identifier->variable_source = member.variable; + break; + case GDScriptParser::ClassNode::Member::FUNCTION: + resolve_function_signature(member.function); + p_identifier->set_datatype(make_callable_type(member.function->info)); + break; + default: + break; // Type already set. + } + return; + } + // Check outer constants. + // TODO: Allow outer static functions. + GDScriptParser::ClassNode *outer = base_class->outer; + while (outer != nullptr) { + if (outer->has_member(name)) { + const GDScriptParser::ClassNode::Member &member = outer->get_member(name); + if (member.type == GDScriptParser::ClassNode::Member::CONSTANT) { + // TODO: Make sure loops won't cause problem. And make special error message for those. + // For out-of-order resolution: + reduce_expression(member.constant->initializer); + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + return; + } + } + outer = outer->outer; + } + + base_class = base_class->base_type.class_type; + } + + // Check native members. + const StringName &native = get_real_class_name(base.native_type); + + if (class_exists(native)) { + PropertyInfo prop_info; + MethodInfo method_info; + if (ClassDB::get_property_info(native, name, &prop_info)) { + p_identifier->set_datatype(type_from_property(prop_info)); + return; + } + if (ClassDB::get_method_info(native, name, &method_info)) { + // Method is callable. + p_identifier->set_datatype(make_callable_type(method_info)); + return; + } + if (ClassDB::get_signal(native, name, &method_info)) { + // Signal is a type too. + p_identifier->set_datatype(make_signal_type(method_info)); + return; + } + if (ClassDB::has_enum(native, name)) { + p_identifier->set_datatype(make_native_enum_type(native, name)); + return; + } + bool valid = false; + int int_constant = ClassDB::get_integer_constant(native, name, &valid); + if (valid) { + p_identifier->is_constant = true; + p_identifier->reduced_value = int_constant; + p_identifier->set_datatype(type_from_variant(int_constant)); + return; + } + } +} + +void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) { + // TODO: This is opportunity to further infer types. + // Check if identifier is local. + // If that's the case, the declaration already was solved before. + switch (p_identifier->source) { + case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: + p_identifier->set_datatype(p_identifier->parameter_source->get_datatype()); + return; + case GDScriptParser::IdentifierNode::LOCAL_CONSTANT: + case GDScriptParser::IdentifierNode::MEMBER_CONSTANT: + p_identifier->set_datatype(p_identifier->constant_source->get_datatype()); + p_identifier->is_constant = true; + // TODO: Constant should have a value on the node itself. + p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value; + return; + case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: + p_identifier->variable_source->usages++; + [[fallthrough]]; + case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: + p_identifier->set_datatype(p_identifier->variable_source->get_datatype()); + return; + case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: + p_identifier->set_datatype(p_identifier->bind_source->get_datatype()); + return; + case GDScriptParser::IdentifierNode::LOCAL_BIND: { + GDScriptParser::DataType result = p_identifier->bind_source->get_datatype(); + result.is_constant = true; + p_identifier->set_datatype(result); + return; + } + case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE: + break; + } + + // Not a local, so check members. + reduce_identifier_from_base(p_identifier); + if (p_identifier->get_datatype().is_set()) { + // Found. + return; + } + + StringName name = p_identifier->name; + p_identifier->source = GDScriptParser::IdentifierNode::UNDEFINED_SOURCE; + + // Check globals. + if (GDScriptParser::get_builtin_type(name) < Variant::VARIANT_MAX) { + if (can_be_builtin) { + p_identifier->set_datatype(make_builtin_meta_type(GDScriptParser::get_builtin_type(name))); + return; + } else { + push_error(R"(Builtin type cannot be used as a name on its own.)", p_identifier); + } + } + + if (class_exists(name)) { + p_identifier->set_datatype(make_native_meta_type(name)); + return; + } + + if (ScriptServer::is_global_class(name)) { + p_identifier->set_datatype(make_global_class_meta_type(name)); + return; + } + + if (GDScriptLanguage::get_singleton()->get_global_map().has(name)) { + int idx = GDScriptLanguage::get_singleton()->get_global_map()[name]; + Variant constant = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + p_identifier->set_datatype(type_from_variant(constant)); + p_identifier->is_constant = true; + p_identifier->reduced_value = constant; + return; + } + + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(name)) { + Variant constant = GDScriptLanguage::get_singleton()->get_named_globals_map()[name]; + p_identifier->set_datatype(type_from_variant(constant)); + p_identifier->is_constant = true; + p_identifier->reduced_value = constant; + return; + } + + // Not found. + // Check if it's a builtin function. + if (parser->get_builtin_function(name) < GDScriptFunctions::FUNC_MAX) { + push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier); + } else { + push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier); + } + GDScriptParser::DataType dummy; + dummy.kind = GDScriptParser::DataType::VARIANT; + p_identifier->set_datatype(dummy); // Just so type is set to something. +} + +void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) { + p_literal->reduced_value = p_literal->value; + p_literal->is_constant = true; + + p_literal->set_datatype(type_from_variant(p_literal->reduced_value)); +} + +void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { + p_preload->is_constant = true; + p_preload->reduced_value = p_preload->resource; + p_preload->set_datatype(type_from_variant(p_preload->reduced_value)); +} + +void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { + p_self->is_constant = false; + p_self->set_datatype(type_from_metatype(parser->current_class->get_datatype())); +} + +void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) { + if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) { + reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true); + } else { + reduce_expression(p_subscript->base); + } + + GDScriptParser::DataType result_type; + + // Reduce index first. If it's a constant StringName, use attribute instead. + if (!p_subscript->is_attribute) { + if (p_subscript->index == nullptr) { + return; + } + reduce_expression(p_subscript->index); + + if (p_subscript->index->is_constant && p_subscript->index->reduced_value.get_type() == Variant::STRING_NAME) { + GDScriptParser::IdentifierNode *attribute = parser->alloc_node<GDScriptParser::IdentifierNode>(); + // Copy location for better error message. + attribute->start_line = p_subscript->index->start_line; + attribute->end_line = p_subscript->index->end_line; + attribute->leftmost_column = p_subscript->index->leftmost_column; + attribute->rightmost_column = p_subscript->index->rightmost_column; + p_subscript->is_attribute = true; + p_subscript->attribute = attribute; + } + } + + if (p_subscript->is_attribute) { + if (p_subscript->attribute == nullptr) { + return; + } + if (p_subscript->base->is_constant) { + // Just try to get it. + bool valid = false; + Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, &valid); + if (!valid) { + push_error(vformat(R"(Cannot get member "%s" from "%s".)", p_subscript->attribute->name, p_subscript->base->reduced_value), p_subscript->index); + } else { + p_subscript->is_constant = true; + p_subscript->reduced_value = value; + result_type = type_from_variant(value); + } + result_type.kind = GDScriptParser::DataType::VARIANT; + } else { + GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); + + if (base_type.is_variant()) { + result_type.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_subscript); + } else { + reduce_identifier_from_base(p_subscript->attribute, &base_type); + GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); + if (attr_type.is_set()) { + result_type = attr_type; + p_subscript->is_constant = p_subscript->attribute->is_constant; + p_subscript->reduced_value = p_subscript->attribute->reduced_value; + } else { + if (base_type.kind == GDScriptParser::DataType::BUILTIN) { + push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, base_type.to_string()), p_subscript->attribute); +#ifdef DEBUG_ENABLED + } else { + parser->push_warning(p_subscript, GDScriptWarning::UNSAFE_PROPERTY_ACCESS, p_subscript->attribute->name, base_type.to_string()); +#endif + } + result_type.kind = GDScriptParser::DataType::VARIANT; + } + } + } + } else { + // Index was already reduced before. + + if (p_subscript->base->is_constant && p_subscript->index->is_constant) { + // Just try to get it. + bool valid = false; + Variant value = p_subscript->base->reduced_value.get(p_subscript->index->reduced_value, &valid); + if (!valid) { + push_error(vformat(R"(Cannot get index "%s" from "%s".)", p_subscript->index->reduced_value, p_subscript->base->reduced_value), p_subscript->index); + } else { + p_subscript->is_constant = true; + p_subscript->reduced_value = value; + result_type = type_from_variant(value); + } + result_type.kind = GDScriptParser::DataType::VARIANT; + } else { + GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); + GDScriptParser::DataType index_type = p_subscript->index->get_datatype(); + + if (base_type.is_variant()) { + result_type.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_subscript); + } else { + if (base_type.kind == GDScriptParser::DataType::BUILTIN && !index_type.is_variant()) { + // Check if indexing is valid. + bool error = index_type.kind != GDScriptParser::DataType::BUILTIN && base_type.builtin_type != Variant::DICTIONARY; + if (!error) { + switch (base_type.builtin_type) { + // Expect int or real as index. + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_COLOR_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_STRING_ARRAY: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::ARRAY: + case Variant::STRING: + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT; + break; + // Expect String only. + case Variant::RECT2: + case Variant::RECT2I: + case Variant::PLANE: + case Variant::QUAT: + case Variant::AABB: + case Variant::OBJECT: + error = index_type.builtin_type != Variant::STRING; + break; + // Expect String or number. + case Variant::BASIS: + case Variant::VECTOR2: + case Variant::VECTOR2I: + case Variant::VECTOR3: + case Variant::VECTOR3I: + case Variant::TRANSFORM: + case Variant::TRANSFORM2D: + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT && + index_type.builtin_type != Variant::STRING; + break; + // Expect String or int. + case Variant::COLOR: + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; + break; + // Don't support indexing, but we will check it later. + case Variant::_RID: + case Variant::BOOL: + case Variant::CALLABLE: + case Variant::FLOAT: + case Variant::INT: + case Variant::NIL: + case Variant::NODE_PATH: + case Variant::SIGNAL: + case Variant::STRING_NAME: + break; + // Here for completeness. + case Variant::DICTIONARY: + case Variant::VARIANT_MAX: + break; + } + + if (error) { + push_error(vformat(R"(Invalid index type "%s" for a base of type "%s".)", index_type.to_string(), base_type.to_string()), p_subscript->index); + } + } + } else if (base_type.kind != GDScriptParser::DataType::BUILTIN && !index_type.is_variant()) { + if (index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME) { + push_error(vformat(R"(Only String or StringName can be used as index for type "%s", but received a "%s".)", base_type.to_string(), index_type.to_string()), p_subscript->index); + } + } + + // Check resulting type if possible. + result_type.builtin_type = Variant::NIL; + result_type.kind = GDScriptParser::DataType::BUILTIN; + result_type.type_source = GDScriptParser::DataType::INFERRED; + + switch (base_type.builtin_type) { + // Can't index at all. + case Variant::_RID: + case Variant::BOOL: + case Variant::CALLABLE: + case Variant::FLOAT: + case Variant::INT: + case Variant::NIL: + case Variant::NODE_PATH: + case Variant::SIGNAL: + case Variant::STRING_NAME: + result_type.kind = GDScriptParser::DataType::VARIANT; + push_error(vformat(R"(Cannot use subscript operator on a base of type "%s".)", base_type.to_string()), p_subscript->base); + break; + // Return int. + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::VECTOR2I: + case Variant::VECTOR3I: + result_type.builtin_type = Variant::INT; + break; + // Return float. + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::QUAT: + result_type.builtin_type = Variant::FLOAT; + break; + // Return Color. + case Variant::PACKED_COLOR_ARRAY: + result_type.builtin_type = Variant::COLOR; + break; + // Return String. + case Variant::PACKED_STRING_ARRAY: + case Variant::STRING: + result_type.builtin_type = Variant::STRING; + break; + // Return Vector2. + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::TRANSFORM2D: + case Variant::RECT2: + result_type.builtin_type = Variant::VECTOR2; + break; + // Return Vector2I. + case Variant::RECT2I: + result_type.builtin_type = Variant::VECTOR2I; + break; + // Return Vector3. + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::AABB: + case Variant::BASIS: + result_type.builtin_type = Variant::VECTOR3; + break; + // Depends on the index. + case Variant::TRANSFORM: + case Variant::PLANE: + case Variant::COLOR: + case Variant::ARRAY: + case Variant::DICTIONARY: + result_type.kind = GDScriptParser::DataType::VARIANT; + result_type.type_source = GDScriptParser::DataType::UNDETECTED; + break; + // Here for completeness. + case Variant::OBJECT: + case Variant::VARIANT_MAX: + break; + } + } + } + } + + p_subscript->set_datatype(result_type); +} + +void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op) { + reduce_expression(p_ternary_op->condition); + reduce_expression(p_ternary_op->true_expr); + reduce_expression(p_ternary_op->false_expr); + + GDScriptParser::DataType result; + + if (p_ternary_op->condition && p_ternary_op->condition->is_constant && p_ternary_op->true_expr->is_constant && p_ternary_op->false_expr && p_ternary_op->false_expr->is_constant) { + p_ternary_op->is_constant = true; + if (p_ternary_op->condition->reduced_value.booleanize()) { + p_ternary_op->reduced_value = p_ternary_op->true_expr->reduced_value; + } else { + p_ternary_op->reduced_value = p_ternary_op->false_expr->reduced_value; + } + } + + GDScriptParser::DataType true_type; + if (p_ternary_op->true_expr) { + true_type = p_ternary_op->true_expr->get_datatype(); + } else { + true_type.kind = GDScriptParser::DataType::VARIANT; + } + GDScriptParser::DataType false_type; + if (p_ternary_op->false_expr) { + false_type = p_ternary_op->false_expr->get_datatype(); + } else { + false_type.kind = GDScriptParser::DataType::VARIANT; + } + + if (true_type.is_variant() || false_type.is_variant()) { + result.kind = GDScriptParser::DataType::VARIANT; + } else { + result = true_type; + if (!is_type_compatible(true_type, false_type)) { + result = false_type; + if (!is_type_compatible(false_type, true_type)) { + result.type_source = GDScriptParser::DataType::UNDETECTED; + result.kind = GDScriptParser::DataType::VARIANT; +#ifdef DEBUG_ENABLED + parser->push_warning(p_ternary_op, GDScriptWarning::INCOMPATIBLE_TERNARY); +#endif + } + } + } + + p_ternary_op->set_datatype(result); +} + +void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) { + reduce_expression(p_unary_op->operand); + + GDScriptParser::DataType result; + + if (p_unary_op->operand->is_constant) { + p_unary_op->is_constant = true; + p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant()); + result = type_from_variant(p_unary_op->reduced_value); + } else if (p_unary_op->operand->get_datatype().is_variant()) { + result.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_unary_op); + } else { + bool valid = false; + result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), p_unary_op->operand->get_datatype(), valid); + + if (!valid) { + push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", p_unary_op->operand->get_datatype().to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op->operand); + } + } + + p_unary_op->set_datatype(result); +} + +GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value) { + GDScriptParser::DataType result; + result.is_constant = true; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = p_value.get_type(); + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; // Constant has explicit type. + + if (p_value.get_type() == Variant::OBJECT) { + Object *obj = p_value; + if (!obj) { + return GDScriptParser::DataType(); + } + result.native_type = obj->get_class_name(); + + Ref<Script> scr = p_value; // Check if value is a script itself. + if (scr.is_valid()) { + result.is_meta_type = true; + } else { + result.is_meta_type = false; + scr = obj->get_script(); + } + if (scr.is_valid()) { + result.script_type = scr; + result.script_path = scr->get_path(); + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + result.kind = GDScriptParser::DataType::CLASS; + Ref<GDScriptParserRef> ref = get_parser_for(gds->get_path()); + ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + result.class_type = ref->get_parser()->head; + result.script_path = ref->get_parser()->script_path; + } else { + result.kind = GDScriptParser::DataType::SCRIPT; + } + result.native_type = scr->get_instance_base_type(); + } else { + result.kind = GDScriptParser::DataType::NATIVE; + if (result.native_type == GDScriptNativeClass::get_class_static()) { + result.is_meta_type = true; + } + } + } + + return result; +} + +GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) const { + GDScriptParser::DataType result = p_meta_type; + result.is_meta_type = false; + result.is_constant = false; + return result; +} + +GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property) const { + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + if (p_property.type == Variant::NIL && (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { + // Variant + result.kind = GDScriptParser::DataType::VARIANT; + return result; + } + result.builtin_type = p_property.type; + if (p_property.type == Variant::OBJECT) { + result.kind = GDScriptParser::DataType::NATIVE; + result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + } else { + result.kind = GDScriptParser::DataType::BUILTIN; + } + return result; +} + +bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { + r_static = false; + r_vararg = false; + r_default_arg_count = 0; + StringName function_name = p_function; + + if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) { + // Construct a base type to get methods. + Callable::CallError err; + Variant dummy = Variant::construct(p_base_type.builtin_type, nullptr, 0, err); + if (err.error != Callable::CallError::CALL_OK) { + ERR_FAIL_V_MSG(false, "Could not construct base Variant type."); + } + List<MethodInfo> methods; + dummy.get_method_list(&methods); + + for (const List<MethodInfo>::Element *E = methods.front(); E != nullptr; E = E->next()) { + if (E->get().name == p_function) { + return function_signature_from_info(E->get(), r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + } + } + + return false; + } + + bool is_constructor = p_base_type.is_meta_type && p_function == "new"; + if (is_constructor) { + function_name = "_init"; + r_static = true; + } + + GDScriptParser::ClassNode *base_class = p_base_type.class_type; + GDScriptParser::FunctionNode *found_function = nullptr; + + while (found_function == nullptr && base_class != nullptr) { + if (base_class->has_member(function_name)) { + if (base_class->get_member(function_name).type != GDScriptParser::ClassNode::Member::FUNCTION) { + // TODO: If this is Callable it can have a better error message. + push_error(vformat(R"(Member "%s" is not a function.)", function_name), p_source); + return false; + } + found_function = base_class->get_member(function_name).function; + } + base_class = base_class->base_type.class_type; + } + + if (found_function != nullptr) { + r_static = is_constructor || found_function->is_static; + for (int i = 0; i < found_function->parameters.size(); i++) { + r_par_types.push_back(found_function->parameters[i]->get_datatype()); + if (found_function->parameters[i]->default_value != nullptr) { + r_default_arg_count++; + } + } + r_return_type = found_function->get_datatype(); + r_return_type.is_coroutine = found_function->is_coroutine; + + return true; + } + + Ref<Script> base_script = p_base_type.script_type; + + while (base_script.is_valid() && base_script->is_valid()) { + MethodInfo info = base_script->get_method_info(function_name); + + if (!(info == MethodInfo())) { + return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + } + base_script = base_script->get_base_script(); + } + + // If the base is a script, it might be trying to access members of the Script class itself. + if (p_base_type.is_meta_type && !is_constructor && (p_base_type.kind == GDScriptParser::DataType::SCRIPT || p_base_type.kind == GDScriptParser::DataType::CLASS)) { + MethodInfo info; + StringName script_class = p_base_type.kind == GDScriptParser::DataType::SCRIPT ? p_base_type.script_type->get_class_name() : StringName(GDScript::get_class_static()); + + if (ClassDB::get_method_info(script_class, function_name, &info)) { + return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + } + } + + StringName base_native = p_base_type.native_type; +#ifdef DEBUG_ENABLED + if (base_native != StringName()) { + // Empty native class might happen in some Script implementations. + // Just ignore it. + if (!class_exists(base_native)) { + ERR_FAIL_V_MSG(false, vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native)); + } + } +#endif + + if (is_constructor) { + // Native types always have a default constructor. + r_return_type = p_base_type; + r_return_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_return_type.is_meta_type = false; + return true; + } + + StringName real_native = get_real_class_name(base_native); + + MethodInfo info; + if (ClassDB::get_method_info(real_native, function_name, &info)) { + return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + } + + return false; +} + +bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { + r_return_type = type_from_property(p_info.return_val); + r_default_arg_count = p_info.default_arguments.size(); + r_vararg = (p_info.flags & METHOD_FLAG_VARARG) != 0; + + for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E != nullptr; E = E->next()) { + r_par_types.push_back(type_from_property(E->get())); + } + return true; +} + +bool GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call) { + List<GDScriptParser::DataType> arg_types; + + for (const List<PropertyInfo>::Element *E = p_method.arguments.front(); E != nullptr; E = E->next()) { + arg_types.push_back(type_from_property(E->get())); + } + + return validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call); +} + +bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call) { + bool valid = true; + + if (p_call->arguments.size() < p_par_types.size() - p_default_args_count) { + push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", p_call->function_name, p_par_types.size() - p_default_args_count, p_call->arguments.size()), p_call); + valid = false; + } + if (!p_is_vararg && p_call->arguments.size() > p_par_types.size()) { + push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", p_call->function_name, p_par_types.size(), p_call->arguments.size()), p_call->arguments[p_par_types.size()]); + valid = false; + } + + for (int i = 0; i < p_call->arguments.size(); i++) { + if (i >= p_par_types.size()) { + // Already on vararg place. + break; + } + GDScriptParser::DataType par_type = p_par_types[i]; + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + + if (arg_type.is_variant()) { + // Argument can be anything, so this is unsafe. + mark_node_unsafe(p_call->arguments[i]); + } else if (!is_type_compatible(par_type, arg_type, true)) { + // Supertypes are acceptable for dynamic compliance, but it's unsafe. + mark_node_unsafe(p_call); + if (!is_type_compatible(arg_type, par_type)) { + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", + p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()), + p_call->arguments[i]); + valid = false; + } +#ifdef DEBUG_ENABLED + } else { + if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) { + parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + } +#endif + } + } + return valid; +} + +#ifdef DEBUG_ENABLED +bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context) { + const StringName &name = p_local->name; + GDScriptParser::DataType base = parser->current_class->get_datatype(); + + GDScriptParser::ClassNode *base_class = base.class_type; + + while (base_class != nullptr) { + if (base_class->has_member(name)) { + parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_local->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line())); + return true; + } + base_class = base_class->base_type.class_type; + } + + StringName base_native = base.native_type; + + ERR_FAIL_COND_V_MSG(!class_exists(base_native), false, "Non-existent native base class."); + + StringName parent = base_native; + while (parent != StringName()) { + StringName real_class_name = get_real_class_name(parent); + if (ClassDB::has_method(real_class_name, name, true)) { + parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent); + return true; + } else if (ClassDB::has_signal(real_class_name, name, true)) { + parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "signal", parent); + return true; + } else if (ClassDB::has_property(real_class_name, name, true)) { + parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "property", parent); + return true; + } else if (ClassDB::has_integer_constant(real_class_name, name, true)) { + parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "constant", parent); + return true; + } else if (ClassDB::has_enum(real_class_name, name, true)) { + parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "enum", parent); + return true; + } + parent = ClassDB::get_parent_class(real_class_name); + } + + return false; +} +#endif + +GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid) { + // This function creates dummy variant values and apply the operation to those. Less error-prone than keeping a table of valid operations. + + GDScriptParser::DataType result; + result.kind = GDScriptParser::DataType::VARIANT; + + Variant::Type a_type = p_a.builtin_type; + Variant::Type b_type = p_b.builtin_type; + + Variant a; + REF a_ref; + if (a_type == Variant::OBJECT) { + a_ref.instance(); + a = a_ref; + } else { + Callable::CallError err; + a = Variant::construct(a_type, nullptr, 0, err); + if (err.error != Callable::CallError::CALL_OK) { + r_valid = false; + ERR_FAIL_V_MSG(result, vformat("Could not construct value of type %s", Variant::get_type_name(a_type))); + } + } + Variant b; + REF b_ref; + if (b_type == Variant::OBJECT) { + b_ref.instance(); + b = b_ref; + } else { + Callable::CallError err; + b = Variant::construct(b_type, nullptr, 0, err); + if (err.error != Callable::CallError::CALL_OK) { + r_valid = false; + ERR_FAIL_V_MSG(result, vformat("Could not construct value of type %s", Variant::get_type_name(b_type))); + } + } + + // Avoid division by zero. + switch (b_type) { + case Variant::INT: + b = 1; + break; + case Variant::FLOAT: + b = 1.0; + break; + case Variant::VECTOR2: + b = Vector2(1.0, 1.0); + break; + case Variant::VECTOR2I: + b = Vector2i(1, 1); + break; + case Variant::VECTOR3: + b = Vector3(1.0, 1.0, 1.0); + break; + case Variant::VECTOR3I: + b = Vector3i(1, 1, 1); + break; + case Variant::COLOR: + b = Color(1.0, 1.0, 1.0, 1.0); + break; + default: + // No change needed. + break; + } + + // Avoid error in formatting operator (%) where it doesn't find a placeholder. + if (a_type == Variant::STRING) { + a = String("%s"); + } + + Variant ret; + Variant::evaluate(p_operation, a, b, ret, r_valid); + + if (r_valid) { + return type_from_variant(ret); + } + + return result; +} + +// TODO: Add safe/unsafe return variable (for variant cases) +bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion) const { + // These return "true" so it doesn't affect users negatively. + ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type"); + ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type"); + + if (p_target.kind == GDScriptParser::DataType::VARIANT) { + // Variant can receive anything. + return true; + } + + if (p_source.kind == GDScriptParser::DataType::VARIANT) { + // TODO: This is acceptable but unsafe. Make sure unsafe line is set. + return true; + } + + if (p_target.kind == GDScriptParser::DataType::BUILTIN) { + bool valid = p_source.kind == GDScriptParser::DataType::BUILTIN && p_target.builtin_type == p_source.builtin_type; + if (!valid && p_allow_implicit_conversion) { + valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type); + } + if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM_VALUE) { + // Enum value is also integer. + valid = true; + } + return valid; + } + + if (p_target.kind == GDScriptParser::DataType::ENUM) { + if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { + return true; + } + if (p_source.kind == GDScriptParser::DataType::ENUM_VALUE) { + if (p_source.native_type == p_target.native_type && p_target.enum_values.has(p_source.enum_type)) { + return true; + } + } + return false; + } + + // From here on the target type is an object, so we have to test polymorphism. + + if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::NIL) { + // null is acceptable in object. + return true; + } + + StringName src_native; + Ref<Script> src_script; + const GDScriptParser::ClassNode *src_class = nullptr; + + switch (p_source.kind) { + case GDScriptParser::DataType::NATIVE: + if (p_target.kind != GDScriptParser::DataType::NATIVE) { + // Non-native class cannot be supertype of native. + return false; + } + if (p_source.is_meta_type) { + src_native = GDScriptNativeClass::get_class_static(); + } else { + src_native = p_source.native_type; + } + break; + case GDScriptParser::DataType::SCRIPT: + if (p_target.kind == GDScriptParser::DataType::CLASS) { + // A script type cannot be a subtype of a GDScript class. + return false; + } + if (p_source.is_meta_type) { + src_native = p_source.script_type->get_class_name(); + } else { + src_script = p_source.script_type; + src_native = src_script->get_instance_base_type(); + } + break; + case GDScriptParser::DataType::CLASS: + if (p_source.is_meta_type) { + src_native = GDScript::get_class_static(); + } else { + src_class = p_source.class_type; + const GDScriptParser::ClassNode *base = src_class; + while (base->base_type.kind == GDScriptParser::DataType::CLASS) { + base = base->base_type.class_type; + } + src_native = base->base_type.native_type; + src_script = base->base_type.script_type; + } + break; + case GDScriptParser::DataType::VARIANT: + case GDScriptParser::DataType::BUILTIN: + case GDScriptParser::DataType::ENUM: + case GDScriptParser::DataType::ENUM_VALUE: + case GDScriptParser::DataType::UNRESOLVED: + break; // Already solved before. + } + + // Get underscore-prefixed version for some classes. + src_native = get_real_class_name(src_native); + + switch (p_target.kind) { + case GDScriptParser::DataType::NATIVE: { + if (p_target.is_meta_type) { + return ClassDB::is_parent_class(src_native, GDScriptNativeClass::get_class_static()); + } + StringName tgt_native = get_real_class_name(p_target.native_type); + return ClassDB::is_parent_class(src_native, tgt_native); + } + case GDScriptParser::DataType::SCRIPT: + if (p_target.is_meta_type) { + return ClassDB::is_parent_class(src_native, p_target.script_type->get_class_name()); + } + while (src_script.is_valid()) { + if (src_script == p_target.script_type) { + return true; + } + src_script = src_script->get_base_script(); + } + return false; + case GDScriptParser::DataType::CLASS: + if (p_target.is_meta_type) { + return ClassDB::is_parent_class(src_native, GDScript::get_class_static()); + } + while (src_class != nullptr) { + if (src_class->fqcn == p_target.class_type->fqcn) { + return true; + } + src_class = src_class->base_type.class_type; + } + return false; + case GDScriptParser::DataType::VARIANT: + case GDScriptParser::DataType::BUILTIN: + case GDScriptParser::DataType::ENUM: + case GDScriptParser::DataType::ENUM_VALUE: + case GDScriptParser::DataType::UNRESOLVED: + break; // Already solved before. + } + + return false; +} + +void GDScriptAnalyzer::push_error(const String &p_message, const GDScriptParser::Node *p_origin) { + mark_node_unsafe(p_origin); + parser->push_error(p_message, p_origin); +} + +void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { +#ifdef DEBUG_ENABLED + for (int i = p_node->start_line; i <= p_node->end_line; i++) { + parser->unsafe_lines.insert(i); + } +#endif +} + +bool GDScriptAnalyzer::class_exists(const StringName &p_class) { + StringName real_name = get_real_class_name(p_class); + return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name); +} + +Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { + Ref<GDScriptParserRef> ref; + if (depended_parsers.has(p_path)) { + ref = depended_parsers[p_path]; + } else { + Error err = OK; + ref = GDScriptCache::get_parser(p_path, GDScriptParserRef::EMPTY, err, parser->script_path); + depended_parsers[p_path] = ref; + } + + return ref; +} + +Error GDScriptAnalyzer::resolve_inheritance() { + return resolve_inheritance(parser->head); +} + +Error GDScriptAnalyzer::resolve_interface() { + resolve_class_interface(parser->head); + return parser->errors.empty() ? OK : ERR_PARSE_ERROR; +} + +Error GDScriptAnalyzer::resolve_body() { + resolve_class_body(parser->head); + return parser->errors.empty() ? OK : ERR_PARSE_ERROR; +} + +Error GDScriptAnalyzer::resolve_program() { + resolve_class_interface(parser->head); + resolve_class_body(parser->head); + + List<String> parser_keys; + depended_parsers.get_key_list(&parser_keys); + for (const List<String>::Element *E = parser_keys.front(); E != nullptr; E = E->next()) { + depended_parsers[E->get()]->raise_status(GDScriptParserRef::FULLY_SOLVED); + } + depended_parsers.clear(); + return parser->errors.empty() ? OK : ERR_PARSE_ERROR; +} + +Error GDScriptAnalyzer::analyze() { + parser->errors.clear(); + Error err = resolve_inheritance(parser->head); + if (err) { + return err; + } + return resolve_program(); +} + +GDScriptAnalyzer::GDScriptAnalyzer(GDScriptParser *p_parser) { + parser = p_parser; +} diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h new file mode 100644 index 0000000000..06d3530cb6 --- /dev/null +++ b/modules/gdscript/gdscript_analyzer.h @@ -0,0 +1,120 @@ +/*************************************************************************/ +/* gdscript_analyzer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_ANALYZER_H +#define GDSCRIPT_ANALYZER_H + +#include "core/object.h" +#include "core/reference.h" +#include "core/set.h" +#include "gdscript_cache.h" +#include "gdscript_parser.h" + +class GDScriptAnalyzer { + GDScriptParser *parser = nullptr; + HashMap<String, Ref<GDScriptParserRef>> depended_parsers; + + Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); + GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); + + void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement); + + // This traverses the tree to resolve all TypeNodes. + Error resolve_program(); + + void resolve_annotation(GDScriptParser::AnnotationNode *p_annotation); + void resolve_class_interface(GDScriptParser::ClassNode *p_class); + void resolve_class_body(GDScriptParser::ClassNode *p_class); + void resolve_function_signature(GDScriptParser::FunctionNode *p_function); + void resolve_function_body(GDScriptParser::FunctionNode *p_function); + void resolve_node(GDScriptParser::Node *p_node); + void resolve_suite(GDScriptParser::SuiteNode *p_suite); + void resolve_if(GDScriptParser::IfNode *p_if); + void resolve_for(GDScriptParser::ForNode *p_for); + void resolve_while(GDScriptParser::WhileNode *p_while); + void resolve_variable(GDScriptParser::VariableNode *p_variable); + void resolve_constant(GDScriptParser::ConstantNode *p_constant); + void resolve_assert(GDScriptParser::AssertNode *p_assert); + void resolve_match(GDScriptParser::MatchNode *p_match); + void resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test); + void resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test); + void resolve_pararameter(GDScriptParser::ParameterNode *p_parameter); + void resolve_return(GDScriptParser::ReturnNode *p_return); + + // Reduction functions. + void reduce_expression(GDScriptParser::ExpressionNode *p_expression); + void reduce_array(GDScriptParser::ArrayNode *p_array); + void reduce_assignment(GDScriptParser::AssignmentNode *p_assignment); + void reduce_await(GDScriptParser::AwaitNode *p_await); + void reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op); + void reduce_call(GDScriptParser::CallNode *p_call, bool is_await = false); + void reduce_cast(GDScriptParser::CastNode *p_cast); + void reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary); + void reduce_get_node(GDScriptParser::GetNodeNode *p_get_node); + void reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin = false); + void reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base = nullptr); + void reduce_literal(GDScriptParser::LiteralNode *p_literal); + void reduce_preload(GDScriptParser::PreloadNode *p_preload); + void reduce_self(GDScriptParser::SelfNode *p_self); + void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript); + void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op); + void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op); + + // Helpers. + GDScriptParser::DataType type_from_variant(const Variant &p_value); + GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const; + GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const; + GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name); + bool get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); + bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); + GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid); + bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const; + void push_error(const String &p_message, const GDScriptParser::Node *p_origin); + void mark_node_unsafe(const GDScriptParser::Node *p_node); + bool class_exists(const StringName &p_class); + Ref<GDScriptParserRef> get_parser_for(const String &p_path); +#ifdef DEBUG_ENABLED + bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context); +#endif + +public: + Error resolve_inheritance(); + Error resolve_interface(); + Error resolve_body(); + Error analyze(); + + GDScriptAnalyzer(GDScriptParser *p_parser); + + static void cleanup(); +}; + +#endif // GDSCRIPT_ANALYZER_H diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp new file mode 100644 index 0000000000..583283ff46 --- /dev/null +++ b/modules/gdscript/gdscript_cache.cpp @@ -0,0 +1,244 @@ +/*************************************************************************/ +/* gdscript_cache.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_cache.h" + +#include "core/os/file_access.h" +#include "core/vector.h" +#include "gdscript.h" +#include "gdscript_analyzer.h" +#include "gdscript_parser.h" + +bool GDScriptParserRef::is_valid() const { + return parser != nullptr; +} + +GDScriptParserRef::Status GDScriptParserRef::get_status() const { + return status; +} + +GDScriptParser *GDScriptParserRef::get_parser() const { + return parser; +} + +Error GDScriptParserRef::raise_status(Status p_new_status) { + ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA); + + Error result = OK; + + while (p_new_status > status) { + switch (status) { + case EMPTY: + result = parser->parse(GDScriptCache::get_source_code(path), path, false); + status = PARSED; + break; + case PARSED: { + analyzer = memnew(GDScriptAnalyzer(parser)); + Error inheritance_result = analyzer->resolve_inheritance(); + status = INHERITANCE_SOLVED; + if (result == OK) { + result = inheritance_result; + } + } break; + case INHERITANCE_SOLVED: { + Error interface_result = analyzer->resolve_interface(); + status = INTERFACE_SOLVED; + if (result == OK) { + result = interface_result; + } + } break; + case INTERFACE_SOLVED: { + Error body_result = analyzer->resolve_body(); + status = FULLY_SOLVED; + if (result == OK) { + result = body_result; + } + } break; + case FULLY_SOLVED: { + return result; + } + } + } + + return result; +} + +GDScriptParserRef::~GDScriptParserRef() { + if (parser != nullptr) { + memdelete(parser); + } + if (analyzer != nullptr) { + memdelete(analyzer); + } + MutexLock(GDScriptCache::singleton->lock); + GDScriptCache::singleton->parser_map.erase(path); +} + +GDScriptCache *GDScriptCache::singleton = nullptr; + +void GDScriptCache::remove_script(const String &p_path) { + MutexLock(singleton->lock); + singleton->shallow_gdscript_cache.erase(p_path); + singleton->full_gdscript_cache.erase(p_path); +} + +Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) { + MutexLock(singleton->lock); + Ref<GDScriptParserRef> ref; + if (p_owner != String()) { + singleton->dependencies[p_owner].insert(p_path); + } + if (singleton->parser_map.has(p_path)) { + ref = singleton->parser_map[p_path]; + } else { + GDScriptParser *parser = memnew(GDScriptParser); + ref.instance(); + ref->parser = parser; + ref->path = p_path; + singleton->parser_map[p_path] = ref; + ref->unreference(); + } + + r_error = ref->raise_status(p_status); + + return ref; +} + +String GDScriptCache::get_source_code(const String &p_path) { + Vector<uint8_t> source_file; + Error err; + FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err); + if (err) { + ERR_FAIL_COND_V(err, ""); + } + + int len = f->get_len(); + source_file.resize(len + 1); + int r = f->get_buffer(source_file.ptrw(), len); + f->close(); + ERR_FAIL_COND_V(r != len, ""); + source_file.write[len] = 0; + + String source; + if (source.parse_utf8((const char *)source_file.ptr())) { + ERR_FAIL_V_MSG("", "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode."); + } + return source; +} + +Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const String &p_owner) { + MutexLock(singleton->lock); + if (p_owner != String()) { + singleton->dependencies[p_owner].insert(p_path); + } + if (singleton->full_gdscript_cache.has(p_path)) { + return singleton->full_gdscript_cache[p_path]; + } + if (singleton->shallow_gdscript_cache.has(p_path)) { + return singleton->shallow_gdscript_cache[p_path]; + } + + Ref<GDScript> script; + script.instance(); + script->set_path(p_path, true); + script->set_script_path(p_path); + script->load_source_code(p_path); + + singleton->shallow_gdscript_cache[p_path] = script; + // The one in cache is not a hard reference: if the script dies somewhere else it's fine. + // Scripts remove themselves from cache when they die. + script->unreference(); + return script; +} + +Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner) { + MutexLock(singleton->lock); + + if (p_owner != String()) { + singleton->dependencies[p_owner].insert(p_path); + } + + r_error = OK; + if (singleton->full_gdscript_cache.has(p_path)) { + return singleton->full_gdscript_cache[p_path]; + } + Ref<GDScript> script = get_shallow_script(p_path); + + r_error = script->load_source_code(p_path); + + if (r_error) { + return script; + } + + r_error = script->reload(); + if (r_error) { + return script; + } + + singleton->full_gdscript_cache[p_path] = script; + singleton->shallow_gdscript_cache.erase(p_path); + + return script; +} + +Error GDScriptCache::finish_compiling(const String &p_owner) { + // Mark this as compiled. + Ref<GDScript> script = get_shallow_script(p_owner); + singleton->full_gdscript_cache[p_owner] = script; + singleton->shallow_gdscript_cache.erase(p_owner); + + Set<String> depends = singleton->dependencies[p_owner]; + + Error err = OK; + for (const Set<String>::Element *E = depends.front(); E != nullptr; E = E->next()) { + Error this_err = OK; + // No need to save the script. We assume it's already referenced in the owner. + get_full_script(E->get(), this_err); + + if (this_err != OK) { + err = this_err; + } + } + + singleton->dependencies.erase(p_owner); + + return err; +} + +GDScriptCache::GDScriptCache() { + singleton = this; +} + +GDScriptCache::~GDScriptCache() { + parser_map.clear(); + shallow_gdscript_cache.clear(); + full_gdscript_cache.clear(); + singleton = nullptr; +} diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h new file mode 100644 index 0000000000..770704d6eb --- /dev/null +++ b/modules/gdscript/gdscript_cache.h @@ -0,0 +1,97 @@ +/*************************************************************************/ +/* gdscript_cache.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_CACHE_H +#define GDSCRIPT_CACHE_H + +#include "core/hash_map.h" +#include "core/os/mutex.h" +#include "core/reference.h" +#include "core/set.h" +#include "gdscript.h" + +class GDScriptAnalyzer; +class GDScriptParser; + +class GDScriptParserRef : public Reference { +public: + enum Status { + EMPTY, + PARSED, + INHERITANCE_SOLVED, + INTERFACE_SOLVED, + FULLY_SOLVED, + }; + +private: + GDScriptParser *parser = nullptr; + GDScriptAnalyzer *analyzer = nullptr; + Status status = EMPTY; + String path; + + friend class GDScriptCache; + +public: + bool is_valid() const; + Status get_status() const; + GDScriptParser *get_parser() const; + Error raise_status(Status p_new_status); + + GDScriptParserRef() {} + ~GDScriptParserRef(); +}; + +class GDScriptCache { + // String key is full path. + HashMap<String, Ref<GDScriptParserRef>> parser_map; + HashMap<String, Ref<GDScript>> shallow_gdscript_cache; + HashMap<String, Ref<GDScript>> full_gdscript_cache; + HashMap<String, Set<String>> dependencies; + + friend class GDScript; + friend class GDScriptParserRef; + + static GDScriptCache *singleton; + + Mutex lock; + static void remove_script(const String &p_path); + +public: + static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String()); + static String get_source_code(const String &p_path); + static Ref<GDScript> get_shallow_script(const String &p_path, const String &p_owner = String()); + static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String()); + static Error finish_compiling(const String &p_owner); + + GDScriptCache(); + ~GDScriptCache(); +}; + +#endif // GDSCRIPT_CACHE_H diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 2bbec29043..e34d87f5cc 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -31,26 +31,27 @@ #include "gdscript_compiler.h" #include "gdscript.h" +#include "gdscript_cache.h" bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) { - - if (codegen.function_node && codegen.function_node->_static) + if (codegen.function_node && codegen.function_node->is_static) { return false; + } - if (codegen.stack_identifiers.has(p_name)) + if (codegen.stack_identifiers.has(p_name)) { return false; //shadowed + } return _is_class_member_property(codegen.script, p_name); } bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringName &p_name) { - GDScript *scr = owner; GDScriptNativeClass *nc = nullptr; while (scr) { - - if (scr->native.is_valid()) + if (scr->native.is_valid()) { nc = scr->native.ptr(); + } scr = scr->_base; } @@ -60,27 +61,25 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa } void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::Node *p_node) { - - if (error != "") + if (error != "") { return; + } error = p_error; if (p_node) { - err_line = p_node->line; - err_column = p_node->column; + err_line = p_node->start_line; + err_column = p_node->leftmost_column; } else { err_line = 0; err_column = 0; } } -bool GDScriptCompiler::_create_unary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level) { - - ERR_FAIL_COND_V(on->arguments.size() != 1, false); - - int src_address_a = _parse_expression(codegen, on->arguments[0], p_stack_level); - if (src_address_a < 0) +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 @@ -90,19 +89,19 @@ bool GDScriptCompiler::_create_unary_operator(CodeGen &codegen, const GDScriptPa return true; } -bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level, bool p_initializer, int p_index_addr) { - - ERR_FAIL_COND_V(on->arguments.size() != 2, false); - - int src_address_a = _parse_expression(codegen, on->arguments[0], p_stack_level, false, p_initializer, p_index_addr); - if (src_address_a < 0) +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) + } + 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, on->arguments[1], p_stack_level, false, p_initializer); - if (src_address_b < 0) + 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 @@ -111,8 +110,12 @@ bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptP return true; } +bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer, int p_index_addr) { + return _create_binary_operator(codegen, on->left_operand, on->right_operand, op, p_stack_level, p_initializer, p_index_addr); +} + GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const { - if (!p_datatype.has_type) { + if (!p_datatype.is_set() || !p_datatype.is_hard_type()) { return GDScriptDataType(); } @@ -120,6 +123,9 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.has_type = true; switch (p_datatype.kind) { + case GDScriptParser::DataType::VARIANT: { + result.has_type = false; + } break; case GDScriptParser::DataType::BUILTIN: { result.kind = GDScriptDataType::BUILTIN; result.builtin_type = p_datatype.builtin_type; @@ -133,36 +139,49 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.script_type = p_datatype.script_type; result.native_type = result.script_type->get_instance_base_type(); } break; - case GDScriptParser::DataType::GDSCRIPT: { - result.kind = GDScriptDataType::GDSCRIPT; - result.script_type = p_datatype.script_type; - result.native_type = result.script_type->get_instance_base_type(); - } break; case GDScriptParser::DataType::CLASS: { // Locate class by constructing the path to it and following that path GDScriptParser::ClassNode *class_type = p_datatype.class_type; - List<StringName> names; - while (class_type->owner) { - names.push_back(class_type->name); - class_type = class_type->owner; - } + if (class_type) { + if (class_type->fqcn.begins_with(main_script->path) || (!main_script->name.empty() && class_type->fqcn.begins_with(main_script->name))) { + // Local class. + List<StringName> names; + while (class_type->outer) { + names.push_back(class_type->identifier->name); + class_type = class_type->outer; + } - Ref<GDScript> script = Ref<GDScript>(main_script); - while (names.back()) { - if (!script->subclasses.has(names.back()->get())) { - ERR_PRINT("Parser bug: Cannot locate datatype class."); - result.has_type = false; - return GDScriptDataType(); + Ref<GDScript> script = Ref<GDScript>(main_script); + while (names.back()) { + if (!script->subclasses.has(names.back()->get())) { + ERR_PRINT("Parser bug: Cannot locate datatype class."); + result.has_type = false; + return GDScriptDataType(); + } + script = script->subclasses[names.back()->get()]; + names.pop_back(); + } + result.kind = GDScriptDataType::GDSCRIPT; + result.script_type = script; + result.native_type = script->get_instance_base_type(); + } else { + result.kind = GDScriptDataType::GDSCRIPT; + result.script_type = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path); + result.native_type = p_datatype.native_type; } - script = script->subclasses[names.back()->get()]; - names.pop_back(); } - - result.kind = GDScriptDataType::GDSCRIPT; - result.script_type = script; - result.native_type = script->get_instance_base_type(); } break; - default: { + case GDScriptParser::DataType::ENUM_VALUE: + result.has_type = true; + result.kind = GDScriptDataType::BUILTIN; + result.builtin_type = Variant::INT; + break; + case GDScriptParser::DataType::ENUM: + result.has_type = true; + result.kind = GDScriptDataType::BUILTIN; + result.builtin_type = Variant::DICTIONARY; + break; + case GDScriptParser::DataType::UNRESOLVED: { ERR_PRINT("Parser bug: converting unresolved type."); return GDScriptDataType(); } @@ -171,42 +190,57 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D return result; } -int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level, int p_index_addr) { - +int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::AssignmentNode *p_assignment, int p_stack_level, int p_index_addr) { Variant::Operator var_op = Variant::OP_MAX; - switch (p_expression->op) { - - case GDScriptParser::OperatorNode::OP_ASSIGN_ADD: var_op = Variant::OP_ADD; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_SUB: var_op = Variant::OP_SUBTRACT; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_MUL: var_op = Variant::OP_MULTIPLY; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_DIV: var_op = Variant::OP_DIVIDE; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_MOD: var_op = Variant::OP_MODULE; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT: var_op = Variant::OP_SHIFT_LEFT; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT: var_op = Variant::OP_SHIFT_RIGHT; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_AND: var_op = Variant::OP_BIT_AND; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_OR: var_op = Variant::OP_BIT_OR; break; - case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_XOR: var_op = Variant::OP_BIT_XOR; break; - case GDScriptParser::OperatorNode::OP_INIT_ASSIGN: - case GDScriptParser::OperatorNode::OP_ASSIGN: { - + 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); } } - bool initializer = p_expression->op == GDScriptParser::OperatorNode::OP_INIT_ASSIGN; + // bool initializer = p_expression->op == GDScriptParser::OperatorNode::OP_INIT_ASSIGN; if (var_op == Variant::OP_MAX) { - - return _parse_expression(codegen, p_expression->arguments[1], p_stack_level, false, initializer); + return _parse_expression(codegen, p_assignment->assigned_value, p_stack_level, false, false); } - if (!_create_binary_operator(codegen, p_expression, var_op, p_stack_level, initializer, p_index_addr)) + if (!_create_binary_operator(codegen, p_assignment->assignee, p_assignment->assigned_value, var_op, p_stack_level, false, p_index_addr)) { return -1; + } 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 @@ -214,11 +248,74 @@ int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDS return dst_addr; } -int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::Node *p_expression, int p_stack_level, bool p_root, bool p_initializer, int p_index_addr) { +bool GDScriptCompiler::_generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type) { + if (p_datatype.has_type && p_value_type.is_variant()) { + // Typed assignment + switch (p_datatype.kind) { + case GDScriptDataType::BUILTIN: { + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator + codegen.opcodes.push_back(p_datatype.builtin_type); // variable type + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 + } break; + case GDScriptDataType::NATIVE: { + int class_idx; + if (GDScriptLanguage::get_singleton()->get_global_map().has(p_datatype.native_type)) { + class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_datatype.native_type]; + class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) + } else { + // _set_error("Invalid native class type '" + String(p_datatype.native_type) + "'.", on->arguments[0]); + return false; + } + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator + codegen.opcodes.push_back(class_idx); // variable type + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 + } break; + case GDScriptDataType::SCRIPT: + case GDScriptDataType::GDSCRIPT: { + Variant script = p_datatype.script_type; + int idx = codegen.get_constant_pos(script); //make it a local constant (faster access) + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator + codegen.opcodes.push_back(idx); // variable type + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 + } break; + default: { + ERR_PRINT("Compiler bug: unresolved assign."); + + // Shouldn't get here, but fail-safe to a regular assignment + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter) + } + } + } else { + if (p_datatype.kind == GDScriptDataType::BUILTIN && p_value_type.kind == GDScriptParser::DataType::BUILTIN && p_datatype.builtin_type != p_value_type.builtin_type) { + // Need conversion. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator + codegen.opcodes.push_back(p_datatype.builtin_type); // variable type + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 + } else { + // Either untyped assignment or already type-checked by the parser + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator + codegen.opcodes.push_back(p_dst_address); // argument 1 + codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter) + } + } + return true; +} + +int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root, bool p_initializer, int p_index_addr) { + if (p_expression->is_constant) { + return codegen.get_constant_pos(p_expression->reduced_value); + } switch (p_expression->type) { //should parse variable declaration and adjust stack accordingly... - case GDScriptParser::Node::TYPE_IDENTIFIER: { + case GDScriptParser::Node::IDENTIFIER: { //return identifier //wait, identifier could be a local variable or something else... careful here, must reference properly //as stack may be more interesting to work with @@ -231,11 +328,15 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: // 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 LOCAL CONSTANTS! + if (codegen.local_named_constants.has(identifier)) { + return codegen.local_named_constants[identifier] | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); + } + // TRY CLASS MEMBER if (_is_class_member_property(codegen, identifier)) { //get property @@ -248,14 +349,26 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: } //TRY MEMBERS! - if (!codegen.function_node || !codegen.function_node->_static) { - + if (!codegen.function_node || !codegen.function_node->is_static) { // TRY MEMBER VARIABLES! //static function if (codegen.script->member_indices.has(identifier)) { - - int idx = codegen.script->member_indices[identifier].index; - return idx | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); //argument (stack root) + if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) { + // Perform getter. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_RETURN); + codegen.opcodes.push_back(0); // Argument count. + codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Base (self). + codegen.opcodes.push_back(codegen.get_name_map_pos(codegen.script->member_indices[identifier].getter)); // Method name. + // Destination. + int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode + codegen.alloc_stack(p_stack_level); + return dst_addr; + } else { + // No getter or inside getter: direct member access. + int idx = codegen.script->member_indices[identifier].index; + return idx | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); //argument (stack root) + } } } @@ -263,26 +376,23 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: 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) } - if (scr->native.is_valid()) + if (scr->native.is_valid()) { nc = scr->native.ptr(); + } scr = scr->_base; } // CLASS C++ Integer Constant if (nc) { - bool success = false; int constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success); if (success) { @@ -290,7 +400,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: int idx; if (!codegen.constant_map.has(key)) { - idx = codegen.constant_map.size(); codegen.constant_map[key] = idx; @@ -305,8 +414,22 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: owner = owner->_owner; } - if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { + // TRY SIGNALS AND METHODS (can be made callables) + if (codegen.class_node->members_indices.has(identifier)) { + const GDScriptParser::ClassNode::Member &member = codegen.class_node->members[codegen.class_node->members_indices[identifier]]; + if (member.type == GDScriptParser::ClassNode::Member::FUNCTION || member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + // Get like it was a property. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET_NAMED); // perform operator + codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Self. + codegen.opcodes.push_back(codegen.get_name_map_pos(identifier)); // argument 2 (unary only takes one parameter) + int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode + codegen.alloc_stack(p_stack_level); + return dst_addr; + } + } + if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) } @@ -314,28 +437,27 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: /* TRY GLOBAL CLASSES */ if (ScriptServer::is_global_class(identifier)) { - const GDScriptParser::ClassNode *class_node = codegen.class_node; - while (class_node->owner) { - class_node = class_node->owner; + while (class_node->outer) { + class_node = class_node->outer; } - if (class_node->name == identifier) { - _set_error("Using own name in class file is not allowed (creates a cyclic reference)", p_expression); - return -1; - } + RES res; - RES res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); - if (res.is_null()) { - _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); - return -1; + if (class_node->identifier && class_node->identifier->name == identifier) { + res = Ref<GDScript>(main_script); + } else { + res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); + if (res.is_null()) { + _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); + return -1; + } } Variant key = res; int idx; if (!codegen.constant_map.has(key)) { - idx = codegen.constant_map.size(); codegen.constant_map[key] = idx; @@ -348,7 +470,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: #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(); @@ -365,14 +486,13 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return -1; } break; - case GDScriptParser::Node::TYPE_CONSTANT: { + case GDScriptParser::Node::LITERAL: { //return constant - const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_expression); + const GDScriptParser::LiteralNode *cn = static_cast<const GDScriptParser::LiteralNode *>(p_expression); int idx; if (!codegen.constant_map.has(cn->value)) { - idx = codegen.constant_map.size(); codegen.constant_map[cn->value] = idx; @@ -383,26 +503,25 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root) } break; - case GDScriptParser::Node::TYPE_SELF: { + case GDScriptParser::Node::SELF: { //return constant - if (codegen.function_node && codegen.function_node->_static) { + if (codegen.function_node && codegen.function_node->is_static) { _set_error("'self' not present in static function!", p_expression); return -1; } return (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); } break; - case GDScriptParser::Node::TYPE_ARRAY: { - + case GDScriptParser::Node::ARRAY: { const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); Vector<int> values; int slevel = p_stack_level; for (int i = 0; i < an->elements.size(); i++) { - int ret = _parse_expression(codegen, an->elements[i], slevel); - if (ret < 0) + if (ret < 0) { return ret; + } if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); @@ -413,8 +532,9 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY); codegen.opcodes.push_back(values.size()); - for (int i = 0; i < values.size(); i++) + for (int i = 0; i < values.size(); i++) { codegen.opcodes.push_back(values[i]); + } 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 @@ -422,40 +542,53 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return dst_addr; } break; - case GDScriptParser::Node::TYPE_DICTIONARY: { - + case GDScriptParser::Node::DICTIONARY: { const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression); - Vector<int> values; + Vector<int> elements; int slevel = p_stack_level; for (int i = 0; i < dn->elements.size(); i++) { - - int ret = _parse_expression(codegen, dn->elements[i].key, slevel); - if (ret < 0) - return ret; - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + // Key. + int ret = -1; + switch (dn->style) { + case GDScriptParser::DictionaryNode::PYTHON_DICT: + // Python-style: key is any expression. + ret = _parse_expression(codegen, dn->elements[i].key, slevel); + if (ret < 0) { + return ret; + } + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { + slevel++; + codegen.alloc_stack(slevel); + } + break; + case GDScriptParser::DictionaryNode::LUA_TABLE: + // Lua-style: key is an identifier interpreted as string. + String key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name; + ret = codegen.get_constant_pos(key); + break; } - values.push_back(ret); + elements.push_back(ret); ret = _parse_expression(codegen, dn->elements[i].value, slevel); - if (ret < 0) + if (ret < 0) { return ret; + } if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } - values.push_back(ret); + elements.push_back(ret); } codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY); codegen.opcodes.push_back(dn->elements.size()); - for (int i = 0; i < values.size(); i++) - codegen.opcodes.push_back(values[i]); + for (int i = 0; i < elements.size(); i++) { + codegen.opcodes.push_back(elements[i]); + } int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode @@ -463,19 +596,20 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return dst_addr; } break; - case GDScriptParser::Node::TYPE_CAST: { + case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); int slevel = p_stack_level; - int src_addr = _parse_expression(codegen, cn->source_node, slevel); - if (src_addr < 0) + 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); } - GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type); + GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype()); switch (cast_type.kind) { case GDScriptDataType::BUILTIN: { @@ -485,7 +619,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: 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 { @@ -497,10 +630,8 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: } break; case GDScriptDataType::SCRIPT: case GDScriptDataType::GDSCRIPT: { - Variant script = cast_type.script_type; - int idx = codegen.get_constant_pos(script); - idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access) + int idx = codegen.get_constant_pos(script); //make it a local constant (faster access) codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator codegen.opcodes.push_back(idx); // variable type @@ -518,257 +649,322 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return dst_addr; } break; - case GDScriptParser::Node::TYPE_OPERATOR: { - //hell breaks loose - - const GDScriptParser::OperatorNode *on = static_cast<const GDScriptParser::OperatorNode *>(p_expression); - switch (on->op) { - - //call/constructor operator - case GDScriptParser::OperatorNode::OP_PARENT_CALL: { - - ERR_FAIL_COND_V(on->arguments.size() < 1, -1); + //hell breaks loose + +#define OPERATOR_RETURN \ + int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); \ + codegen.opcodes.push_back(dst_addr); \ + codegen.alloc_stack(p_stack_level); \ + return dst_addr + + case GDScriptParser::Node::CALL: { + const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression); + if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != Variant::VARIANT_MAX) { + //construct a basic type + + Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); + + Vector<int> arguments; + int slevel = p_stack_level; + for (int i = 0; i < call->arguments.size(); i++) { + int ret = _parse_expression(codegen, call->arguments[i], slevel); + if (ret < 0) { + return ret; + } + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { + slevel++; + codegen.alloc_stack(slevel); + } + arguments.push_back(ret); + } - const GDScriptParser::IdentifierNode *in = (const GDScriptParser::IdentifierNode *)on->arguments[0]; + //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 + } - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 1; i < on->arguments.size(); i++) { + } 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 - int ret = _parse_expression(codegen, on->arguments[i], slevel); - if (ret < 0) - return ret; - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - arguments.push_back(ret); + Vector<int> arguments; + int slevel = p_stack_level; + for (int i = 0; i < call->arguments.size(); i++) { + int ret = _parse_expression(codegen, call->arguments[i], slevel); + if (ret < 0) { + return ret; } - //push call bytecode - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_SELF_BASE); // basic type constructor + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { + slevel++; + codegen.alloc_stack(slevel); + } - codegen.opcodes.push_back(codegen.get_name_map_pos(in->name)); //instance - codegen.opcodes.push_back(arguments.size()); //argument count - codegen.alloc_call(arguments.size()); - for (int i = 0; i < arguments.size(); i++) - codegen.opcodes.push_back(arguments[i]); //arguments + arguments.push_back(ret); + } - } break; - case GDScriptParser::OperatorNode::OP_CALL: { + 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]); + } - if (on->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { - //construct a basic type - ERR_FAIL_COND_V(on->arguments.size() < 1, -1); + } else { + //regular function - const GDScriptParser::TypeNode *tn = (const GDScriptParser::TypeNode *)on->arguments[0]; - int vtype = tn->vtype; + const GDScriptParser::ExpressionNode *callee = call->callee; - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 1; i < on->arguments.size(); i++) { + Vector<int> arguments; + int slevel = p_stack_level; - int ret = _parse_expression(codegen, on->arguments[i], slevel); - if (ret < 0) - return ret; - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - arguments.push_back(ret); + // TODO: Use callables when possible if needed. + int ret = -1; + int super_address = -1; + if (call->is_super) { + // Super call. + if (call->callee == nullptr) { + // Implicit super function call. + super_address = codegen.get_name_map_pos(codegen.function_node->identifier->name); + } else { + super_address = codegen.get_name_map_pos(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); + } + } else { + if (callee->type == GDScriptParser::Node::IDENTIFIER) { + // Self function call. + if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") { + ret = (GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS); + } else { + ret = (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); } + arguments.push_back(ret); + ret = codegen.get_name_map_pos(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); + arguments.push_back(ret); + } else if (callee->type == GDScriptParser::Node::SUBSCRIPT) { + const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee); - //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 - - } else if (on->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - //built in function - - ERR_FAIL_COND_V(on->arguments.size() < 1, -1); - - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 1; i < on->arguments.size(); i++) { - - int ret = _parse_expression(codegen, on->arguments[i], slevel); - if (ret < 0) + if (subscript->is_attribute) { + ret = _parse_expression(codegen, subscript->base, slevel); + if (ret < 0) { return ret; - + } if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } - arguments.push_back(ret); + arguments.push_back(codegen.get_name_map_pos(subscript->attribute->name)); + } else { + _set_error("Cannot call something that isn't a function.", call->callee); + return -1; } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(static_cast<const GDScriptParser::BuiltInFunctionNode *>(on->arguments[0])->function); - codegen.opcodes.push_back(on->arguments.size() - 1); - codegen.alloc_call(on->arguments.size() - 1); - for (int i = 0; i < arguments.size(); i++) - codegen.opcodes.push_back(arguments[i]); - } else { - //regular function - ERR_FAIL_COND_V(on->arguments.size() < 2, -1); - - const GDScriptParser::Node *instance = on->arguments[0]; - - if (instance->type == GDScriptParser::Node::TYPE_SELF) { - //room for optimization - } - - Vector<int> arguments; - int slevel = p_stack_level; - - for (int i = 0; i < on->arguments.size(); i++) { - - int ret; - - if (i == 0 && on->arguments[i]->type == GDScriptParser::Node::TYPE_SELF && codegen.function_node && codegen.function_node->_static) { - //static call to self - ret = (GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS); - } else if (i == 1) { - - if (on->arguments[i]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { - _set_error("Attempt to call a non-identifier.", on); - return -1; - } - GDScriptParser::IdentifierNode *id = static_cast<GDScriptParser::IdentifierNode *>(on->arguments[i]); - ret = codegen.get_name_map_pos(id->name); - - } else { - - ret = _parse_expression(codegen, on->arguments[i], slevel); - if (ret < 0) - return ret; - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - } - arguments.push_back(ret); - } + _set_error("Cannot call something that isn't a function.", call->callee); + return -1; + } + } - codegen.opcodes.push_back(p_root ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); // perform operator - codegen.opcodes.push_back(on->arguments.size() - 2); - codegen.alloc_call(on->arguments.size() - 2); - for (int i = 0; i < arguments.size(); i++) - codegen.opcodes.push_back(arguments[i]); + for (int i = 0; i < call->arguments.size(); i++) { + ret = _parse_expression(codegen, call->arguments[i], slevel); + if (ret < 0) { + return ret; } - } break; - case GDScriptParser::OperatorNode::OP_YIELD: { + if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { + slevel++; + codegen.alloc_stack(slevel); + } + arguments.push_back(ret); + } - ERR_FAIL_COND_V(on->arguments.size() && on->arguments.size() != 2, -1); + 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; + } - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 0; i < on->arguments.size(); i++) { + codegen.opcodes.push_back(opcode); // perform operator + if (call->is_super) { + codegen.opcodes.push_back(super_address); + } + codegen.opcodes.push_back(call->arguments.size()); + codegen.alloc_call(call->arguments.size()); + for (int i = 0; i < arguments.size(); i++) { + codegen.opcodes.push_back(arguments[i]); + } + } + OPERATOR_RETURN; + } break; + case GDScriptParser::Node::GET_NODE: { + const GDScriptParser::GetNodeNode *get_node = static_cast<const GDScriptParser::GetNodeNode *>(p_expression); - int ret = _parse_expression(codegen, on->arguments[i], slevel); - if (ret < 0) - return ret; - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - arguments.push_back(ret); + String node_name; + if (get_node->string != nullptr) { + node_name += String(get_node->string->value); + } else { + for (int i = 0; i < get_node->chain.size(); i++) { + if (i > 0) { + node_name += "/"; } + node_name += get_node->chain[i]->name; + } + } - //push call bytecode - codegen.opcodes.push_back(arguments.size() == 0 ? GDScriptFunction::OPCODE_YIELD : GDScriptFunction::OPCODE_YIELD_SIGNAL); // basic type constructor - for (int i = 0; i < arguments.size(); i++) - codegen.opcodes.push_back(arguments[i]); //arguments - codegen.opcodes.push_back(GDScriptFunction::OPCODE_YIELD_RESUME); - //next will be where to place the result :) + int arg_address = codegen.get_constant_pos(NodePath(node_name)); - } break; + codegen.opcodes.push_back(p_root ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); + codegen.opcodes.push_back(1); // number of arguments. + codegen.alloc_call(1); + codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // self. + codegen.opcodes.push_back(codegen.get_name_map_pos("get_node")); // function. + codegen.opcodes.push_back(arg_address); // argument (NodePath). + OPERATOR_RETURN; + } break; + case GDScriptParser::Node::PRELOAD: { + const GDScriptParser::PreloadNode *preload = static_cast<const GDScriptParser::PreloadNode *>(p_expression); - //indexing operator - case GDScriptParser::OperatorNode::OP_INDEX: - case GDScriptParser::OperatorNode::OP_INDEX_NAMED: { + // Add resource as constant. + return codegen.get_constant_pos(preload->resource); + } break; + case GDScriptParser::Node::AWAIT: { + const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression); - ERR_FAIL_COND_V(on->arguments.size() != 2, -1); + int slevel = p_stack_level; + within_await = true; + int argument = _parse_expression(codegen, await->to_await, slevel); + within_await = false; + if (argument < 0) { + return argument; + } + if ((argument >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { + slevel++; + codegen.alloc_stack(slevel); + } + //push call bytecode + codegen.opcodes.push_back(GDScriptFunction::OPCODE_AWAIT); + codegen.opcodes.push_back(argument); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_AWAIT_RESUME); + //next will be where to place the result :) - int slevel = p_stack_level; - bool named = (on->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED); + OPERATOR_RETURN; + } break; + + //indexing operator + case GDScriptParser::Node::SUBSCRIPT: { + int slevel = p_stack_level; - int from = _parse_expression(codegen, on->arguments[0], slevel); - if (from < 0) - return from; + const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression); - int index; - if (p_index_addr != 0) { - index = p_index_addr; - } else if (named) { - if (on->arguments[0]->type == GDScriptParser::Node::TYPE_SELF && codegen.script && codegen.function_node && !codegen.function_node->_static) { + int from = _parse_expression(codegen, subscript->base, slevel); + if (from < 0) { + return from; + } - GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(on->arguments[1]); - const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(identifier->name); + bool named = subscript->is_attribute; + int index; + if (p_index_addr != 0) { + index = p_index_addr; + } else if (subscript->is_attribute) { + if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { + GDScriptParser::IdentifierNode *identifier = subscript->attribute; + const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(identifier->name); #ifdef DEBUG_ENABLED - if (MI && MI->get().getter == codegen.function_node->name) { - String n = static_cast<GDScriptParser::IdentifierNode *>(on->arguments[1])->name; - _set_error("Must use '" + n + "' instead of 'self." + n + "' in getter.", on); - return -1; - } + if (MI && MI->get().getter == codegen.function_name) { + String n = identifier->name; + _set_error("Must use '" + n + "' instead of 'self." + n + "' in getter.", identifier); + return -1; + } #endif - if (MI && MI->get().getter == "") { - // Faster than indexing self (as if no self. had been used) - return (MI->get().index) | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); - } - } - - index = codegen.get_name_map_pos(static_cast<GDScriptParser::IdentifierNode *>(on->arguments[1])->name); - - } else { + if (MI && MI->get().getter == "") { + // Faster than indexing self (as if no self. had been used) + return (MI->get().index) | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); + } + } - if (on->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT && static_cast<const GDScriptParser::ConstantNode *>(on->arguments[1])->value.get_type() == Variant::STRING) { - //also, somehow, named (speed up anyway) - StringName name = static_cast<const GDScriptParser::ConstantNode *>(on->arguments[1])->value; - index = codegen.get_name_map_pos(name); - named = true; + index = codegen.get_name_map_pos(subscript->attribute->name); - } else { - //regular indexing - if (from & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + } else { + if (subscript->index->type == GDScriptParser::Node::LITERAL && static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value.get_type() == Variant::STRING) { + //also, somehow, named (speed up anyway) + StringName name = static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value; + index = codegen.get_name_map_pos(name); + named = true; - index = _parse_expression(codegen, on->arguments[1], slevel); - if (index < 0) - return index; - } + } else { + //regular indexing + if (from & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); } - codegen.opcodes.push_back(named ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET); // perform operator - codegen.opcodes.push_back(from); // argument 1 - codegen.opcodes.push_back(index); // argument 2 (unary only takes one parameter) + index = _parse_expression(codegen, subscript->index, slevel); + if (index < 0) { + return index; + } + } + } + codegen.opcodes.push_back(named ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET); // perform operator + codegen.opcodes.push_back(from); // argument 1 + codegen.opcodes.push_back(index); // argument 2 (unary only takes one parameter) + OPERATOR_RETURN; + } break; + case GDScriptParser::Node::UNARY_OPERATOR: { + //unary operators + const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression); + switch (unary->operation) { + case GDScriptParser::UnaryOpNode::OP_NEGATIVE: { + if (!_create_unary_operator(codegen, unary, Variant::OP_NEGATE, p_stack_level)) { + return -1; + } + } break; + case GDScriptParser::UnaryOpNode::OP_POSITIVE: { + if (!_create_unary_operator(codegen, unary, Variant::OP_POSITIVE, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_AND: { + 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; + } + OPERATOR_RETURN; + } + case GDScriptParser::Node::BINARY_OPERATOR: { + //binary operators (in precedence order) + const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); + switch (binary->operation) { + case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: { // AND operator with early out on failure - int res = _parse_expression(codegen, on->arguments[0], p_stack_level); - if (res < 0) + int res = _parse_expression(codegen, binary->left_operand, 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); - res = _parse_expression(codegen, on->arguments[1], p_stack_level); - if (res < 0) + res = _parse_expression(codegen, binary->right_operand, p_stack_level); + if (res < 0) { return res; + } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(res); @@ -787,21 +983,22 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; } break; - case GDScriptParser::OperatorNode::OP_OR: { - + case GDScriptParser::BinaryOpNode::OP_LOGIC_OR: { // OR operator with early out on success - int res = _parse_expression(codegen, on->arguments[0], p_stack_level); - if (res < 0) + int res = _parse_expression(codegen, binary->left_operand, p_stack_level); + if (res < 0) { return res; + } 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, on->arguments[1], p_stack_level); - if (res < 0) + res = _parse_expression(codegen, binary->right_operand, p_stack_level); + if (res < 0) { return res; + } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF); codegen.opcodes.push_back(res); @@ -820,713 +1017,1036 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; } break; - // ternary operators - case GDScriptParser::OperatorNode::OP_TERNARY_IF: { - - // x IF a ELSE y operator with early out on failure - - int res = _parse_expression(codegen, on->arguments[0], 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); - - res = _parse_expression(codegen, on->arguments[1], p_stack_level); - if (res < 0) - return res; - - 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); + case GDScriptParser::BinaryOpNode::OP_TYPE_TEST: { + int slevel = p_stack_level; - codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); - res = _parse_expression(codegen, on->arguments[2], p_stack_level); - if (res < 0) - return res; + int src_address_a = _parse_expression(codegen, binary->left_operand, slevel); + if (src_address_a < 0) { + return -1; + } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(res); + if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + slevel++; //uses stack for return, increase stack + } - codegen.opcodes.write[jump_past_pos] = codegen.opcodes.size(); + int src_address_b = -1; + bool builtin = false; + if (binary->right_operand->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name) != Variant::VARIANT_MAX) { + // `is` with builtin type + builtin = true; + src_address_b = (int)GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name); + } else { + src_address_b = _parse_expression(codegen, binary->right_operand, slevel); + if (src_address_b < 0) { + return -1; + } + } - return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + codegen.opcodes.push_back(builtin ? GDScriptFunction::OPCODE_IS_BUILTIN : GDScriptFunction::OPCODE_EXTENDS_TEST); // perform operator + codegen.opcodes.push_back(src_address_a); // argument 1 + codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) } break; - //unary operators - case GDScriptParser::OperatorNode::OP_NEG: { - if (!_create_unary_operator(codegen, on, Variant::OP_NEGATE, p_stack_level)) return -1; - } break; - case GDScriptParser::OperatorNode::OP_POS: { - if (!_create_unary_operator(codegen, on, Variant::OP_POSITIVE, p_stack_level)) return -1; - } break; - case GDScriptParser::OperatorNode::OP_NOT: { - if (!_create_unary_operator(codegen, on, Variant::OP_NOT, p_stack_level)) return -1; - } break; - case GDScriptParser::OperatorNode::OP_BIT_INVERT: { - if (!_create_unary_operator(codegen, on, Variant::OP_BIT_NEGATE, p_stack_level)) return -1; - } break; - //binary operators (in precedence order) - case GDScriptParser::OperatorNode::OP_IN: { - if (!_create_binary_operator(codegen, on, Variant::OP_IN, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_CONTENT_TEST: { + if (!_create_binary_operator(codegen, binary, Variant::OP_IN, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_EQUAL: { - if (!_create_binary_operator(codegen, on, Variant::OP_EQUAL, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_COMP_EQUAL: { + if (!_create_binary_operator(codegen, binary, Variant::OP_EQUAL, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_NOT_EQUAL: { - if (!_create_binary_operator(codegen, on, Variant::OP_NOT_EQUAL, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_COMP_NOT_EQUAL: { + if (!_create_binary_operator(codegen, binary, Variant::OP_NOT_EQUAL, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_LESS: { - if (!_create_binary_operator(codegen, on, Variant::OP_LESS, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_COMP_LESS: { + if (!_create_binary_operator(codegen, binary, Variant::OP_LESS, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_LESS_EQUAL: { - if (!_create_binary_operator(codegen, on, Variant::OP_LESS_EQUAL, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_COMP_LESS_EQUAL: { + if (!_create_binary_operator(codegen, binary, Variant::OP_LESS_EQUAL, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_GREATER: { - if (!_create_binary_operator(codegen, on, Variant::OP_GREATER, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_COMP_GREATER: { + if (!_create_binary_operator(codegen, binary, Variant::OP_GREATER, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_GREATER_EQUAL: { - if (!_create_binary_operator(codegen, on, Variant::OP_GREATER_EQUAL, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_COMP_GREATER_EQUAL: { + if (!_create_binary_operator(codegen, binary, Variant::OP_GREATER_EQUAL, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_ADD: { - if (!_create_binary_operator(codegen, on, Variant::OP_ADD, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_ADDITION: { + if (!_create_binary_operator(codegen, binary, Variant::OP_ADD, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_SUB: { - if (!_create_binary_operator(codegen, on, Variant::OP_SUBTRACT, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_SUBTRACTION: { + if (!_create_binary_operator(codegen, binary, Variant::OP_SUBTRACT, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_MUL: { - if (!_create_binary_operator(codegen, on, Variant::OP_MULTIPLY, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_MULTIPLICATION: { + if (!_create_binary_operator(codegen, binary, Variant::OP_MULTIPLY, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_DIV: { - if (!_create_binary_operator(codegen, on, Variant::OP_DIVIDE, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_DIVISION: { + if (!_create_binary_operator(codegen, binary, Variant::OP_DIVIDE, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_MOD: { - if (!_create_binary_operator(codegen, on, Variant::OP_MODULE, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_MODULO: { + if (!_create_binary_operator(codegen, binary, Variant::OP_MODULE, p_stack_level)) { + return -1; + } } break; - //case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_LEFT,p_stack_level)) return -1;} break; - //case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_RIGHT,p_stack_level)) return -1;} break; - case GDScriptParser::OperatorNode::OP_BIT_AND: { - if (!_create_binary_operator(codegen, on, Variant::OP_BIT_AND, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_BIT_AND: { + if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_AND, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_BIT_OR: { - if (!_create_binary_operator(codegen, on, Variant::OP_BIT_OR, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_BIT_OR: { + if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_OR, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_BIT_XOR: { - if (!_create_binary_operator(codegen, on, Variant::OP_BIT_XOR, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_BIT_XOR: { + if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_XOR, p_stack_level)) { + return -1; + } } break; //shift - case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: { - if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_LEFT, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_BIT_LEFT_SHIFT: { + if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_LEFT, p_stack_level)) { + return -1; + } } break; - case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: { - if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_RIGHT, p_stack_level)) return -1; + case GDScriptParser::BinaryOpNode::OP_BIT_RIGHT_SHIFT: { + if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_RIGHT, p_stack_level)) { + return -1; + } } break; - //assignment operators - case GDScriptParser::OperatorNode::OP_ASSIGN_ADD: - case GDScriptParser::OperatorNode::OP_ASSIGN_SUB: - case GDScriptParser::OperatorNode::OP_ASSIGN_MUL: - case GDScriptParser::OperatorNode::OP_ASSIGN_DIV: - case GDScriptParser::OperatorNode::OP_ASSIGN_MOD: - case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT: - case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT: - case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_AND: - case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_OR: - case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_XOR: - case GDScriptParser::OperatorNode::OP_INIT_ASSIGN: - case GDScriptParser::OperatorNode::OP_ASSIGN: { - - ERR_FAIL_COND_V(on->arguments.size() != 2, -1); - - if (on->arguments[0]->type == GDScriptParser::Node::TYPE_OPERATOR && (static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX || static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED)) { - - // SET (chained) MODE! -#ifdef DEBUG_ENABLED - if (static_cast<GDScriptParser::OperatorNode *>(on->arguments[0])->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) { - const GDScriptParser::OperatorNode *inon = static_cast<GDScriptParser::OperatorNode *>(on->arguments[0]); - - if (inon->arguments[0]->type == GDScriptParser::Node::TYPE_SELF && codegen.script && codegen.function_node && !codegen.function_node->_static) { - - const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(static_cast<GDScriptParser::IdentifierNode *>(inon->arguments[1])->name); - if (MI && MI->get().setter == codegen.function_node->name) { - String n = static_cast<GDScriptParser::IdentifierNode *>(inon->arguments[1])->name; - _set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", inon); - return -1; - } - } - } -#endif - - int slevel = p_stack_level; + } + OPERATOR_RETURN; + } break; + // ternary operators + case GDScriptParser::Node::TERNARY_OPERATOR: { + // x IF a ELSE y operator with early out on failure + + const GDScriptParser::TernaryOpNode *ternary = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression); + int res = _parse_expression(codegen, ternary->condition, p_stack_level); + if (res < 0) { + return res; + } + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(res); + int jump_fail_pos = codegen.opcodes.size(); + codegen.opcodes.push_back(0); - GDScriptParser::OperatorNode *op = static_cast<GDScriptParser::OperatorNode *>(on->arguments[0]); + res = _parse_expression(codegen, ternary->true_expr, p_stack_level); + if (res < 0) { + return res; + } - /* Find chain of sets */ + 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); - StringName assign_property; + codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); + res = _parse_expression(codegen, ternary->false_expr, p_stack_level); + if (res < 0) { + return res; + } - List<GDScriptParser::OperatorNode *> chain; + 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); - { - //create get/set chain - GDScriptParser::OperatorNode *n = op; - while (true) { + codegen.opcodes.write[jump_past_pos] = codegen.opcodes.size(); - chain.push_back(n); - if (n->arguments[0]->type != GDScriptParser::Node::TYPE_OPERATOR) { + return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - //check for a built-in property - if (n->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { + } break; + //assignment operators + case GDScriptParser::Node::ASSIGNMENT: { + const GDScriptParser::AssignmentNode *assignment = static_cast<const GDScriptParser::AssignmentNode *>(p_expression); - GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(n->arguments[0]); - if (_is_class_member_property(codegen, identifier->name)) { - assign_property = identifier->name; - } - } - break; - } - n = static_cast<GDScriptParser::OperatorNode *>(n->arguments[0]); - if (n->op != GDScriptParser::OperatorNode::OP_INDEX && n->op != GDScriptParser::OperatorNode::OP_INDEX_NAMED) - break; - } + if (assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) { + // SET (chained) MODE! + const GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(assignment->assignee); +#ifdef DEBUG_ENABLED + if (subscript->is_attribute) { + if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { + const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(subscript->attribute->name); + if (MI && MI->get().setter == codegen.function_name) { + String n = subscript->attribute->name; + _set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", subscript); + return -1; } + } + } +#endif - /* Chain of gets */ + int slevel = p_stack_level; - //get at (potential) root stack pos, so it can be returned - int prev_pos = _parse_expression(codegen, chain.back()->get()->arguments[0], slevel); - if (prev_pos < 0) - return prev_pos; - int retval = prev_pos; + /* Find chain of sets */ - if (retval & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + StringName assign_property; - Vector<int> setchain; + List<const GDScriptParser::SubscriptNode *> chain; - if (assign_property != StringName()) { + { + //create get/set chain + const GDScriptParser::SubscriptNode *n = subscript; + while (true) { + chain.push_back(n); - // recover and assign at the end, this allows stuff like - // position.x+=2.0 - // in Node2D - setchain.push_back(prev_pos); - setchain.push_back(codegen.get_name_map_pos(assign_property)); - setchain.push_back(GDScriptFunction::OPCODE_SET_MEMBER); + if (n->base->type != GDScriptParser::Node::SUBSCRIPT) { + //check for a built-in property + if (n->base->type == GDScriptParser::Node::IDENTIFIER) { + GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(n->base); + if (_is_class_member_property(codegen, identifier->name)) { + assign_property = identifier->name; + } + } + break; } + n = static_cast<const GDScriptParser::SubscriptNode *>(n->base); + } + } - for (List<GDScriptParser::OperatorNode *>::Element *E = chain.back(); E; E = E->prev()) { - - if (E == chain.front()) //ignore first - break; - - bool named = E->get()->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED; - int key_idx; + /* Chain of gets */ - if (named) { + //get at (potential) root stack pos, so it can be returned + int prev_pos = _parse_expression(codegen, chain.back()->get()->base, slevel); + if (prev_pos < 0) { + return prev_pos; + } + int retval = prev_pos; - key_idx = codegen.get_name_map_pos(static_cast<const GDScriptParser::IdentifierNode *>(E->get()->arguments[1])->name); - //printf("named key %x\n",key_idx); + if (retval & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } - } else { + Vector<int> setchain; - if (prev_pos & (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS)) { - slevel++; - codegen.alloc_stack(slevel); - } + 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); + } - GDScriptParser::Node *key = E->get()->arguments[1]; - key_idx = _parse_expression(codegen, key, slevel); - //printf("expr key %x\n",key_idx); + for (List<const GDScriptParser::SubscriptNode *>::Element *E = chain.back(); E; E = E->prev()) { + if (E == chain.front()) { //ignore first + break; + } - //stack was raised here if retval was stack but.. - } + const GDScriptParser::SubscriptNode *subscript_elem = E->get(); + int key_idx; - if (key_idx < 0) //error - return key_idx; + if (subscript_elem->is_attribute) { + key_idx = codegen.get_name_map_pos(subscript_elem->attribute->name); + //printf("named key %x\n",key_idx); - codegen.opcodes.push_back(named ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET); - codegen.opcodes.push_back(prev_pos); - codegen.opcodes.push_back(key_idx); + } else { + if (prev_pos & (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS)) { slevel++; codegen.alloc_stack(slevel); - int dst_pos = (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) | slevel; - - codegen.opcodes.push_back(dst_pos); - - //add in reverse order, since it will be reverted - - setchain.push_back(dst_pos); - setchain.push_back(key_idx); - setchain.push_back(prev_pos); - setchain.push_back(named ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET); - - prev_pos = dst_pos; } - setchain.invert(); + GDScriptParser::ExpressionNode *key = subscript_elem->index; + key_idx = _parse_expression(codegen, key, slevel); + //printf("expr key %x\n",key_idx); - int set_index; - bool named = false; + //stack was raised here if retval was stack but.. + } - if (op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) { + if (key_idx < 0) { //error + return key_idx; + } - set_index = codegen.get_name_map_pos(static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name); - named = true; - } else { + 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; - set_index = _parse_expression(codegen, op->arguments[1], slevel + 1); - named = false; - } + codegen.opcodes.push_back(dst_pos); - if (set_index < 0) //error - return set_index; + //add in reverse order, since it will be reverted - if (set_index & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + 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); - int set_value = _parse_assign_right_expression(codegen, on, slevel + 1, named ? 0 : set_index); - if (set_value < 0) //error - return set_value; + prev_pos = dst_pos; + } - codegen.opcodes.push_back(named ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET); - codegen.opcodes.push_back(prev_pos); - codegen.opcodes.push_back(set_index); - codegen.opcodes.push_back(set_value); + setchain.invert(); - for (int i = 0; i < setchain.size(); i++) { + int set_index; - codegen.opcodes.push_back(setchain[i]); - } + if (subscript->is_attribute) { + set_index = codegen.get_name_map_pos(subscript->attribute->name); + } else { + set_index = _parse_expression(codegen, subscript->index, slevel + 1); + } - return retval; + if (set_index < 0) { //error + return set_index; + } - } else if (on->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && _is_class_member_property(codegen, static_cast<GDScriptParser::IdentifierNode *>(on->arguments[0])->name)) { - //assignment to member property + if (set_index & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); + } - int slevel = p_stack_level; + int set_value = _parse_assign_right_expression(codegen, assignment, slevel + 1, subscript->is_attribute ? 0 : set_index); + if (set_value < 0) { //error + return set_value; + } - int src_address = _parse_assign_right_expression(codegen, on, slevel); - if (src_address < 0) - return -1; + codegen.opcodes.push_back(subscript->is_attribute ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET); + codegen.opcodes.push_back(prev_pos); + codegen.opcodes.push_back(set_index); + codegen.opcodes.push_back(set_value); - StringName name = static_cast<GDScriptParser::IdentifierNode *>(on->arguments[0])->name; + for (int i = 0; i < setchain.size(); i++) { + codegen.opcodes.push_back(setchain[i]); + } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_SET_MEMBER); - codegen.opcodes.push_back(codegen.get_name_map_pos(name)); - codegen.opcodes.push_back(src_address); + return retval; - return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; - } else { + } else if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER && _is_class_member_property(codegen, static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name)) { + //assignment to member property - //REGULAR ASSIGNMENT MODE!! + int slevel = p_stack_level; - int slevel = p_stack_level; + int src_address = _parse_assign_right_expression(codegen, assignment, slevel); + if (src_address < 0) { + return -1; + } - int dst_address_a = _parse_expression(codegen, on->arguments[0], slevel, false, on->op == GDScriptParser::OperatorNode::OP_INIT_ASSIGN); - if (dst_address_a < 0) - return -1; + StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (dst_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_SET_MEMBER); + codegen.opcodes.push_back(codegen.get_name_map_pos(name)); + codegen.opcodes.push_back(src_address); + + return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; + } else { + //REGULAR ASSIGNMENT MODE!! + + int slevel = p_stack_level; + int dst_address_a = -1; + + bool has_setter = false; + bool is_in_setter = false; + StringName setter_function; + if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { + StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; + if (!codegen.stack_identifiers.has(var_name) && codegen.script->member_indices.has(var_name)) { + setter_function = codegen.script->member_indices[var_name].setter; + if (setter_function != StringName()) { + has_setter = true; + is_in_setter = setter_function == codegen.function_name; + dst_address_a = codegen.script->member_indices[var_name].index; } + } + } - int src_address_b = _parse_assign_right_expression(codegen, on, slevel); - if (src_address_b < 0) - return -1; + if (has_setter) { + if (is_in_setter) { + // Use direct member access. + dst_address_a |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; + } else { + // Store stack slot for the temp value. + dst_address_a = slevel++; + codegen.alloc_stack(slevel); + dst_address_a |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + } + } else { + dst_address_a = _parse_expression(codegen, assignment->assignee, slevel); + if (dst_address_a < 0) { + return -1; + } - GDScriptDataType assign_type = _gdtype_from_datatype(on->arguments[0]->get_datatype()); - - if (assign_type.has_type && !on->datatype.has_type) { - // Typed assignment - switch (assign_type.kind) { - case GDScriptDataType::BUILTIN: { - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator - codegen.opcodes.push_back(assign_type.builtin_type); // variable type - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 - } break; - case GDScriptDataType::NATIVE: { - int class_idx; - if (GDScriptLanguage::get_singleton()->get_global_map().has(assign_type.native_type)) { - - class_idx = GDScriptLanguage::get_singleton()->get_global_map()[assign_type.native_type]; - class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) - } else { - _set_error("Invalid native class type '" + String(assign_type.native_type) + "'.", on->arguments[0]); - return -1; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator - codegen.opcodes.push_back(class_idx); // variable type - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 - } break; - case GDScriptDataType::SCRIPT: - case GDScriptDataType::GDSCRIPT: { - - Variant script = assign_type.script_type; - int idx = codegen.get_constant_pos(script); - idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access) - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator - codegen.opcodes.push_back(idx); // variable type - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 - } break; - default: { - ERR_PRINT("Compiler bug: unresolved assign."); - - // Shouldn't get here, but fail-safe to a regular assignment - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) - } - } - } else { - // Either untyped assignment or already type-checked by the parser - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator - codegen.opcodes.push_back(dst_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) - } - return dst_address_a; //if anything, returns wathever was assigned or correct stack position + if (dst_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { + slevel++; + codegen.alloc_stack(slevel); } - } break; - case GDScriptParser::OperatorNode::OP_IS: { + } - ERR_FAIL_COND_V(on->arguments.size() != 2, false); + int src_address_b = _parse_assign_right_expression(codegen, assignment, slevel); + if (src_address_b < 0) { + return -1; + } - int slevel = p_stack_level; + 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; + } - int src_address_a = _parse_expression(codegen, on->arguments[0], slevel); - if (src_address_a < 0) - return -1; + return dst_address_a; //if anything, returns wathever was assigned or correct stack position + } + } break; +#undef OPERATOR_RETURN + //TYPE_TYPE, + default: { + ERR_FAIL_V_MSG(-1, "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); //unreachable code + } break; + } +} - if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) - slevel++; //uses stack for return, increase stack +Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address) { + // TODO: Many "repeated" code here that could be abstracted. This compiler is going away when new VM arrives though, so... + switch (p_pattern->pattern_type) { + case GDScriptParser::PatternNode::PT_LITERAL: { + // Get literal type into constant map. + int literal_type_addr = -1; + if (!codegen.constant_map.has((int)p_pattern->literal->value.get_type())) { + literal_type_addr = codegen.constant_map.size(); + codegen.constant_map[(int)p_pattern->literal->value.get_type()] = literal_type_addr; - int src_address_b = _parse_expression(codegen, on->arguments[1], slevel); - if (src_address_b < 0) - return -1; + } else { + literal_type_addr = codegen.constant_map[(int)p_pattern->literal->value.get_type()]; + } + literal_type_addr |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_EXTENDS_TEST); // perform operator - codegen.opcodes.push_back(src_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) + // Check type equality. + int equality_addr = p_stack_level++; + equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + codegen.alloc_stack(p_stack_level); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); + codegen.opcodes.push_back(Variant::OP_EQUAL); + codegen.opcodes.push_back(p_type_addr); + codegen.opcodes.push_back(literal_type_addr); + codegen.opcodes.push_back(equality_addr); // Address to result. + + // Jump away if not the same type. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(equality_addr); + r_patch_addresses.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + + // Get literal. + int literal_addr = _parse_expression(codegen, p_pattern->literal, p_stack_level); + + // Check value equality. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); + codegen.opcodes.push_back(Variant::OP_EQUAL); + codegen.opcodes.push_back(p_value_addr); + codegen.opcodes.push_back(literal_addr); + codegen.opcodes.push_back(equality_addr); // Address to result. + + // Jump away if doesn't match. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(equality_addr); + r_patch_addresses.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + + // Jump to the actual block since it matches. This is needed to take multi-pattern into account. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); + r_block_patch_address.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + } break; + case GDScriptParser::PatternNode::PT_EXPRESSION: { + // Evaluate expression. + int expr_addr = _parse_expression(codegen, p_pattern->expression, p_stack_level); + if ((expr_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { + p_stack_level++; + codegen.alloc_stack(p_stack_level); + } - } break; - case GDScriptParser::OperatorNode::OP_IS_BUILTIN: { - ERR_FAIL_COND_V(on->arguments.size() != 2, false); - ERR_FAIL_COND_V(on->arguments[1]->type != GDScriptParser::Node::TYPE_TYPE, false); + // Evaluate expression type. + int expr_type_addr = p_stack_level++; + expr_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + codegen.alloc_stack(p_stack_level); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); + codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); + codegen.opcodes.push_back(1); // One argument. + codegen.opcodes.push_back(expr_addr); // Argument is the value we want to test. + codegen.opcodes.push_back(expr_type_addr); // Address to result. + + // Check type equality. + int equality_addr = p_stack_level++; + equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + codegen.alloc_stack(p_stack_level); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); + codegen.opcodes.push_back(Variant::OP_EQUAL); + codegen.opcodes.push_back(p_type_addr); + codegen.opcodes.push_back(expr_type_addr); + codegen.opcodes.push_back(equality_addr); // Address to result. + + // Jump away if not the same type. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(equality_addr); + r_patch_addresses.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + + // Check value equality. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); + codegen.opcodes.push_back(Variant::OP_EQUAL); + codegen.opcodes.push_back(p_value_addr); + codegen.opcodes.push_back(expr_addr); + codegen.opcodes.push_back(equality_addr); // Address to result. + + // Jump away if doesn't match. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(equality_addr); + r_patch_addresses.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + + // Jump to the actual block since it matches. This is needed to take multi-pattern into account. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); + r_block_patch_address.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + } break; + case GDScriptParser::PatternNode::PT_BIND: { + // Create new stack variable. + int bind_addr = p_stack_level | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); + codegen.add_stack_identifier(p_pattern->bind->name, p_stack_level++); + codegen.alloc_stack(p_stack_level); + r_bound_variables++; - int slevel = p_stack_level; + // Assign value to bound variable. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); + codegen.opcodes.push_back(bind_addr); // Destination. + codegen.opcodes.push_back(p_value_addr); // Source. + // Not need to block jump because bind happens only once. + } break; + case GDScriptParser::PatternNode::PT_ARRAY: { + int slevel = p_stack_level; - int src_address_a = _parse_expression(codegen, on->arguments[0], slevel); - if (src_address_a < 0) - return -1; + // Get array type into constant map. + int array_type_addr = codegen.get_constant_pos(Variant::ARRAY); + + // Check type equality. + int equality_addr = slevel++; + equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + codegen.alloc_stack(slevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); + codegen.opcodes.push_back(Variant::OP_EQUAL); + codegen.opcodes.push_back(p_type_addr); + codegen.opcodes.push_back(array_type_addr); + codegen.opcodes.push_back(equality_addr); // Address to result. + + // Jump away if not the same type. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(equality_addr); + r_patch_addresses.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + + // Store pattern length in constant map. + int array_length_addr = codegen.get_constant_pos(p_pattern->rest_used ? p_pattern->array.size() - 1 : p_pattern->array.size()); + + // Get value length. + int value_length_addr = slevel++; + codegen.alloc_stack(slevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); + codegen.opcodes.push_back(GDScriptFunctions::LEN); + codegen.opcodes.push_back(1); // One argument. + codegen.opcodes.push_back(p_value_addr); // Argument is the value we want to test. + codegen.opcodes.push_back(value_length_addr); // Address to result. + + // Test length compatibility. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); + codegen.opcodes.push_back(p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL); + codegen.opcodes.push_back(value_length_addr); + codegen.opcodes.push_back(array_length_addr); + codegen.opcodes.push_back(equality_addr); // Address to result. + + // Jump away if length is not compatible. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(equality_addr); + r_patch_addresses.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + + // Evaluate element by element. + for (int i = 0; i < p_pattern->array.size(); i++) { + if (p_pattern->array[i]->pattern_type == GDScriptParser::PatternNode::PT_REST) { + // Don't want to access an extra element of the user array. + break; + } - if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) - slevel++; //uses stack for return, increase stack + int stlevel = p_stack_level; + Vector<int> element_block_patches; // I want to internal patterns try the next element instead of going to the block. + // Add index to constant map. + int index_addr = codegen.get_constant_pos(i); + + // Get the actual element from the user-sent array. + int element_addr = stlevel++; + codegen.alloc_stack(stlevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET); + codegen.opcodes.push_back(p_value_addr); // Source. + codegen.opcodes.push_back(index_addr); // Index. + codegen.opcodes.push_back(element_addr); // Destination. + + // Also get type of element. + int element_type_addr = stlevel++; + element_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + codegen.alloc_stack(stlevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); + codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); + codegen.opcodes.push_back(1); // One argument. + codegen.opcodes.push_back(element_addr); // Argument is the value we want to test. + codegen.opcodes.push_back(element_type_addr); // Address to result. + + // Try the pattern inside the element. + Error err = _parse_match_pattern(codegen, p_pattern->array[i], stlevel, element_addr, element_type_addr, r_bound_variables, r_patch_addresses, element_block_patches); + if (err != OK) { + return err; + } - const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(on->arguments[1]); + // 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.opcodes.push_back(GDScriptFunction::OPCODE_IS_BUILTIN); // perform operator - codegen.opcodes.push_back(src_address_a); // argument 1 - codegen.opcodes.push_back((int)tn->vtype); // argument 2 (unary only takes one parameter) - } break; - default: { + // Jump to the actual block since it matches. This is needed to take multi-pattern into account. + // Also here for the case of empty arrays. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); + r_block_patch_address.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + } break; + case GDScriptParser::PatternNode::PT_DICTIONARY: { + int slevel = p_stack_level; - ERR_FAIL_V_MSG(0, "Bug in bytecode compiler, unexpected operator #" + itos(on->op) + " in parse tree while parsing expression."); //unreachable code + // Get dictionary type into constant map. + int dict_type_addr = codegen.get_constant_pos(Variant::DICTIONARY); + + // Check type equality. + int equality_addr = slevel++; + equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + codegen.alloc_stack(slevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); + codegen.opcodes.push_back(Variant::OP_EQUAL); + codegen.opcodes.push_back(p_type_addr); + codegen.opcodes.push_back(dict_type_addr); + codegen.opcodes.push_back(equality_addr); // Address to result. + + // Jump away if not the same type. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(equality_addr); + r_patch_addresses.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + + // Store pattern length in constant map. + int dict_length_addr = codegen.get_constant_pos(p_pattern->rest_used ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size()); + + // Get user's dictionary length. + int value_length_addr = slevel++; + codegen.alloc_stack(slevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); + codegen.opcodes.push_back(GDScriptFunctions::LEN); + codegen.opcodes.push_back(1); // One argument. + codegen.opcodes.push_back(p_value_addr); // Argument is the value we want to test. + codegen.opcodes.push_back(value_length_addr); // Address to result. + + // Test length compatibility. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); + codegen.opcodes.push_back(p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL); + codegen.opcodes.push_back(value_length_addr); + codegen.opcodes.push_back(dict_length_addr); + codegen.opcodes.push_back(equality_addr); // Address to result. + + // Jump away if length is not compatible. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(equality_addr); + r_patch_addresses.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + + // Evaluate element by element. + for (int i = 0; i < p_pattern->dictionary.size(); i++) { + const GDScriptParser::PatternNode::Pair &element = p_pattern->dictionary[i]; + if (element.value_pattern && element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) { + // Ignore rest pattern. + continue; + } + int stlevel = p_stack_level; + Vector<int> element_block_patches; // I want to internal patterns try the next element instead of going to the block. - } break; + // Get the pattern key. + int pattern_key_addr = _parse_expression(codegen, element.key, stlevel); + if (pattern_key_addr < 0) { + return ERR_PARSE_ERROR; + } + if ((pattern_key_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { + stlevel++; + codegen.alloc_stack(stlevel); + } + + // Create stack slot for test result. + int test_result = stlevel++; + test_result |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + codegen.alloc_stack(stlevel); + + // Check if pattern key exists in user's dictionary. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_RETURN); + codegen.opcodes.push_back(1); // Argument count. + codegen.opcodes.push_back(p_value_addr); // Base (user dictionary). + codegen.opcodes.push_back(codegen.get_name_map_pos("has")); // Function name. + codegen.opcodes.push_back(pattern_key_addr); // Argument (pattern key). + codegen.opcodes.push_back(test_result); // Return address. + + // Jump away if key doesn't exist. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(test_result); + r_patch_addresses.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. + + if (element.value_pattern != nullptr) { + // Get actual value from user dictionary. + int value_addr = stlevel++; + codegen.alloc_stack(stlevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET); + codegen.opcodes.push_back(p_value_addr); // Source. + codegen.opcodes.push_back(pattern_key_addr); // Index. + codegen.opcodes.push_back(value_addr); // Destination. + + // Also get type of value. + int value_type_addr = stlevel++; + value_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + codegen.alloc_stack(stlevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); + codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); + codegen.opcodes.push_back(1); // One argument. + codegen.opcodes.push_back(value_addr); // Argument is the value we want to test. + codegen.opcodes.push_back(value_type_addr); // Address to result. + + // Try the pattern inside the value. + Error err = _parse_match_pattern(codegen, element.value_pattern, stlevel, value_addr, value_type_addr, r_bound_variables, r_patch_addresses, element_block_patches); + if (err != OK) { + return err; + } + } + + // Patch jumps to block to try the next element. + for (int j = 0; j < element_block_patches.size(); j++) { + codegen.opcodes.write[element_block_patches[j]] = codegen.opcodes.size(); + } } - 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; - } break; - //TYPE_TYPE, - default: { + // 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. - ERR_FAIL_V_MSG(-1, "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); //unreachable code } break; + case GDScriptParser::PatternNode::PT_REST: + // Do nothing. + break; + case GDScriptParser::PatternNode::PT_WILDCARD: + // This matches anything so just do the jump. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); + r_block_patch_address.push_back(codegen.opcodes.size()); + codegen.opcodes.push_back(0); // Will be replaced. } + return OK; } -Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::BlockNode *p_block, int p_stack_level, int p_break_addr, int p_continue_addr) { - +Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level, int p_break_addr, int p_continue_addr) { codegen.push_stack_identifiers(); int new_identifiers = 0; - codegen.current_line = p_block->line; + codegen.current_line = p_block->start_line; for (int i = 0; i < p_block->statements.size(); i++) { - const GDScriptParser::Node *s = p_block->statements[i]; - switch (s->type) { - case GDScriptParser::Node::TYPE_NEWLINE: { #ifdef DEBUG_ENABLED - const GDScriptParser::NewLineNode *nl = static_cast<const GDScriptParser::NewLineNode *>(s); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); - codegen.opcodes.push_back(nl->line); - codegen.current_line = nl->line; + // Add a newline before each statement, since the debugger needs those. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); + codegen.opcodes.push_back(s->start_line); + codegen.current_line = s->start_line; #endif - } break; - case GDScriptParser::Node::TYPE_CONTROL_FLOW: { - // try subblocks - - const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(s); - switch (cf->cf_type) { + switch (s->type) { + case GDScriptParser::Node::MATCH: { + const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s); - case GDScriptParser::ControlFlowNode::CF_MATCH: { - GDScriptParser::MatchNode *match = cf->match; + int slevel = p_stack_level; - GDScriptParser::IdentifierNode *id = memnew(GDScriptParser::IdentifierNode); - id->name = "#match_value"; + // First, let's save the addres of the value match. + int temp_addr = _parse_expression(codegen, match->test, slevel); + if (temp_addr < 0) { + return ERR_PARSE_ERROR; + } + if ((temp_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { + slevel++; + codegen.alloc_stack(slevel); + } - // var #match_value - // copied because there is no _parse_statement :( - codegen.add_stack_identifier(id->name, p_stack_level++); - codegen.alloc_stack(p_stack_level); - new_identifiers++; + // Then, let's save the type of the value in the stack too, so we can reuse for later comparisons. + int type_addr = slevel++; + type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + codegen.alloc_stack(slevel); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); + codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); + codegen.opcodes.push_back(1); // One argument. + codegen.opcodes.push_back(temp_addr); // Argument is the value we want to test. + codegen.opcodes.push_back(type_addr); // Address to result. - GDScriptParser::OperatorNode *op = memnew(GDScriptParser::OperatorNode); - op->op = GDScriptParser::OperatorNode::OP_ASSIGN; - op->arguments.push_back(id); - op->arguments.push_back(match->val_to_match); + Vector<int> patch_match_end; // Will patch the jump to the end of match. - int ret = _parse_expression(codegen, op, p_stack_level); - if (ret < 0) { - memdelete(id); - memdelete(op); - return ERR_PARSE_ERROR; - } + // Now we can actually start testing. + // For each branch. + for (int j = 0; j < match->branches.size(); j++) { + const GDScriptParser::MatchBranchNode *branch = match->branches[j]; - // break address - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(codegen.opcodes.size() + 3); - int break_addr = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(0); // break addr - - for (int j = 0; j < match->compiled_pattern_branches.size(); j++) { - GDScriptParser::MatchNode::CompiledPatternBranch branch = match->compiled_pattern_branches[j]; - - // jump over continue - // jump unconditionally - // continue address - // compile the condition - int ret2 = _parse_expression(codegen, branch.compiled_pattern, p_stack_level); - if (ret2 < 0) { - memdelete(id); - memdelete(op); - return ERR_PARSE_ERROR; - } + int bound_variables = 0; + codegen.push_stack_identifiers(); // Create an extra block around for binds. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF); - codegen.opcodes.push_back(ret2); - codegen.opcodes.push_back(codegen.opcodes.size() + 3); - int continue_addr = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(0); - - Error err = _parse_block(codegen, branch.body, p_stack_level, p_break_addr, continue_addr); - if (err) { - memdelete(id); - memdelete(op); - return ERR_PARSE_ERROR; +#ifdef DEBUG_ENABLED + // Add a newline before each branch, since the debugger needs those. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); + codegen.opcodes.push_back(s->start_line); + codegen.current_line = s->start_line; +#endif + Vector<int> patch_addrs; // Will patch with end of pattern to jump. + Vector<int> block_patch_addrs; // Will patch with start of block to jump. + + // For each pattern in branch. + for (int k = 0; k < branch->patterns.size(); k++) { + if (k > 0) { + // Patch jumps per pattern to allow for multipattern. If a pattern fails it just tries the next. + for (int l = 0; l < patch_addrs.size(); l++) { + codegen.opcodes.write[patch_addrs[l]] = codegen.opcodes.size(); } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(break_addr); - - codegen.opcodes.write[continue_addr + 1] = codegen.opcodes.size(); + patch_addrs.clear(); } + Error err = _parse_match_pattern(codegen, branch->patterns[k], slevel, temp_addr, type_addr, bound_variables, patch_addrs, block_patch_addrs); + if (err != OK) { + return err; + } + } + // Patch jumps to the block. + for (int k = 0; k < block_patch_addrs.size(); k++) { + codegen.opcodes.write[block_patch_addrs[k]] = codegen.opcodes.size(); + } - codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size(); - - memdelete(id); - memdelete(op); - - } break; - - case GDScriptParser::ControlFlowNode::CF_IF: { + // Leave space for bound variables. + slevel += bound_variables; + codegen.alloc_stack(slevel); - int ret2 = _parse_expression(codegen, cf->arguments[0], p_stack_level, false); - if (ret2 < 0) - return ERR_PARSE_ERROR; + // Parse the branch block. + _parse_block(codegen, branch->block, slevel, p_break_addr, p_continue_addr); - 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 + // 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. - Error err = _parse_block(codegen, cf->body, p_stack_level, p_break_addr, p_continue_addr); - if (err) - return err; + // 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(); + } - if (cf->body_else) { + codegen.pop_stack_identifiers(); // Get out of extra block. + } + // Patch the addresses to jump to the end of the match statement. + for (int j = 0; j < patch_match_end.size(); j++) { + codegen.opcodes.write[patch_match_end[j]] = codegen.opcodes.size(); + } + } break; - 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(); + 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; + } - Error err2 = _parse_block(codegen, cf->body_else, p_stack_level, p_break_addr, p_continue_addr); - if (err2) - return err2; + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(ret2); + int else_addr = codegen.opcodes.size(); + codegen.opcodes.push_back(0); //temporary - codegen.opcodes.write[end_addr] = codegen.opcodes.size(); - } else { - //end without else - codegen.opcodes.write[else_addr] = codegen.opcodes.size(); - } + Error err = _parse_block(codegen, if_n->true_block, p_stack_level, p_break_addr, p_continue_addr); + if (err) { + return err; + } - } break; - case GDScriptParser::ControlFlowNode::CF_FOR: { + 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(); - 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); + Error err2 = _parse_block(codegen, if_n->false_block, p_stack_level, p_break_addr, p_continue_addr); + if (err2) { + return err2; + } - codegen.push_stack_identifiers(); - codegen.add_stack_identifier(static_cast<const GDScriptParser::IdentifierNode *>(cf->arguments[0])->name, iter_stack_pos); - - int ret2 = _parse_expression(codegen, cf->arguments[1], 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, cf->body, slevel, break_pos, continue_pos); - if (err) - return err; + codegen.opcodes.write[end_addr] = codegen.opcodes.size(); + } else { + //end without else + codegen.opcodes.write[else_addr] = codegen.opcodes.size(); + } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(continue_pos); - codegen.opcodes.write[break_pos + 1] = codegen.opcodes.size(); - - codegen.pop_stack_identifiers(); - - } break; - case GDScriptParser::ControlFlowNode::CF_WHILE: { - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(codegen.opcodes.size() + 3); - int break_addr = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(0); - int continue_addr = codegen.opcodes.size(); - - int ret2 = _parse_expression(codegen, cf->arguments[0], p_stack_level, false); - if (ret2 < 0) - return ERR_PARSE_ERROR; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(ret2); - codegen.opcodes.push_back(break_addr); - Error err = _parse_block(codegen, cf->body, p_stack_level, break_addr, continue_addr); - if (err) - return err; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(continue_addr); + } 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.opcodes.write[break_addr + 1] = codegen.opcodes.size(); + codegen.push_stack_identifiers(); + codegen.add_stack_identifier(for_n->variable->name, iter_stack_pos); - } break; - case GDScriptParser::ControlFlowNode::CF_BREAK: { + int ret2 = _parse_expression(codegen, for_n->list, slevel, false); + if (ret2 < 0) { + return ERR_COMPILATION_FAILED; + } - if (p_break_addr < 0) { + //assign container + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); + codegen.opcodes.push_back(container_pos); + codegen.opcodes.push_back(ret2); - _set_error("'break'' not within loop", cf); - return ERR_COMPILATION_FAILED; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(p_break_addr); + //begin loop + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE_BEGIN); + codegen.opcodes.push_back(counter_pos); + codegen.opcodes.push_back(container_pos); + codegen.opcodes.push_back(codegen.opcodes.size() + 4); + codegen.opcodes.push_back(iterator_pos); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next + codegen.opcodes.push_back(codegen.opcodes.size() + 8); + //break loop + int break_pos = codegen.opcodes.size(); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next + codegen.opcodes.push_back(0); //skip code for next + //next loop + int continue_pos = codegen.opcodes.size(); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE); + codegen.opcodes.push_back(counter_pos); + codegen.opcodes.push_back(container_pos); + codegen.opcodes.push_back(break_pos); + codegen.opcodes.push_back(iterator_pos); + + Error err = _parse_block(codegen, for_n->loop, slevel, break_pos, continue_pos); + if (err) { + return err; + } - } break; - case GDScriptParser::ControlFlowNode::CF_CONTINUE: { + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); + codegen.opcodes.push_back(continue_pos); + codegen.opcodes.write[break_pos + 1] = codegen.opcodes.size(); - if (p_continue_addr < 0) { + codegen.pop_stack_identifiers(); - _set_error("'continue' not within loop", cf); - return ERR_COMPILATION_FAILED; - } + } break; + case GDScriptParser::Node::WHILE: { + const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); + codegen.opcodes.push_back(codegen.opcodes.size() + 3); + int break_addr = codegen.opcodes.size(); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); + codegen.opcodes.push_back(0); + int continue_addr = codegen.opcodes.size(); + + int ret2 = _parse_expression(codegen, while_n->condition, p_stack_level, false); + if (ret2 < 0) { + return ERR_PARSE_ERROR; + } + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(ret2); + codegen.opcodes.push_back(break_addr); + Error err = _parse_block(codegen, while_n->loop, p_stack_level, break_addr, continue_addr); + if (err) { + return err; + } + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); + codegen.opcodes.push_back(continue_addr); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(p_continue_addr); + codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size(); - } break; - case GDScriptParser::ControlFlowNode::CF_RETURN: { + } 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); - int ret2; + } break; + case GDScriptParser::Node::CONTINUE: { + if (p_continue_addr < 0) { + _set_error("'continue' not within loop", s); + return ERR_COMPILATION_FAILED; + } - if (cf->arguments.size()) { + codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); + codegen.opcodes.push_back(p_continue_addr); - ret2 = _parse_expression(codegen, cf->arguments[0], p_stack_level, false); - if (ret2 < 0) - return ERR_PARSE_ERROR; + } break; + case GDScriptParser::Node::RETURN: { + const GDScriptParser::ReturnNode *return_n = static_cast<const GDScriptParser::ReturnNode *>(s); + int ret2; - } else { + 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; + } - ret2 = GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; - } + } else { + ret2 = GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; + } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_RETURN); - codegen.opcodes.push_back(ret2); + codegen.opcodes.push_back(GDScriptFunction::OPCODE_RETURN); + codegen.opcodes.push_back(ret2); - } break; - } } break; - case GDScriptParser::Node::TYPE_ASSERT: { + 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) + if (ret2 < 0) { return ERR_PARSE_ERROR; + } int message_ret = 0; if (as->message) { message_ret = _parse_expression(codegen, as->message, p_stack_level + 1, false); - if (message_ret < 0) + if (message_ret < 0) { return ERR_PARSE_ERROR; + } } codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSERT); @@ -1534,15 +2054,14 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo codegen.opcodes.push_back(message_ret); #endif } break; - case GDScriptParser::Node::TYPE_BREAKPOINT: { + case GDScriptParser::Node::BREAKPOINT: { #ifdef DEBUG_ENABLED // try subblocks codegen.opcodes.push_back(GDScriptFunction::OPCODE_BREAKPOINT); #endif } break; - case GDScriptParser::Node::TYPE_LOCAL_VAR: { - - const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(s); + case GDScriptParser::Node::VARIABLE: { + const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s); // since we are using properties now for most class access, allow shadowing of class members to make user's life easier. // @@ -1551,25 +2070,54 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo // return ERR_ALREADY_EXISTS; //} - codegen.add_stack_identifier(lv->name, p_stack_level++); + codegen.add_stack_identifier(lv->identifier->name, p_stack_level++); codegen.alloc_stack(p_stack_level); new_identifiers++; + if (lv->initializer != nullptr) { + int dst_address = codegen.stack_identifiers[lv->identifier->name]; + dst_address |= GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS; + + int src_address = _parse_expression(codegen, lv->initializer, p_stack_level); + if (src_address < 0) { + return ERR_PARSE_ERROR; + } + if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(lv->get_datatype()), lv->initializer->get_datatype())) { + return ERR_PARSE_ERROR; + } + } } break; + case GDScriptParser::Node::CONSTANT: { + // Local constants. + const GDScriptParser::ConstantNode *lc = static_cast<const GDScriptParser::ConstantNode *>(s); + if (!lc->initializer->is_constant) { + _set_error("Local constant must have a constant value as initializer.", lc->initializer); + return ERR_PARSE_ERROR; + } + codegen.local_named_constants[lc->identifier->name] = codegen.get_constant_pos(lc->initializer->reduced_value); + } break; + case GDScriptParser::Node::PASS: + // Nothing to do. + break; default: { //expression - int ret2 = _parse_expression(codegen, s, p_stack_level, true); - if (ret2 < 0) - return ERR_PARSE_ERROR; + if (s->is_expression()) { + int ret2 = _parse_expression(codegen, static_cast<const GDScriptParser::ExpressionNode *>(s), p_stack_level, true); + if (ret2 < 0) { + return ERR_PARSE_ERROR; + } + } else { + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Bug in bytecode compiler, unexpected node in parse tree while parsing statement."); //unreachable code + } } break; } } + codegen.pop_stack_identifiers(); return OK; } Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready) { - Vector<int> bytecode; CodeGen codegen; @@ -1583,9 +2131,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser Vector<StringName> argnames; int stack_level = 0; + int optional_parameters = 0; if (p_func) { - for (int i = 0; i < p_func->arguments.size(); i++) { + for (int i = 0; i < p_func->parameters.size(); i++) { // since we are using properties now for most class access, allow shadowing of class members to make user's life easier. // //if (_is_class_member_property(p_script, p_func->arguments[i])) { @@ -1593,42 +2142,82 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser // return ERR_ALREADY_EXISTS; //} - codegen.add_stack_identifier(p_func->arguments[i], i); + codegen.add_stack_identifier(p_func->parameters[i]->identifier->name, i); #ifdef TOOLS_ENABLED - argnames.push_back(p_func->arguments[i]); + argnames.push_back(p_func->parameters[i]->identifier->name); #endif + if (p_func->parameters[i]->default_value != nullptr) { + optional_parameters++; + } } - stack_level = p_func->arguments.size(); + stack_level = p_func->parameters.size(); } codegen.alloc_stack(stack_level); /* Parse initializer -if applies- */ - bool is_initializer = !p_for_ready && !p_func; + bool is_implicit_initializer = !p_for_ready && !p_func; + bool is_initializer = p_func && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init; - if (is_initializer || (p_func && String(p_func->name) == "_init")) { - //parse initializer for class members - if (!p_func && p_class->extends_used && p_script->native.is_null()) { + if (is_implicit_initializer) { + // Initialize class fields. + for (int i = 0; i < p_class->members.size(); i++) { + if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { + continue; + } + const GDScriptParser::VariableNode *field = p_class->members[i].variable; + if (field->onready) { + // Only initialize in _ready. + continue; + } - //call implicit parent constructor - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_SELF_BASE); - codegen.opcodes.push_back(codegen.get_name_map_pos("_init")); - codegen.opcodes.push_back(0); - codegen.opcodes.push_back((GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) | 0); + 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; + } + } } - Error err = _parse_block(codegen, p_class->initializer, stack_level); - if (err) - return err; - is_initializer = true; } - if (p_for_ready || (p_func && String(p_func->name) == "_ready")) { - //parse initializer for class members - if (p_class->ready->statements.size()) { - Error err = _parse_block(codegen, p_class->ready, stack_level); - if (err) - return err; + if (p_for_ready || (p_func && String(p_func->identifier->name) == "_ready")) { + // Initialize class fields on ready. + for (int i = 0; i < p_class->members.size(); i++) { + if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { + continue; + } + const GDScriptParser::VariableNode *field = p_class->members[i].variable; + if (!field->onready) { + continue; + } + + if (field->initializer) { + // Emit proper line change. + codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); + codegen.opcodes.push_back(field->initializer->start_line); + + int src_address = _parse_expression(codegen, field->initializer, stack_level, false, true); + if (src_address < 0) { + return ERR_PARSE_ERROR; + } + int dst_address = codegen.script->member_indices[field->identifier->name].index; + dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; + + if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) { + return ERR_PARSE_ERROR; + } + } } } @@ -1638,32 +2227,39 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser StringName func_name; if (p_func) { - - if (p_func->default_values.size()) { - + if (optional_parameters > 0) { codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT); defarg_addr.push_back(codegen.opcodes.size()); - for (int i = 0; i < p_func->default_values.size(); i++) { - - _parse_expression(codegen, p_func->default_values[i], stack_level, true); + for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) { + int src_addr = _parse_expression(codegen, p_func->parameters[i]->default_value, stack_level, true); + if (src_addr < 0) { + return ERR_PARSE_ERROR; + } + int dst_addr = codegen.stack_identifiers[p_func->parameters[i]->identifier->name] | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); + if (!_generate_typed_assign(codegen, src_addr, dst_addr, _gdtype_from_datatype(p_func->parameters[i]->get_datatype()), p_func->parameters[i]->default_value->get_datatype())) { + return ERR_PARSE_ERROR; + } defarg_addr.push_back(codegen.opcodes.size()); } - defarg_addr.invert(); } + func_name = p_func->identifier->name; + codegen.function_name = func_name; Error err = _parse_block(codegen, p_func->body, stack_level); - if (err) + if (err) { return err; + } - func_name = p_func->name; } else { - if (p_for_ready) + if (p_for_ready) { func_name = "_ready"; - else - func_name = "_init"; + } else { + func_name = "@implicit_new"; + } } + codegen.function_name = func_name; codegen.opcodes.push_back(GDScriptFunction::OPCODE_END); /* @@ -1676,13 +2272,13 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser //} if (p_func) { - gdfunc->_static = p_func->_static; + gdfunc->_static = p_func->is_static; gdfunc->rpc_mode = p_func->rpc_mode; - gdfunc->argument_types.resize(p_func->argument_types.size()); - for (int i = 0; i < p_func->argument_types.size(); i++) { - gdfunc->argument_types.write[i] = _gdtype_from_datatype(p_func->argument_types[i]); + gdfunc->argument_types.resize(p_func->parameters.size()); + for (int i = 0; i < p_func->parameters.size(); i++) { + gdfunc->argument_types.write[i] = _gdtype_from_datatype(p_func->parameters[i]->get_datatype()); } - gdfunc->return_type = _gdtype_from_datatype(p_func->return_type); + gdfunc->return_type = _gdtype_from_datatype(p_func->get_datatype()); } else { gdfunc->_static = false; gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; @@ -1706,17 +2302,14 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser 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(); @@ -1739,19 +2332,16 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser #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]; @@ -1760,7 +2350,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser gdfunc->_default_arg_ptr = nullptr; } - gdfunc->_argument_count = p_func ? p_func->arguments.size() : 0; + gdfunc->_argument_count = p_func ? p_func->parameters.size() : 0; gdfunc->_stack_size = codegen.stack_max; gdfunc->_call_size = codegen.call_max; gdfunc->name = func_name; @@ -1768,19 +2358,20 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser if (EngineDebugger::is_active()) { String signature; //path - if (p_script->get_path() != String()) + if (p_script->get_path() != String()) { signature += p_script->get_path(); + } //loc if (p_func) { - signature += "::" + itos(p_func->body->line); + signature += "::" + itos(p_func->body->start_line); } else { signature += "::0"; } //function and class - if (p_class->name) { - signature += "::" + String(p_class->name) + "." + String(func_name); + if (p_class->identifier) { + signature += "::" + String(p_class->identifier->name) + "." + String(func_name); } else { signature += "::" + String(func_name); } @@ -1800,36 +2391,184 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser #endif if (p_func) { - gdfunc->_initial_line = p_func->line; + gdfunc->_initial_line = p_func->start_line; #ifdef TOOLS_ENABLED - p_script->member_lines[func_name] = p_func->line; + p_script->member_lines[func_name] = p_func->start_line; #endif } else { gdfunc->_initial_line = 0; } - if (codegen.debug_stack) + if (codegen.debug_stack) { gdfunc->stack_debug = codegen.stack_debug; + } - if (is_initializer) + if (is_initializer) { p_script->initializer = gdfunc; + } + if (is_implicit_initializer) { + p_script->implicit_initializer = gdfunc; + } return OK; } -Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { +Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) { + Vector<int> bytecode; + CodeGen codegen; + codegen.class_node = p_class; + codegen.script = p_script; + codegen.function_node = nullptr; + codegen.stack_max = 0; + codegen.current_line = 0; + codegen.call_max = 0; + codegen.debug_stack = EngineDebugger::is_active(); + Vector<StringName> argnames; + + int stack_level = 0; + + if (p_is_setter) { + codegen.add_stack_identifier(p_variable->setter_parameter->name, stack_level++); + argnames.push_back(p_variable->setter_parameter->name); + } + + codegen.alloc_stack(stack_level); + + StringName func_name; + + if (p_is_setter) { + func_name = "@" + p_variable->identifier->name + "_setter"; + } else { + func_name = "@" + p_variable->identifier->name + "_getter"; + } + codegen.function_name = func_name; + + Error err = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter, stack_level); + if (err != OK) { + return err; + } + + codegen.opcodes.push_back(GDScriptFunction::OPCODE_END); + + p_script->member_functions[func_name] = memnew(GDScriptFunction); + GDScriptFunction *gdfunc = p_script->member_functions[func_name]; + + gdfunc->_static = false; + gdfunc->rpc_mode = p_variable->rpc_mode; + gdfunc->argument_types.resize(p_is_setter ? 1 : 0); + gdfunc->return_type = _gdtype_from_datatype(p_variable->get_datatype()); +#ifdef TOOLS_ENABLED + gdfunc->arg_names = argnames; +#endif + + // TODO: Unify this with function compiler. + //constants + if (codegen.constant_map.size()) { + gdfunc->_constant_count = codegen.constant_map.size(); + gdfunc->constants.resize(codegen.constant_map.size()); + gdfunc->_constants_ptr = gdfunc->constants.ptrw(); + const Variant *K = nullptr; + while ((K = codegen.constant_map.next(K))) { + int idx = codegen.constant_map[*K]; + gdfunc->constants.write[idx] = *K; + } + } else { + gdfunc->_constants_ptr = nullptr; + gdfunc->_constant_count = 0; + } + //global names + if (codegen.name_map.size()) { + gdfunc->global_names.resize(codegen.name_map.size()); + gdfunc->_global_names_ptr = &gdfunc->global_names[0]; + for (Map<StringName, int>::Element *E = codegen.name_map.front(); E; E = E->next()) { + gdfunc->global_names.write[E->get()] = E->key(); + } + gdfunc->_global_names_count = gdfunc->global_names.size(); + + } else { + gdfunc->_global_names_ptr = nullptr; + gdfunc->_global_names_count = 0; + } + +#ifdef TOOLS_ENABLED + // Named globals + if (codegen.named_globals.size()) { + gdfunc->named_globals.resize(codegen.named_globals.size()); + gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr(); + for (int i = 0; i < codegen.named_globals.size(); i++) { + gdfunc->named_globals.write[i] = codegen.named_globals[i]; + } + gdfunc->_named_globals_count = gdfunc->named_globals.size(); + } +#endif + + gdfunc->code = codegen.opcodes; + gdfunc->_code_ptr = &gdfunc->code[0]; + gdfunc->_code_size = codegen.opcodes.size(); + gdfunc->_default_arg_count = 0; + gdfunc->_default_arg_ptr = nullptr; + gdfunc->_argument_count = argnames.size(); + gdfunc->_stack_size = codegen.stack_max; + gdfunc->_call_size = codegen.call_max; + gdfunc->name = func_name; +#ifdef DEBUG_ENABLED + if (EngineDebugger::is_active()) { + String signature; + //path + if (p_script->get_path() != String()) { + signature += p_script->get_path(); + } + //loc + signature += "::" + itos(p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line); + + //function and class + + if (p_class->identifier) { + signature += "::" + String(p_class->identifier->name) + "." + String(func_name); + } else { + signature += "::" + String(func_name); + } + + gdfunc->profile.signature = signature; + } +#endif + gdfunc->_script = p_script; + gdfunc->source = source; + +#ifdef DEBUG_ENABLED + + { + gdfunc->func_cname = (String(source) + " - " + String(func_name)).utf8(); + gdfunc->_func_cname = gdfunc->func_cname.get_data(); + } + +#endif + gdfunc->_initial_line = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line; +#ifdef TOOLS_ENABLED + + p_script->member_lines[func_name] = gdfunc->_initial_line; +#endif + + if (codegen.debug_stack) { + gdfunc->stack_debug = codegen.stack_debug; + } + + return OK; +} + +Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { parsing_classes.insert(p_script); - if (p_class->owner && p_class->owner->owner) { + if (p_class->outer && p_class->outer->outer) { // Owner is not root if (!parsed_classes.has(p_script->_owner)) { if (parsing_classes.has(p_script->_owner)) { - _set_error("Cyclic class reference for '" + String(p_class->name) + "'.", p_class); + _set_error("Cyclic class reference for '" + String(p_class->identifier->name) + "'.", p_class); return ERR_PARSE_ERROR; } - Error err = _parse_class_level(p_script->_owner, p_class->owner, p_keep_state); + Error err = _parse_class_level(p_script->_owner, p_class->outer, p_keep_state); if (err) { return err; } @@ -1850,8 +2589,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->_signals.clear(); p_script->initializer = nullptr; - p_script->tool = p_class->tool; - p_script->name = p_class->name; + p_script->tool = parser->is_tool(); + p_script->name = p_class->identifier ? p_class->identifier->name : ""; Ref<GDScriptNativeClass> native; @@ -1868,12 +2607,12 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar Ref<GDScript> base = base_type.script_type; p_script->base = base; p_script->_base = base.ptr(); - p_script->member_indices = base->member_indices; - if (p_class->base_type.kind == GDScriptParser::DataType::CLASS) { + if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) { if (!parsed_classes.has(p_script->_base)) { if (parsing_classes.has(p_script->_base)) { - _set_error("Cyclic class reference for '" + String(p_class->name) + "'.", p_class); + String class_name = p_class->identifier ? p_class->identifier->name : "<main>"; + _set_error("Cyclic class reference for '" + class_name + "'.", p_class); return ERR_PARSE_ERROR; } Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); @@ -1882,6 +2621,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } } } + + p_script->member_indices = base->member_indices; } break; default: { _set_error("Parser bug: invalid inheritance.", p_class); @@ -1889,91 +2630,146 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } break; } - for (int i = 0; i < p_class->variables.size(); i++) { - - StringName name = p_class->variables[i].identifier; - - GDScript::MemberInfo minfo; - minfo.index = p_script->member_indices.size(); - minfo.setter = p_class->variables[i].setter; - minfo.getter = p_class->variables[i].getter; - minfo.rpc_mode = p_class->variables[i].rpc_mode; - minfo.data_type = _gdtype_from_datatype(p_class->variables[i].data_type); - - PropertyInfo prop_info = minfo.data_type; - prop_info.name = name; - PropertyInfo export_info = p_class->variables[i]._export; + for (int i = 0; i < p_class->members.size(); i++) { + const GDScriptParser::ClassNode::Member &member = p_class->members[i]; + switch (member.type) { + case GDScriptParser::ClassNode::Member::VARIABLE: { + const GDScriptParser::VariableNode *variable = member.variable; + StringName name = variable->identifier->name; + + GDScript::MemberInfo minfo; + minfo.index = p_script->member_indices.size(); + switch (variable->property) { + case GDScriptParser::VariableNode::PROP_NONE: + break; // Nothing to do. + case GDScriptParser::VariableNode::PROP_SETGET: + if (variable->setter_pointer != nullptr) { + minfo.setter = variable->setter_pointer->name; + } + if (variable->getter_pointer != nullptr) { + minfo.getter = variable->getter_pointer->name; + } + break; + case GDScriptParser::VariableNode::PROP_INLINE: + if (variable->setter != nullptr) { + minfo.setter = "@" + variable->identifier->name + "_setter"; + } + if (variable->getter != nullptr) { + minfo.getter = "@" + variable->identifier->name + "_getter"; + } + break; + } + minfo.rpc_mode = variable->rpc_mode; + minfo.data_type = _gdtype_from_datatype(variable->get_datatype()); - if (export_info.type != Variant::NIL) { + PropertyInfo prop_info = minfo.data_type; + prop_info.name = name; + PropertyInfo export_info = variable->export_info; - if (!minfo.data_type.has_type) { - prop_info.type = export_info.type; - prop_info.class_name = export_info.class_name; - } - prop_info.hint = export_info.hint; - prop_info.hint_string = export_info.hint_string; - prop_info.usage = export_info.usage; + if (variable->exported) { + if (!minfo.data_type.has_type) { + prop_info.type = export_info.type; + prop_info.class_name = export_info.class_name; + } + prop_info.hint = export_info.hint; + prop_info.hint_string = export_info.hint_string; + prop_info.usage = export_info.usage; #ifdef TOOLS_ENABLED - if (p_class->variables[i].default_value.get_type() != Variant::NIL) { - p_script->member_default_values[name] = p_class->variables[i].default_value; - } + if (variable->initializer != nullptr && variable->initializer->type == GDScriptParser::Node::LITERAL) { + p_script->member_default_values[name] = static_cast<const GDScriptParser::LiteralNode *>(variable->initializer)->value; + } #endif - } else { - prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; - } + } else { + prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; + } - p_script->member_info[name] = prop_info; - p_script->member_indices[name] = minfo; - p_script->members.insert(name); + p_script->member_info[name] = prop_info; + p_script->member_indices[name] = minfo; + p_script->members.insert(name); #ifdef TOOLS_ENABLED - p_script->member_lines[name] = p_class->variables[i].line; + p_script->member_lines[name] = variable->start_line; #endif - } - - for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { + } break; - StringName name = E->key(); + case GDScriptParser::ClassNode::Member::CONSTANT: { + const GDScriptParser::ConstantNode *constant = member.constant; + StringName name = constant->identifier->name; - ERR_CONTINUE(E->get().expression->type != GDScriptParser::Node::TYPE_CONSTANT); + ERR_CONTINUE(constant->initializer->type != GDScriptParser::Node::LITERAL); - GDScriptParser::ConstantNode *constant = static_cast<GDScriptParser::ConstantNode *>(E->get().expression); + const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(constant->initializer); - p_script->constants.insert(name, constant->value); + p_script->constants.insert(name, literal->value); #ifdef TOOLS_ENABLED - p_script->member_lines[name] = E->get().expression->line; + p_script->member_lines[name] = constant->start_line; #endif - } + } break; - for (int i = 0; i < p_class->_signals.size(); i++) { + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + const GDScriptParser::EnumNode::Value &enum_value = member.enum_value; + StringName name = enum_value.identifier->name; - StringName name = p_class->_signals[i].name; + p_script->constants.insert(name, enum_value.value); +#ifdef TOOLS_ENABLED + p_script->member_lines[name] = enum_value.identifier->start_line; +#endif + } break; - GDScript *c = p_script; + case GDScriptParser::ClassNode::Member::SIGNAL: { + const GDScriptParser::SignalNode *signal = member.signal; + StringName name = signal->identifier->name; - while (c) { + GDScript *c = p_script; - if (c->_signals.has(name)) { - _set_error("Signal '" + name + "' redefined (in current or parent class)", p_class); - return ERR_ALREADY_EXISTS; - } + while (c) { + if (c->_signals.has(name)) { + _set_error("Signal '" + name + "' redefined (in current or parent class)", p_class); + return ERR_ALREADY_EXISTS; + } - if (c->base.is_valid()) { - c = c->base.ptr(); - } else { - c = nullptr; - } - } + if (c->base.is_valid()) { + c = c->base.ptr(); + } else { + c = nullptr; + } + } - if (native.is_valid()) { - if (ClassDB::has_signal(native->get_name(), name)) { - _set_error("Signal '" + name + "' redefined (original in native class '" + String(native->get_name()) + "')", p_class); - return ERR_ALREADY_EXISTS; - } - } + if (native.is_valid()) { + if (ClassDB::has_signal(native->get_name(), name)) { + _set_error("Signal '" + name + "' redefined (original in native class '" + String(native->get_name()) + "')", p_class); + return ERR_ALREADY_EXISTS; + } + } + + Vector<StringName> parameters_names; + parameters_names.resize(signal->parameters.size()); + for (int j = 0; j < signal->parameters.size(); j++) { + parameters_names.write[j] = signal->parameters[j]->identifier->name; + } + p_script->_signals[name] = parameters_names; + } break; - p_script->_signals[name] = p_class->_signals[i].arguments; + case GDScriptParser::ClassNode::Member::ENUM: { + const GDScriptParser::EnumNode *enum_n = member.m_enum; + + // TODO: Make enums not be just a dictionary? + Dictionary new_enum; + for (int j = 0; j < enum_n->values.size(); j++) { + int value = enum_n->values[j].value; + // Needs to be string because Variant::get will convert to String. + new_enum[String(enum_n->values[j].identifier->name)] = value; + } + + p_script->constants.insert(enum_n->identifier->name, new_enum); +#ifdef TOOLS_ENABLED + p_script->member_lines[enum_n->identifier->name] = enum_n->start_line; +#endif + } break; + default: + break; // Nothing to do here. + } } parsed_classes.insert(p_script); @@ -1981,21 +2777,27 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar //parse sub-classes - for (int i = 0; i < p_class->subclasses.size(); i++) { - StringName name = p_class->subclasses[i]->name; + for (int i = 0; i < p_class->members.size(); i++) { + const GDScriptParser::ClassNode::Member &member = p_class->members[i]; + if (member.type != member.CLASS) { + continue; + } + const GDScriptParser::ClassNode *inner_class = member.m_class; + StringName name = inner_class->identifier->name; Ref<GDScript> &subclass = p_script->subclasses[name]; GDScript *subclass_ptr = subclass.ptr(); // Subclass might still be parsing, just skip it if (!parsed_classes.has(subclass_ptr) && !parsing_classes.has(subclass_ptr)) { - Error err = _parse_class_level(subclass_ptr, p_class->subclasses[i], p_keep_state); - if (err) + Error err = _parse_class_level(subclass_ptr, inner_class, p_keep_state); + if (err) { return err; + } } #ifdef TOOLS_ENABLED - p_script->member_lines[name] = p_class->subclasses[i]->line; + p_script->member_lines[name] = inner_class->start_line; #endif p_script->constants.insert(name, subclass); //once parsed, goes to the list of constants @@ -2007,41 +2809,52 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { //parse methods - bool has_initializer = false; bool has_ready = false; - for (int i = 0; i < p_class->functions.size(); i++) { - - if (!has_initializer && p_class->functions[i]->name == "_init") - has_initializer = true; - if (!has_ready && p_class->functions[i]->name == "_ready") - has_ready = true; - Error err = _parse_function(p_script, p_class, p_class->functions[i]); - if (err) - return err; - } - - //parse static methods - - for (int i = 0; i < p_class->static_functions.size(); i++) { - - Error err = _parse_function(p_script, p_class, p_class->static_functions[i]); - if (err) - return err; + for (int i = 0; i < p_class->members.size(); i++) { + const GDScriptParser::ClassNode::Member &member = p_class->members[i]; + if (member.type == member.FUNCTION) { + const GDScriptParser::FunctionNode *function = member.function; + if (!has_ready && function->identifier->name == "_ready") { + has_ready = true; + } + Error err = _parse_function(p_script, p_class, function); + if (err) { + return err; + } + } else if (member.type == member.VARIABLE) { + const GDScriptParser::VariableNode *variable = member.variable; + if (variable->property == GDScriptParser::VariableNode::PROP_INLINE) { + if (variable->setter != nullptr) { + Error err = _parse_setter_getter(p_script, p_class, variable, true); + if (err) { + return err; + } + } + if (variable->getter != nullptr) { + Error err = _parse_setter_getter(p_script, p_class, variable, false); + if (err) { + return err; + } + } + } + } } - if (!has_initializer) { - //create a constructor + { + // Create an implicit constructor in any case. Error err = _parse_function(p_script, p_class, nullptr); - if (err) + if (err) { return err; + } } - if (!has_ready && p_class->ready->statements.size()) { - //create a constructor + if (!has_ready && p_class->onready_used) { + //create a _ready constructor Error err = _parse_function(p_script, p_class, nullptr, true); - if (err) + if (err) { return err; + } } #ifdef DEBUG_ENABLED @@ -2050,7 +2863,6 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa if (p_keep_state) { for (Set<Object *>::Element *E = p_script->instances.front(); E;) { - Set<Object *>::Element *N = E->next(); ScriptInstance *si = E->get()->get_script_instance(); @@ -2085,7 +2897,6 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa } #endif } else { - GDScriptInstance *gi = static_cast<GDScriptInstance *>(si); gi->reload_members(); } @@ -2095,11 +2906,15 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa } #endif - for (int i = 0; i < p_class->subclasses.size(); i++) { - StringName name = p_class->subclasses[i]->name; + for (int i = 0; i < p_class->members.size(); i++) { + if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { + continue; + } + const GDScriptParser::ClassNode *inner_class = p_class->members[i].m_class; + StringName name = inner_class->identifier->name; GDScript *subclass = p_script->subclasses[name].ptr(); - Error err = _parse_class_blocks(subclass, p_class->subclasses[i], p_keep_state); + Error err = _parse_class_blocks(subclass, inner_class, p_keep_state); if (err) { return err; } @@ -2110,7 +2925,6 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa } void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - Map<StringName, Ref<GDScript>> old_subclasses; if (p_keep_state) { @@ -2119,8 +2933,12 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C p_script->subclasses.clear(); - for (int i = 0; i < p_class->subclasses.size(); i++) { - StringName name = p_class->subclasses[i]->name; + for (int i = 0; i < p_class->members.size(); i++) { + if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { + continue; + } + const GDScriptParser::ClassNode *inner_class = p_class->members[i].m_class; + StringName name = inner_class->identifier->name; Ref<GDScript> subclass; String fully_qualified_name = p_script->fully_qualified_name + "::" + name; @@ -2140,19 +2958,17 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C subclass->fully_qualified_name = fully_qualified_name; p_script->subclasses.insert(name, subclass); - _make_scripts(subclass.ptr(), p_class->subclasses[i], false); + _make_scripts(subclass.ptr(), inner_class, false); } } Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state) { - err_line = -1; err_column = -1; error = ""; parser = p_parser; main_script = p_script; - const GDScriptParser::Node *root = parser->get_parse_tree(); - ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, ERR_INVALID_DATA); + const GDScriptParser::ClassNode *root = parser->get_tree(); source = p_script->get_path(); @@ -2160,32 +2976,33 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri p_script->fully_qualified_name = p_script->path; // Create scripts for subclasses beforehand so they can be referenced - _make_scripts(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); + _make_scripts(p_script, root, p_keep_state); p_script->_owner = nullptr; - Error err = _parse_class_level(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); + Error err = _parse_class_level(p_script, root, p_keep_state); - if (err) + if (err) { return err; + } - err = _parse_class_blocks(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); + err = _parse_class_blocks(p_script, root, p_keep_state); - if (err) + if (err) { return err; + } - return OK; + return GDScriptCache::finish_compiling(p_script->get_path()); } String GDScriptCompiler::get_error() const { - return error; } -int GDScriptCompiler::get_error_line() const { +int GDScriptCompiler::get_error_line() const { return err_line; } -int GDScriptCompiler::get_error_column() const { +int GDScriptCompiler::get_error_column() const { return err_column; } diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 34b066b5c7..e8601f69c7 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -33,19 +33,19 @@ #include "core/set.h" #include "gdscript.h" +#include "gdscript_function.h" #include "gdscript_parser.h" class GDScriptCompiler { - const GDScriptParser *parser; Set<GDScript *> parsed_classes; Set<GDScript *> parsing_classes; GDScript *main_script; struct CodeGen { - GDScript *script; const GDScriptParser::ClassNode *class_node; const GDScriptParser::FunctionNode *function_node; + StringName function_name; bool debug_stack; List<Map<StringName, int>> stack_id_stack; @@ -54,6 +54,7 @@ class GDScriptCompiler { List<GDScriptFunction::StackDebug> stack_debug; List<Map<StringName, int>> block_identifier_stack; Map<StringName, int> block_identifiers; + Map<StringName, int> local_named_constants; void add_stack_identifier(const StringName &p_id, int p_stackpos) { stack_identifiers[p_id] = p_stackpos; @@ -71,7 +72,6 @@ class GDScriptCompiler { void push_stack_identifiers() { stack_id_stack.push_back(stack_identifiers); if (debug_stack) { - block_identifier_stack.push_back(block_identifiers); block_identifiers.clear(); } @@ -83,7 +83,6 @@ class GDScriptCompiler { 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(); @@ -114,19 +113,24 @@ class GDScriptCompiler { } int get_constant_pos(const Variant &p_constant) { - if (constant_map.has(p_constant)) - return constant_map[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; + return pos | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); } Vector<int> opcodes; void alloc_stack(int p_level) { - if (p_level >= stack_max) stack_max = p_level + 1; + 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; + if (p_params >= call_max) { + call_max = p_params; + } } int current_line; @@ -139,15 +143,19 @@ class GDScriptCompiler { void _set_error(const String &p_error, const GDScriptParser::Node *p_node); - bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level); - bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0); + bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level); + bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0); + bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0); + bool _generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type); GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const; - int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level, int p_index_addr = 0); - int _parse_expression(CodeGen &codegen, const GDScriptParser::Node *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false, int p_index_addr = 0); - Error _parse_block(CodeGen &codegen, const GDScriptParser::BlockNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1); + int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::AssignmentNode *p_assignment, int p_stack_level, int p_index_addr = 0); + int _parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false, int p_index_addr = 0); + Error _parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address); + Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1); Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false); + Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); void _make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); @@ -155,6 +163,7 @@ class GDScriptCompiler { int err_column; StringName source; String error; + bool within_await = false; public: Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 1e3cbd661e..88bf8cde0f 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -33,27 +33,28 @@ #include "core/engine.h" #include "core/global_constants.h" #include "core/os/file_access.h" +#include "gdscript_analyzer.h" #include "gdscript_compiler.h" +#include "gdscript_parser.h" +#include "gdscript_tokenizer.h" #ifdef TOOLS_ENABLED +#include "core/project_settings.h" #include "editor/editor_file_system.h" #include "editor/editor_settings.h" #endif void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const { - p_delimiters->push_back("#"); } void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { - p_delimiters->push_back("\" \""); p_delimiters->push_back("' '"); p_delimiters->push_back("\"\"\" \"\"\""); } String GDScriptLanguage::_get_processed_template(const String &p_template, const String &p_base_class_name) const { - String processed_template = p_template; #ifdef TOOLS_ENABLED @@ -109,27 +110,43 @@ Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const Str } bool GDScriptLanguage::is_using_templates() { - return true; } void GDScriptLanguage::make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) { - String _template = _get_processed_template(p_script->get_source_code(), p_base_class_name); p_script->set_source_code(_template); } -bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const { +static void get_function_names_recursively(const GDScriptParser::ClassNode *p_class, const String &p_prefix, Map<int, String> &r_funcs) { + for (int i = 0; i < p_class->members.size(); i++) { + if (p_class->members[i].type == GDScriptParser::ClassNode::Member::FUNCTION) { + const GDScriptParser::FunctionNode *function = p_class->members[i].function; + r_funcs[function->start_line] = p_prefix.empty() ? String(function->identifier->name) : p_prefix + "." + String(function->identifier->name); + } else if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { + String new_prefix = p_class->members[i].m_class->identifier->name; + get_function_names_recursively(p_class->members[i].m_class, p_prefix.empty() ? new_prefix : p_prefix + "." + new_prefix, r_funcs); + } + } +} +bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const { GDScriptParser parser; + GDScriptAnalyzer analyzer(&parser); - Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path, false, r_safe_lines); + Error err = parser.parse(p_script, p_path, false); + if (err == OK) { + err = analyzer.analyze(); + } #ifdef DEBUG_ENABLED if (r_warnings) { for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) { const GDScriptWarning &warn = E->get(); ScriptLanguage::Warning w; - w.line = warn.line; + w.start_line = warn.start_line; + w.end_line = warn.end_line; + w.leftmost_column = warn.leftmost_column; + w.rightmost_column = warn.rightmost_column; w.code = (int)warn.code; w.string_code = GDScriptWarning::get_name_from_code(warn.code); w.message = warn.get_message(); @@ -138,81 +155,70 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & } #endif if (err) { - r_line_error = parser.get_error_line(); - r_col_error = parser.get_error_column(); - r_test_error = parser.get_error(); + GDScriptParser::ParserError parse_error = parser.get_errors().front()->get(); + r_line_error = parse_error.line; + r_col_error = parse_error.column; + r_test_error = parse_error.message; return false; } else { - - const GDScriptParser::Node *root = parser.get_parse_tree(); - ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, false); - - const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root); + const GDScriptParser::ClassNode *cl = parser.get_tree(); Map<int, String> funcs; - for (int i = 0; i < cl->functions.size(); i++) { - - funcs[cl->functions[i]->line] = cl->functions[i]->name; - } - for (int i = 0; i < cl->static_functions.size(); i++) { + get_function_names_recursively(cl, "", funcs); - funcs[cl->static_functions[i]->line] = cl->static_functions[i]->name; + for (Map<int, String>::Element *E = funcs.front(); E; E = E->next()) { + r_functions->push_back(E->get() + ":" + itos(E->key())); } + } - for (int i = 0; i < cl->subclasses.size(); i++) { - for (int j = 0; j < cl->subclasses[i]->functions.size(); j++) { - - funcs[cl->subclasses[i]->functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->functions[j]->name; - } - for (int j = 0; j < cl->subclasses[i]->static_functions.size(); j++) { - - funcs[cl->subclasses[i]->static_functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->static_functions[j]->name; +#ifdef DEBUG_ENABLED + if (r_safe_lines) { + const Set<int> &unsafe_lines = parser.get_unsafe_lines(); + for (int i = 1; i <= parser.get_last_line_number(); i++) { + if (!unsafe_lines.has(i)) { + r_safe_lines->insert(i); } } - - for (Map<int, String>::Element *E = funcs.front(); E; E = E->next()) { - - r_functions->push_back(E->get() + ":" + itos(E->key())); - } } +#endif return true; } bool GDScriptLanguage::has_named_classes() const { - return false; } bool GDScriptLanguage::supports_builtin_mode() const { - return true; } int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const { - - GDScriptTokenizerText tokenizer; - tokenizer.set_code(p_code); + GDScriptTokenizer tokenizer; + tokenizer.set_source_code(p_code); int indent = 0; - while (tokenizer.get_token() != GDScriptTokenizer::TK_EOF && tokenizer.get_token() != GDScriptTokenizer::TK_ERROR) { - - if (tokenizer.get_token() == GDScriptTokenizer::TK_NEWLINE) { - indent = tokenizer.get_token_line_indent(); + GDScriptTokenizer::Token current = tokenizer.scan(); + while (current.type != GDScriptTokenizer::Token::TK_EOF && current.type != GDScriptTokenizer::Token::ERROR) { + if (current.type == GDScriptTokenizer::Token::INDENT) { + indent++; + } else if (current.type == GDScriptTokenizer::Token::DEDENT) { + indent--; } - if (indent == 0 && tokenizer.get_token() == GDScriptTokenizer::TK_PR_FUNCTION && tokenizer.get_token(1) == GDScriptTokenizer::TK_IDENTIFIER) { - - String identifier = tokenizer.get_token_identifier(1); - if (identifier == p_function) { - return tokenizer.get_token_line(); + if (indent == 0 && current.type == GDScriptTokenizer::Token::FUNC) { + current = tokenizer.scan(); + if (current.is_identifier()) { + String identifier = current.get_identifier(); + if (identifier == p_function) { + return current.start_line; + } } } - tokenizer.advance(); + current = tokenizer.scan(); } return -1; } Script *GDScriptLanguage::create_script() const { - return memnew(GDScript); } @@ -222,7 +228,6 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const //break because of parse error if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { - _debug_parse_err_line = p_line; _debug_parse_err_file = p_file; _debug_error = p_error; @@ -234,9 +239,7 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const } bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) { - if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { - _debug_parse_err_line = -1; _debug_parse_err_file = ""; _debug_error = p_error; @@ -249,21 +252,21 @@ bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) } String GDScriptLanguage::debug_get_error() const { - return _debug_error; } int GDScriptLanguage::debug_get_stack_level_count() const { - - if (_debug_parse_err_line >= 0) + if (_debug_parse_err_line >= 0) { return 1; + } return _debug_call_stack_pos; } -int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { - if (_debug_parse_err_line >= 0) +int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { + if (_debug_parse_err_line >= 0) { return _debug_parse_err_line; + } ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, -1); @@ -271,28 +274,31 @@ int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { return *(_call_stack[l].line); } -String GDScriptLanguage::debug_get_stack_level_function(int p_level) const { - if (_debug_parse_err_line >= 0) +String GDScriptLanguage::debug_get_stack_level_function(int p_level) const { + if (_debug_parse_err_line >= 0) { return ""; + } ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, ""); int l = _debug_call_stack_pos - p_level - 1; return _call_stack[l].function->get_name(); } -String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { - if (_debug_parse_err_line >= 0) +String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { + if (_debug_parse_err_line >= 0) { return _debug_parse_err_file; + } ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, ""); int l = _debug_call_stack_pos - p_level - 1; return _call_stack[l].function->get_source(); } -void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) { - if (_debug_parse_err_line >= 0) +void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) { + if (_debug_parse_err_line >= 0) { return; + } ERR_FAIL_INDEX(p_level, _debug_call_stack_pos); int l = _debug_call_stack_pos - p_level - 1; @@ -303,23 +309,24 @@ void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p f->debug_get_stack_member_state(*_call_stack[l].line, &locals); for (List<Pair<StringName, int>>::Element *E = locals.front(); E; E = E->next()) { - p_locals->push_back(E->get().first); p_values->push_back(_call_stack[l].stack[E->get().second]); } } -void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) { - if (_debug_parse_err_line >= 0) +void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) { + if (_debug_parse_err_line >= 0) { return; + } ERR_FAIL_INDEX(p_level, _debug_call_stack_pos); int l = _debug_call_stack_pos - p_level - 1; GDScriptInstance *instance = _call_stack[l].instance; - if (!instance) + if (!instance) { return; + } Ref<GDScript> script = instance->get_script(); ERR_FAIL_COND(script.is_null()); @@ -327,16 +334,15 @@ void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> * const Map<StringName, GDScript::MemberInfo> &mi = script->debug_get_member_indices(); for (const Map<StringName, GDScript::MemberInfo>::Element *E = mi.front(); E; E = E->next()) { - p_members->push_back(E->key()); p_values->push_back(instance->debug_get_member_by_index(E->get().index)); } } ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) { - - if (_debug_parse_err_line >= 0) + if (_debug_parse_err_line >= 0) { return nullptr; + } ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, nullptr); @@ -347,7 +353,6 @@ ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) { } void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) { - const Map<StringName, int> &name_idx = GDScriptLanguage::get_singleton()->get_global_map(); const Variant *globals = GDScriptLanguage::get_singleton()->get_global_array(); @@ -355,9 +360,9 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> get_public_constants(&cinfo); for (const Map<StringName, int>::Element *E = name_idx.front(); E; E = E->next()) { - - if (ClassDB::class_exists(E->key()) || Engine::get_singleton()->has_singleton(E->key())) + if (ClassDB::class_exists(E->key()) || Engine::get_singleton()->has_singleton(E->key())) { continue; + } bool is_script_constant = false; for (List<Pair<String, Variant>>::Element *CE = cinfo.front(); CE; CE = CE->next()) { @@ -366,13 +371,15 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> break; } } - if (is_script_constant) + if (is_script_constant) { continue; + } const Variant &var = globals[E->value()]; if (Object *obj = var) { - if (Object::cast_to<GDScriptNativeClass>(obj)) + if (Object::cast_to<GDScriptNativeClass>(obj)) { continue; + } } bool skip = false; @@ -382,8 +389,9 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> break; } } - if (skip) + if (skip) { continue; + } p_globals->push_back(E->key()); p_values->push_back(var); @@ -391,19 +399,15 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> } String GDScriptLanguage::debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) { - return ""; } void GDScriptLanguage::get_recognized_extensions(List<String> *p_extensions) const { - p_extensions->push_back("gd"); } void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const { - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - p_functions->push_back(GDScriptFunctions::get_info(GDScriptFunctions::Function(i))); } @@ -417,16 +421,6 @@ void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const } { MethodInfo mi; - mi.name = "yield"; - mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "object")); - mi.arguments.push_back(PropertyInfo(Variant::STRING, "signal")); - mi.default_arguments.push_back(Variant()); - mi.default_arguments.push_back(String()); - mi.return_val = PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, "GDScriptFunctionState"); - p_functions->push_back(mi); - } - { - MethodInfo mi; mi.name = "assert"; mi.return_val.type = Variant::NIL; mi.arguments.push_back(PropertyInfo(Variant::BOOL, "condition")); @@ -437,7 +431,6 @@ void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const } void GDScriptLanguage::get_public_constants(List<Pair<String, Variant>> *p_constants) const { - Pair<String, Variant> pi; pi.first = "PI"; pi.second = Math_PI; @@ -460,7 +453,6 @@ void GDScriptLanguage::get_public_constants(List<Pair<String, Variant>> *p_const } String GDScriptLanguage::make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const { - #ifdef TOOLS_ENABLED bool th = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints"); #else @@ -470,8 +462,9 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na String s = "func " + p_name + "("; if (p_args.size()) { for (int i = 0; i < p_args.size(); i++) { - if (i > 0) + if (i > 0) { s += ", "; + } s += p_args[i].get_slice(":", 0); if (th) { String type = p_args[i].get_slice(":", 1); @@ -488,54 +481,46 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na //////// COMPLETION ////////// -#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) - -struct GDScriptCompletionContext { - - const GDScriptParser::ClassNode *_class; - const GDScriptParser::FunctionNode *function; - const GDScriptParser::BlockNode *block; - Object *base; - String base_path; - int line; - uint32_t depth; - - GDScriptCompletionContext() : - _class(nullptr), - function(nullptr), - block(nullptr), - base(nullptr), - line(0), - depth(0) {} -}; +#ifdef TOOLS_ENABLED struct GDScriptCompletionIdentifier { GDScriptParser::DataType type; String enumeration; Variant value; - const GDScriptParser::Node *assigned_expression; - - GDScriptCompletionIdentifier() : - assigned_expression(nullptr) {} + const GDScriptParser::ExpressionNode *assigned_expression = nullptr; }; -static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String, ScriptCodeCompletionOption> &r_list) { - - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; - - for (int i = 0; i < p_dir->get_file_count(); i++) { - ScriptCodeCompletionOption option(p_dir->get_file_path(i), ScriptCodeCompletionOption::KIND_FILE_PATH); - option.insert_text = quote_style + option.display + quote_style; - r_list.insert(option.display, option); - } - - for (int i = 0; i < p_dir->get_subdir_count(); i++) { - _get_directory_contents(p_dir->get_subdir(i), r_list); +// TODO: Move this to a central location (maybe core?). +static const char *underscore_classes[] = { + "ClassDB", + "Directory", + "Engine", + "File", + "Geometry", + "GodotSharp", + "JSON", + "Marshalls", + "Mutex", + "OS", + "ResourceLoader", + "ResourceSaver", + "Semaphore", + "Thread", + "VisualScriptEditor", + nullptr, +}; +static StringName _get_real_class_name(const StringName &p_source) { + const char **class_name = underscore_classes; + while (*class_name != nullptr) { + if (p_source == *class_name) { + return String("_") + p_source; + } + class_name++; } + return p_source; } -static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = true) { - +static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { String enum_name = p_info.class_name; if (enum_name.find(".") == -1) { @@ -558,7 +543,7 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = tr } } if (p_info.type == Variant::NIL) { - if (p_isarg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { + if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { return "Variant"; } else { return "void"; @@ -568,11 +553,538 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = tr return Variant::get_type_name(p_info.type); } +static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx, bool p_is_annotation = false) { + String arghint; + if (!p_is_annotation) { + arghint += _get_visual_datatype(p_info.return_val, false) + " "; + } + arghint += p_info.name + "("; + + int def_args = p_info.arguments.size() - p_info.default_arguments.size(); + int i = 0; + for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E; E = E->next()) { + if (i > 0) { + arghint += ", "; + } + + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + arghint += E->get().name + ": " + _get_visual_datatype(E->get(), true); + + if (i - def_args >= 0) { + arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string(); + } + + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + + i++; + } + + if (p_info.flags & METHOD_FLAG_VARARG) { + if (p_info.arguments.size() > 0) { + arghint += ", "; + } + if (p_arg_idx >= p_info.arguments.size()) { + arghint += String::chr(0xFFFF); + } + arghint += "..."; + if (p_arg_idx >= p_info.arguments.size()) { + arghint += String::chr(0xFFFF); + } + } + + arghint += ")"; + + return arghint; +} + +static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) { + String arghint = p_function->get_datatype().to_string() + " " + p_function->identifier->name.operator String() + "("; + + for (int i = 0; i < p_function->parameters.size(); i++) { + if (i > 0) { + arghint += ", "; + } + + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + const GDScriptParser::ParameterNode *par = p_function->parameters[i]; + arghint += par->identifier->name.operator String() + ": " + par->get_datatype().to_string(); + + if (par->default_value) { + String def_val = "<unknown>"; + if (par->default_value->type == GDScriptParser::Node::LITERAL) { + const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->default_value); + def_val = literal->value.get_construct_string(); + } else if (par->default_value->type == GDScriptParser::Node::IDENTIFIER) { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->default_value); + def_val = id->name.operator String(); + } + arghint += " = " + def_val; + } + if (i == p_arg_idx) { + arghint += String::chr(0xFFFF); + } + } + + arghint += ")"; + + return arghint; +} + +static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String, ScriptCodeCompletionOption> &r_list) { + const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; + + for (int i = 0; i < p_dir->get_file_count(); i++) { + ScriptCodeCompletionOption option(p_dir->get_file_path(i), ScriptCodeCompletionOption::KIND_FILE_PATH); + option.insert_text = quote_style + option.display + quote_style; + r_list.insert(option.display, option); + } + + for (int i = 0; i < p_dir->get_subdir_count(); i++) { + _get_directory_contents(p_dir->get_subdir(i), r_list); + } +} + +static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, Map<String, ScriptCodeCompletionOption> &r_result) { + if (p_annotation->name == "@export_range" || p_annotation->name == "@export_exp_range") { + if (p_argument == 3 || p_argument == 4) { + // Slider hint. + ScriptCodeCompletionOption slider1("or_greater", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + slider1.insert_text = p_quote_style + slider1.display + p_quote_style; + r_result.insert(slider1.display, slider1); + ScriptCodeCompletionOption slider2("or_lesser", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + slider2.insert_text = p_quote_style + slider2.display + p_quote_style; + r_result.insert(slider2.display, slider2); + } + } else if (p_annotation->name == "@export_exp_easing") { + if (p_argument == 0 || p_argument == 1) { + // Easing hint. + ScriptCodeCompletionOption hint1("attenuation", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + hint1.insert_text = p_quote_style + hint1.display + p_quote_style; + r_result.insert(hint1.display, hint1); + ScriptCodeCompletionOption hint2("inout", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + hint2.insert_text = p_quote_style + hint2.display + p_quote_style; + r_result.insert(hint2.display, hint2); + } + } else if (p_annotation->name == "@export_node_path") { + ScriptCodeCompletionOption node("Node", ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(node.display, node); + List<StringName> node_types; + ClassDB::get_inheriters_from_class("Node", &node_types); + for (const List<StringName>::Element *E = node_types.front(); E != nullptr; E = E->next()) { + if (!ClassDB::is_class_exposed(E->get())) { + continue; + } + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } + } +} + +static void _list_available_types(bool p_inherit_only, GDScriptParser::CompletionContext &p_context, Map<String, ScriptCodeCompletionOption> &r_result) { + List<StringName> native_types; + ClassDB::get_class_list(&native_types); + for (const List<StringName>::Element *E = native_types.front(); E != nullptr; E = E->next()) { + if (ClassDB::is_class_exposed(E->get()) && !Engine::get_singleton()->has_singleton(E->get())) { + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } + } + + if (p_context.current_class) { + if (!p_inherit_only && p_context.current_class->base_type.is_set()) { + // Native enums from base class + List<StringName> enums; + ClassDB::get_enum_list(p_context.current_class->base_type.native_type, &enums); + for (const List<StringName>::Element *E = enums.front(); E != nullptr; E = E->next()) { + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_ENUM); + r_result.insert(option.display, option); + } + } + // Check current class for potential types + const GDScriptParser::ClassNode *current = p_context.current_class; + while (current) { + for (int i = 0; i < current->members.size(); i++) { + const GDScriptParser::ClassNode::Member &member = current->members[i]; + switch (member.type) { + case GDScriptParser::ClassNode::Member::CLASS: { + ScriptCodeCompletionOption option(member.m_class->identifier->name, ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } break; + case GDScriptParser::ClassNode::Member::ENUM: { + if (!p_inherit_only) { + ScriptCodeCompletionOption option(member.m_enum->identifier->name, ScriptCodeCompletionOption::KIND_ENUM); + r_result.insert(option.display, option); + } + } break; + case GDScriptParser::ClassNode::Member::CONSTANT: { + if (member.constant->get_datatype().is_meta_type && p_context.current_class->outer != nullptr) { + ScriptCodeCompletionOption option(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } + } break; + default: + break; + } + } + current = current->outer; + } + } + + // Global scripts + List<StringName> global_classes; + ScriptServer::get_global_class_list(&global_classes); + for (const List<StringName>::Element *E = global_classes.front(); E != nullptr; E = E->next()) { + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } + + // Autoload singletons + Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->get(); + if (!info.is_singleton || info.path.get_extension().to_lower() != "gd") { + continue; + } + ScriptCodeCompletionOption option(info.name, ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } +} + +static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, Map<String, ScriptCodeCompletionOption> &r_result) { + for (int i = 0; i < p_suite->locals.size(); i++) { + ScriptCodeCompletionOption option(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_VARIABLE); + r_result.insert(option.display, option); + } + if (p_suite->parent_block) { + _find_identifiers_in_suite(p_suite->parent_block, r_result); + } +} + +static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result); + +static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) { + if (!p_parent_only) { + bool outer = false; + const GDScriptParser::ClassNode *clss = p_class; + while (clss) { + for (int i = 0; i < clss->members.size(); i++) { + const GDScriptParser::ClassNode::Member &member = clss->members[i]; + ScriptCodeCompletionOption option; + switch (member.type) { + case GDScriptParser::ClassNode::Member::VARIABLE: + if (p_only_functions || outer || (p_static)) { + continue; + } + option = ScriptCodeCompletionOption(member.variable->identifier->name, ScriptCodeCompletionOption::KIND_MEMBER); + break; + case GDScriptParser::ClassNode::Member::CONSTANT: + if (p_only_functions) { + continue; + } + option = ScriptCodeCompletionOption(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT); + break; + case GDScriptParser::ClassNode::Member::CLASS: + if (p_only_functions) { + continue; + } + option = ScriptCodeCompletionOption(member.m_class->identifier->name, ScriptCodeCompletionOption::KIND_CLASS); + break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: + if (p_only_functions) { + continue; + } + option = ScriptCodeCompletionOption(member.enum_value.identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT); + break; + case GDScriptParser::ClassNode::Member::ENUM: + if (p_only_functions) { + continue; + } + option = ScriptCodeCompletionOption(member.m_enum->identifier->name, ScriptCodeCompletionOption::KIND_ENUM); + break; + case GDScriptParser::ClassNode::Member::FUNCTION: + if (outer || (p_static && !member.function->is_static) || member.function->identifier->name.operator String().begins_with("@")) { + continue; + } + option = ScriptCodeCompletionOption(member.function->identifier->name, ScriptCodeCompletionOption::KIND_FUNCTION); + if (member.function->parameters.size() > 0) { + option.insert_text += "("; + } else { + option.insert_text += "()"; + } + break; + case GDScriptParser::ClassNode::Member::SIGNAL: + if (p_only_functions || outer) { + clss = clss->outer; + continue; + } + option = ScriptCodeCompletionOption(member.signal->identifier->name, ScriptCodeCompletionOption::KIND_SIGNAL); + break; + case GDScriptParser::ClassNode::Member::UNDEFINED: + break; + } + r_result.insert(option.display, option); + } + outer = true; + clss = clss->outer; + } + } + + // Parents. + GDScriptCompletionIdentifier base_type; + base_type.type = p_class->base_type; + base_type.type.is_meta_type = p_static; + + _find_identifiers_in_base(base_type, p_only_functions, r_result); +} + +static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { + GDScriptParser::DataType base_type = p_base.type; + bool _static = base_type.is_meta_type; + + if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) { + ScriptCodeCompletionOption option("new", ScriptCodeCompletionOption::KIND_FUNCTION); + option.insert_text += "("; + r_result.insert(option.display, option); + } + + while (!base_type.has_no_type()) { + switch (base_type.kind) { + case GDScriptParser::DataType::CLASS: { + _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result); + // This already finds all parent identifiers, so we are done. + base_type = GDScriptParser::DataType(); + } break; + case GDScriptParser::DataType::SCRIPT: { + Ref<Script> scr = base_type.script_type; + if (scr.is_valid()) { + if (!p_only_functions) { + if (!_static) { + List<PropertyInfo> members; + scr->get_script_property_list(&members); + for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); + } + } + Map<StringName, Variant> constants; + scr->get_constants(&constants); + for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); + } + + List<MethodInfo> signals; + scr->get_script_signal_list(&signals); + for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL); + r_result.insert(option.display, option); + } + } + + List<MethodInfo> methods; + scr->get_script_method_list(&methods); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name.begins_with("@")) { + continue; + } + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); + if (E->get().arguments.size()) { + option.insert_text += "("; + } else { + option.insert_text += "()"; + } + r_result.insert(option.display, option); + } + + Ref<Script> base_script = scr->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.native_type = scr->get_instance_base_type(); + } + } else { + return; + } + } break; + case GDScriptParser::DataType::NATIVE: { + StringName type = _get_real_class_name(base_type.native_type); + if (!ClassDB::class_exists(type)) { + return; + } + + if (!p_only_functions) { + List<String> constants; + ClassDB::get_integer_constant_list(type, &constants); + for (List<String>::Element *E = constants.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); + } + + if (!_static || Engine::get_singleton()->has_singleton(type)) { + List<PropertyInfo> pinfo; + ClassDB::get_property_list(type, &pinfo); + for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) { + continue; + } + if (E->get().name.find("/") != -1) { + continue; + } + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); + } + } + } + + if (!_static || Engine::get_singleton()->has_singleton(type)) { + List<MethodInfo> methods; + bool is_autocompleting_getters = GLOBAL_GET("debug/gdscript/completion/autocomplete_setters_and_getters").booleanize(); + ClassDB::get_method_list(type, &methods, false, !is_autocompleting_getters); + for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name.begins_with("_")) { + continue; + } + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); + if (E->get().arguments.size()) { + option.insert_text += "("; + } else { + option.insert_text += "()"; + } + r_result.insert(option.display, option); + } + } + + return; + } break; + case GDScriptParser::DataType::BUILTIN: { + Callable::CallError err; + Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err); + if (err.error != Callable::CallError::CALL_OK) { + return; + } + + if (!p_only_functions) { + List<PropertyInfo> members; + if (p_base.value.get_type() != Variant::NIL) { + p_base.value.get_property_list(&members); + } else { + tmp.get_property_list(&members); + } + + for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { + if (String(E->get().name).find("/") == -1) { + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); + } + } + } + + List<MethodInfo> methods; + tmp.get_method_list(&methods); + for (const List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); + if (E->get().arguments.size()) { + option.insert_text += "("; + } else { + option.insert_text += "()"; + } + r_result.insert(option.display, option); + } + + return; + } break; + default: { + return; + } break; + } + } +} + +static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { + if (!p_only_functions && p_context.current_suite) { + // This includes function parameters, since they are also locals. + _find_identifiers_in_suite(p_context.current_suite, r_result); + } + + if (p_context.current_class) { + _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result); + } + + for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { + MethodInfo function = GDScriptFunctions::get_info(GDScriptFunctions::Function(i)); + ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION); + if (function.arguments.size() || (function.flags & METHOD_FLAG_VARARG)) { + option.insert_text += "("; + } else { + option.insert_text += "()"; + } + r_result.insert(option.display, option); + } + + if (p_only_functions) { + return; + } + + static const char *_type_names[Variant::VARIANT_MAX] = { + "null", "bool", "int", "float", "String", "StringName", "Vector2", "Vector2i", "Rect2", "Rect2i", "Vector3", "Vector3i", "Transform2D", "Plane", "Quat", "AABB", "Basis", "Transform", + "Color", "NodePath", "RID", "Signal", "Callable", "Object", "Dictionary", "Array", "PackedByteArray", "PackedInt32Array", "PackedInt64Array", "PackedFloat32Array", "PackedFloat64Array", "PackedStringArray", + "PackedVector2Array", "PackedVector3Array", "PackedColorArray" + }; + static_assert((sizeof(_type_names) / sizeof(*_type_names)) == Variant::VARIANT_MAX, "Completion for builtin types is incomplete"); + + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + ScriptCodeCompletionOption option(_type_names[i], ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); + } + + static const char *_keywords[] = { + "and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert", + "breakpoint", "class", "extends", "is", "func", "preload", "signal", "tool", "await", + "const", "enum", "static", "super", "var", "break", "continue", "if", "elif", + "else", "for", "pass", "return", "match", "while", + 0 + }; + + const char **kw = _keywords; + while (*kw) { + ScriptCodeCompletionOption option(*kw, ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + r_result.insert(option.display, option); + kw++; + } + + Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) { + if (!E->value().is_singleton) { + continue; + } + ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); + } + + // Native classes and global constants. + for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { + ScriptCodeCompletionOption option; + if (ClassDB::class_exists(E->key()) || Engine::get_singleton()->has_singleton(E->key())) { + option = ScriptCodeCompletionOption(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + } else { + option = ScriptCodeCompletionOption(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + } + r_result.insert(option.display, option); + } +} + static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) { GDScriptCompletionIdentifier ci; ci.value = p_value; ci.type.is_constant = true; - ci.type.has_type = true; + ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; ci.type.kind = GDScriptParser::DataType::BUILTIN; ci.type.builtin_type = p_value.get_type(); @@ -591,12 +1103,7 @@ static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) { } if (scr.is_valid()) { ci.type.script_type = scr; - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - ci.type.kind = GDScriptParser::DataType::GDSCRIPT; - } else { - ci.type.kind = GDScriptParser::DataType::SCRIPT; - } + ci.type.kind = GDScriptParser::DataType::SCRIPT; ci.type.native_type = scr->get_instance_base_type(); } else { ci.type.kind = GDScriptParser::DataType::NATIVE; @@ -618,7 +1125,7 @@ static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_pr ci.enumeration = p_property.class_name; } - ci.type.has_type = true; + ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; ci.type.builtin_type = p_property.type; if (p_property.type == Variant::OBJECT) { ci.type.kind = GDScriptParser::DataType::NATIVE; @@ -629,346 +1136,292 @@ static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_pr return ci; } -static GDScriptCompletionIdentifier _type_from_gdtype(const GDScriptDataType &p_gdtype) { - GDScriptCompletionIdentifier ci; - if (!p_gdtype.has_type) { - return ci; - } - - ci.type.has_type = true; - ci.type.builtin_type = p_gdtype.builtin_type; - ci.type.native_type = p_gdtype.native_type; - ci.type.script_type = p_gdtype.script_type; - - switch (p_gdtype.kind) { - case GDScriptDataType::UNINITIALIZED: { - ERR_PRINT("Uninitialized completion. Please report a bug."); - } break; - case GDScriptDataType::BUILTIN: { - ci.type.kind = GDScriptParser::DataType::BUILTIN; - } break; - case GDScriptDataType::NATIVE: { - ci.type.kind = GDScriptParser::DataType::NATIVE; - } break; - case GDScriptDataType::GDSCRIPT: { - ci.type.kind = GDScriptParser::DataType::GDSCRIPT; - } break; - case GDScriptDataType::SCRIPT: { - ci.type.kind = GDScriptParser::DataType::SCRIPT; - } break; - } - return ci; -} +static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); +static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); +static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type); -static bool _guess_identifier_type(GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); -static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); -static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type); - -static bool _guess_expression_type(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_expression, GDScriptCompletionIdentifier &r_type) { +static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::ExpressionNode *p_expression, GDScriptCompletionIdentifier &r_type) { bool found = false; - if (++p_context.depth > 100) { - print_error("Maximum _guess_expression_type depth limit reached. Please file a bugreport."); - return false; - } - - switch (p_expression->type) { - case GDScriptParser::Node::TYPE_CONSTANT: { - const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_expression); - r_type = _type_from_variant(cn->value); - found = true; - } break; - case GDScriptParser::Node::TYPE_SELF: { - if (p_context._class) { - r_type.type.has_type = true; - r_type.type.kind = GDScriptParser::DataType::CLASS; - r_type.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); - r_type.type.is_constant = true; - r_type.value = p_context.base; + if (p_expression->is_constant) { + // Already has a value, so just use that. + r_type = _type_from_variant(p_expression->reduced_value); + found = true; + } else { + switch (p_expression->type) { + case GDScriptParser::Node::LITERAL: { + const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(p_expression); + r_type = _type_from_variant(literal->value); found = true; - } - } break; - case GDScriptParser::Node::TYPE_IDENTIFIER: { - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); - found = _guess_identifier_type(p_context, id->name, r_type); - } break; - case GDScriptParser::Node::TYPE_DICTIONARY: { - // Try to recreate the dictionary - const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression); - Dictionary d; - bool full = true; - for (int i = 0; i < dn->elements.size(); i++) { - GDScriptCompletionIdentifier key; - if (_guess_expression_type(p_context, dn->elements[i].key, key)) { - GDScriptCompletionIdentifier value; - if (_guess_expression_type(p_context, dn->elements[i].value, value)) { - if (!value.type.is_constant) { + } break; + case GDScriptParser::Node::SELF: { + if (p_context.current_class) { + r_type.type.kind = GDScriptParser::DataType::CLASS; + r_type.type.type_source = GDScriptParser::DataType::INFERRED; + r_type.type.is_constant = true; + r_type.type.class_type = p_context.current_class; + r_type.value = p_context.base; + found = true; + } + } break; + case GDScriptParser::Node::IDENTIFIER: { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); + found = _guess_identifier_type(p_context, id->name, r_type); + } break; + case GDScriptParser::Node::DICTIONARY: { + // Try to recreate the dictionary. + const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression); + Dictionary d; + bool full = true; + for (int i = 0; i < dn->elements.size(); i++) { + GDScriptCompletionIdentifier key; + if (_guess_expression_type(p_context, dn->elements[i].key, key)) { + if (!key.type.is_constant) { + full = false; + break; + } + GDScriptCompletionIdentifier value; + if (_guess_expression_type(p_context, dn->elements[i].value, value)) { + if (!value.type.is_constant) { + full = false; + break; + } + d[key.value] = value.value; + } else { full = false; break; } - d[key.value] = value.value; } else { full = false; break; } - } else { - full = false; - break; } - } - if (full) { - // If not fully constant, setting this value is detrimental to the inference - r_type.value = d; - r_type.type.is_constant = true; - } - r_type.type.has_type = true; - r_type.type.kind = GDScriptParser::DataType::BUILTIN; - r_type.type.builtin_type = Variant::DICTIONARY; - } break; - case GDScriptParser::Node::TYPE_ARRAY: { - // Try to recreate the array - const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); - Array a; - bool full = true; - a.resize(an->elements.size()); - for (int i = 0; i < an->elements.size(); i++) { - GDScriptCompletionIdentifier value; - if (_guess_expression_type(p_context, an->elements[i], value)) { - a[i] = value.value; - } else { - full = false; - break; + if (full) { + r_type.value = d; + r_type.type.is_constant = true; } - } - if (full) { - // If not fully constant, setting this value is detrimental to the inference - r_type.value = a; - } - r_type.type.has_type = true; - r_type.type.kind = GDScriptParser::DataType::BUILTIN; - r_type.type.builtin_type = Variant::ARRAY; - } break; - case GDScriptParser::Node::TYPE_CAST: { - const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); - GDScriptCompletionIdentifier value; - if (_guess_expression_type(p_context, cn->source_node, r_type)) { - r_type.type = cn->get_datatype(); + r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = Variant::DICTIONARY; found = true; - } - } break; - case GDScriptParser::Node::TYPE_OPERATOR: { - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_expression); - switch (op->op) { - case GDScriptParser::OperatorNode::OP_CALL: { - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { - const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); - r_type.type.has_type = true; - r_type.type.kind = GDScriptParser::DataType::BUILTIN; - r_type.type.builtin_type = tn->vtype; - found = true; - break; - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - const GDScriptParser::BuiltInFunctionNode *bin = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); - MethodInfo mi = GDScriptFunctions::get_info(bin->function); - r_type = _type_from_property(mi.return_val); - found = true; + } break; + case GDScriptParser::Node::ARRAY: { + // Try to recreate the array + const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); + Array a; + bool full = true; + a.resize(an->elements.size()); + for (int i = 0; i < an->elements.size(); i++) { + GDScriptCompletionIdentifier value; + if (_guess_expression_type(p_context, an->elements[i], value)) { + if (value.type.is_constant) { + a[i] = value.value; + } else { + full = false; + break; + } + } else { + full = false; break; - } else if (op->arguments.size() >= 2 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name; + } + } + if (full) { + // If not fully constant, setting this value is detrimental to the inference. + r_type.value = a; + } + r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = Variant::ARRAY; + found = true; + } break; + case GDScriptParser::Node::CAST: { + const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); + GDScriptCompletionIdentifier value; + if (_guess_expression_type(p_context, cn->operand, r_type)) { + r_type.type = cn->get_datatype(); + found = true; + } + } break; + case GDScriptParser::Node::CALL: { + const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression); + if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { + r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = GDScriptParser::get_builtin_type(call->function_name); + found = true; + break; + } else if (GDScriptParser::get_builtin_function(call->function_name) < GDScriptFunctions::FUNC_MAX) { + MethodInfo mi = GDScriptFunctions::get_info(GDScriptParser::get_builtin_function(call->function_name)); + r_type = _type_from_property(mi.return_val); + found = true; + break; + } else { + GDScriptParser::CompletionContext c = p_context; + c.current_line = call->start_line; - GDScriptCompletionContext c = p_context; - c.line = op->line; + GDScriptParser::Node::Type callee_type = call->get_callee_type(); - GDScriptCompletionIdentifier base; - if (!_guess_expression_type(c, op->arguments[0], base)) { + GDScriptCompletionIdentifier base; + 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; + base.type.type_source = GDScriptParser::DataType::INFERRED; + base.type.is_constant = true; + base.type.class_type = p_context.current_class; + base.value = p_context.base; + } else { + break; + } + } else if (callee_type == GDScriptParser::Node::SUBSCRIPT && static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->is_attribute) { + if (!_guess_expression_type(c, static_cast<const GDScriptParser::SubscriptNode *>(call->callee)->base, base)) { found = false; break; } + } else { + break; + } - // Try call if constant methods with constant arguments - if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) { - GDScriptParser::DataType native_type = base.type; + // Try call if constant methods with constant arguments + if (base.type.is_constant && base.value.get_type() == Variant::OBJECT) { + GDScriptParser::DataType native_type = base.type; - while (native_type.kind == GDScriptParser::DataType::CLASS) { - native_type = native_type.class_type->base_type; - } + while (native_type.kind == GDScriptParser::DataType::CLASS) { + native_type = native_type.class_type->base_type; + } - while (native_type.kind == GDScriptParser::DataType::GDSCRIPT || native_type.kind == GDScriptParser::DataType::SCRIPT) { - if (native_type.script_type.is_valid()) { - Ref<Script> parent = native_type.script_type->get_base_script(); - if (parent.is_valid()) { - native_type.script_type = parent; - } else { - native_type.kind = GDScriptParser::DataType::NATIVE; - native_type.native_type = native_type.script_type->get_instance_base_type(); + while (native_type.kind == GDScriptParser::DataType::SCRIPT) { + if (native_type.script_type.is_valid()) { + Ref<Script> parent = native_type.script_type->get_base_script(); + if (parent.is_valid()) { + native_type.script_type = parent; + } else { + native_type.kind = GDScriptParser::DataType::NATIVE; + native_type.native_type = native_type.script_type->get_instance_base_type(); + if (!ClassDB::class_exists(native_type.native_type)) { + native_type.native_type = String("_") + native_type.native_type; if (!ClassDB::class_exists(native_type.native_type)) { - native_type.native_type = String("_") + native_type.native_type; - if (!ClassDB::class_exists(native_type.native_type)) { - native_type.has_type = false; - } + native_type.kind = GDScriptParser::DataType::UNRESOLVED; } } } } + } - if (native_type.has_type && native_type.kind == GDScriptParser::DataType::NATIVE) { - MethodBind *mb = ClassDB::get_method(native_type.native_type, id); - if (mb && mb->is_const()) { - bool all_is_const = true; - Vector<Variant> args; - GDScriptCompletionContext c2 = p_context; - c2.line = op->line; - for (int i = 2; all_is_const && i < op->arguments.size(); i++) { - GDScriptCompletionIdentifier arg; - - if (_guess_expression_type(c2, op->arguments[i], arg)) { - if (arg.type.has_type && arg.type.is_constant && arg.value.get_type() != Variant::OBJECT) { - args.push_back(arg.value); - } else { - all_is_const = false; - } - } else { - all_is_const = false; - } + if (native_type.kind == GDScriptParser::DataType::NATIVE) { + MethodBind *mb = ClassDB::get_method(native_type.native_type, call->function_name); + if (mb && mb->is_const()) { + bool all_is_const = true; + Vector<Variant> args; + GDScriptParser::CompletionContext c2 = p_context; + c2.current_line = call->start_line; + for (int i = 0; all_is_const && i < call->arguments.size(); i++) { + GDScriptCompletionIdentifier arg; + + if (!call->arguments[i]->is_constant) { + all_is_const = false; } + } - Object *baseptr = base.value; - - if (all_is_const && String(id) == "get_node" && ClassDB::is_parent_class(native_type.native_type, "Node") && args.size()) { + Object *baseptr = base.value; - String arg1 = args[0]; - if (arg1.begins_with("/root/")) { - String which = arg1.get_slice("/", 2); - if (which != "") { - // Try singletons first - if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) { - r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); - found = true; - } else { - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); + if (all_is_const && String(call->function_name) == "get_node" && ClassDB::is_parent_class(native_type.native_type, "Node") && args.size()) { + String arg1 = args[0]; + if (arg1.begins_with("/root/")) { + String which = arg1.get_slice("/", 2); + if (which != "") { + // Try singletons first + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) { + r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); + found = true; + } else { + Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + String name = E->key(); + if (name == which) { + String script = E->value().path; - String s = E->get().name; - if (!s.begins_with("autoload/")) { - continue; + if (!script.begins_with("res://")) { + script = "res://" + script; } - String name = s.get_slice("/", 1); - if (name == which) { - String script = ProjectSettings::get_singleton()->get(s); - if (script.begins_with("*")) { - script = script.right(1); - } - - if (!script.begins_with("res://")) { - script = "res://" + script; - } - - if (!script.ends_with(".gd")) { - //not a script, try find the script anyway, - //may have some success - script = script.get_basename() + ".gd"; - } + if (!script.ends_with(".gd")) { + //not a script, try find the script anyway, + //may have some success + script = script.get_basename() + ".gd"; + } - if (FileAccess::exists(script)) { - Ref<Script> scr; - if (ScriptCodeCompletionCache::get_singleton()) { - scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script); - } else { - scr = ResourceLoader::load(script); - } - if (scr.is_valid()) { - r_type.type.has_type = true; - r_type.type.script_type = scr; - r_type.type.is_constant = false; - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - r_type.type.kind = GDScriptParser::DataType::GDSCRIPT; - } else { - r_type.type.kind = GDScriptParser::DataType::SCRIPT; - } - r_type.value = Variant(); - found = true; - } + if (FileAccess::exists(script)) { + Error err = OK; + Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); + if (err == OK) { + r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_type.type.script_path = script; + r_type.type.class_type = parser->get_parser()->get_tree(); + r_type.type.is_constant = false; + r_type.type.kind = GDScriptParser::DataType::CLASS; + r_type.value = Variant(); + p_context.dependent_parsers.push_back(parser); + found = true; } - break; } + break; } } } } } + } - if (!found && all_is_const && baseptr) { - Vector<const Variant *> argptr; - for (int i = 0; i < args.size(); i++) { - argptr.push_back(&args[i]); - } + if (!found && all_is_const && baseptr) { + Vector<const Variant *> argptr; + for (int i = 0; i < args.size(); i++) { + argptr.push_back(&args[i]); + } - Callable::CallError ce; - Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce); + Callable::CallError ce; + Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce); - if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { - if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) { - r_type = _type_from_variant(ret); - found = true; - } + if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { + if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) { + r_type = _type_from_variant(ret); + found = true; } } } } } - - if (!found) { - found = _guess_method_return_type_from_base(c, base, id, r_type); - } - } - } break; - case GDScriptParser::OperatorNode::OP_PARENT_CALL: { - if (!p_context._class || !op->arguments.size() || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { - break; } - StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name; - - GDScriptCompletionIdentifier base; - base.value = p_context.base; - base.type = p_context._class->base_type; - - GDScriptCompletionContext c = p_context; - c.line = op->line; - - found = _guess_method_return_type_from_base(c, base, id, r_type); - } break; - case GDScriptParser::OperatorNode::OP_INDEX_NAMED: { - if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { - found = false; - break; + if (!found) { + found = _guess_method_return_type_from_base(c, base, call->function_name, r_type); } - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]); - - GDScriptCompletionContext c = p_context; - c.line = op->line; + } + } break; + case GDScriptParser::Node::SUBSCRIPT: { + const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression); + if (subscript->is_attribute) { + GDScriptParser::CompletionContext c = p_context; + c.current_line = subscript->start_line; GDScriptCompletionIdentifier base; - if (!_guess_expression_type(c, op->arguments[0], base)) { + if (!_guess_expression_type(c, subscript->base, base)) { found = false; break; } - if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(id->name))) { - Variant value = base.value.operator Dictionary()[String(id->name)]; + if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(subscript->attribute->name))) { + Variant value = base.value.operator Dictionary()[String(subscript->attribute->name)]; r_type = _type_from_variant(value); found = true; break; } const GDScriptParser::DictionaryNode *dn = nullptr; - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) { - dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]); - } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) { + if (subscript->base->type == GDScriptParser::Node::DICTIONARY) { + dn = static_cast<const GDScriptParser::DictionaryNode *>(subscript->base); + } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::DICTIONARY) { dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression); } @@ -978,7 +1431,7 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G if (!_guess_expression_type(c, dn->elements[i].key, key)) { continue; } - if (key.value == String(id->name)) { + if (key.value == String(subscript->attribute->name)) { r_type.assigned_expression = dn->elements[i].value; found = _guess_expression_type(c, dn->elements[i].value, r_type); break; @@ -987,26 +1440,25 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G } if (!found) { - found = _guess_identifier_type_from_base(c, base, id->name, r_type); + found = _guess_identifier_type_from_base(c, base, subscript->attribute->name, r_type); } - } break; - case GDScriptParser::OperatorNode::OP_INDEX: { - if (op->arguments.size() < 2) { + } else { + if (subscript->index == nullptr) { found = false; break; } - GDScriptCompletionContext c = p_context; - c.line = op->line; + GDScriptParser::CompletionContext c = p_context; + c.current_line = subscript->start_line; GDScriptCompletionIdentifier base; - if (!_guess_expression_type(c, op->arguments[0], base)) { + if (!_guess_expression_type(c, subscript->base, base)) { found = false; break; } GDScriptCompletionIdentifier index; - if (!_guess_expression_type(c, op->arguments[1], index)) { + if (!_guess_expression_type(c, subscript->index, index)) { found = false; break; } @@ -1018,11 +1470,11 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G break; } - // Look if it is a dictionary node + // Look if it is a dictionary node. const GDScriptParser::DictionaryNode *dn = nullptr; - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) { - dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]); - } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_DICTIONARY) { + if (subscript->base->type == GDScriptParser::Node::DICTIONARY) { + dn = static_cast<const GDScriptParser::DictionaryNode *>(subscript->base); + } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::DICTIONARY) { dn = static_cast<const GDScriptParser::DictionaryNode *>(base.assigned_expression); } @@ -1040,13 +1492,13 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G } } - // Look if it is an array node + // Look if it is an array node. if (!found && index.value.is_num()) { int idx = index.value; const GDScriptParser::ArrayNode *an = nullptr; - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_ARRAY) { - an = static_cast<const GDScriptParser::ArrayNode *>(op->arguments[0]); - } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::TYPE_ARRAY) { + if (subscript->base->type == GDScriptParser::Node::ARRAY) { + an = static_cast<const GDScriptParser::ArrayNode *>(subscript->base); + } else if (base.assigned_expression && base.assigned_expression->type == GDScriptParser::Node::ARRAY) { an = static_cast<const GDScriptParser::ArrayNode *>(base.assigned_expression); } @@ -1073,93 +1525,74 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G found = true; } } - } break; - default: { - if (op->arguments.size() < 2) { - found = false; - break; - } - - Variant::Operator vop = Variant::OP_MAX; - switch (op->op) { - case GDScriptParser::OperatorNode::OP_ADD: vop = Variant::OP_ADD; break; - case GDScriptParser::OperatorNode::OP_SUB: vop = Variant::OP_SUBTRACT; break; - case GDScriptParser::OperatorNode::OP_MUL: vop = Variant::OP_MULTIPLY; break; - case GDScriptParser::OperatorNode::OP_DIV: vop = Variant::OP_DIVIDE; break; - case GDScriptParser::OperatorNode::OP_MOD: vop = Variant::OP_MODULE; break; - case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: vop = Variant::OP_SHIFT_LEFT; break; - case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: vop = Variant::OP_SHIFT_RIGHT; break; - case GDScriptParser::OperatorNode::OP_BIT_AND: vop = Variant::OP_BIT_AND; break; - case GDScriptParser::OperatorNode::OP_BIT_OR: vop = Variant::OP_BIT_OR; break; - case GDScriptParser::OperatorNode::OP_BIT_XOR: vop = Variant::OP_BIT_XOR; break; - default: { - } - } + } + } break; + case GDScriptParser::Node::BINARY_OPERATOR: { + const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); - if (vop == Variant::OP_MAX) { - break; - } + if (op->variant_op == Variant::OP_MAX) { + break; + } - GDScriptCompletionContext context = p_context; - context.line = op->line; + GDScriptParser::CompletionContext context = p_context; + context.current_line = op->start_line; - GDScriptCompletionIdentifier p1; - GDScriptCompletionIdentifier p2; + GDScriptCompletionIdentifier p1; + GDScriptCompletionIdentifier p2; - if (!_guess_expression_type(context, op->arguments[0], p1)) { - found = false; - break; - } + if (!_guess_expression_type(context, op->left_operand, p1)) { + found = false; + break; + } - if (!_guess_expression_type(context, op->arguments[1], p2)) { - found = false; - break; - } + if (!_guess_expression_type(context, op->right_operand, p2)) { + found = false; + break; + } - Callable::CallError ce; - bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT; - Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type.builtin_type, nullptr, 0, ce); - bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT; - Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type.builtin_type, nullptr, 0, ce); - // avoid potential invalid ops - if ((vop == Variant::OP_DIVIDE || vop == Variant::OP_MODULE) && v2.get_type() == Variant::INT) { - v2 = 1; - v2_use_value = false; - } - if (vop == Variant::OP_DIVIDE && v2.get_type() == Variant::FLOAT) { - v2 = 1.0; - v2_use_value = false; - } + Callable::CallError ce; + bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT; + Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type.builtin_type, nullptr, 0, ce); + bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT; + Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type.builtin_type, nullptr, 0, ce); + // avoid potential invalid ops + if ((op->variant_op == Variant::OP_DIVIDE || op->variant_op == Variant::OP_MODULE) && v2.get_type() == Variant::INT) { + v2 = 1; + v2_use_value = false; + } + if (op->variant_op == Variant::OP_DIVIDE && v2.get_type() == Variant::FLOAT) { + v2 = 1.0; + v2_use_value = false; + } - Variant res; - bool valid; - Variant::evaluate(vop, v1, v2, res, valid); - if (!valid) { - found = false; - break; - } - r_type = _type_from_variant(res); - if (!v1_use_value || !v2_use_value) { - r_type.value = Variant(); - r_type.type.is_constant = false; - } + Variant res; + bool valid; + Variant::evaluate(op->variant_op, v1, v2, res, valid); + if (!valid) { + found = false; + break; + } + r_type = _type_from_variant(res); + if (!v1_use_value || !v2_use_value) { + r_type.value = Variant(); + r_type.type.is_constant = false; + } - found = true; - } break; - } - } break; - default: { + found = true; + } break; + default: + break; } } // It may have found a null, but that's never useful - if (found && r_type.type.has_type && r_type.type.kind == GDScriptParser::DataType::BUILTIN && r_type.type.builtin_type == Variant::NIL) { + if (found && r_type.type.kind == GDScriptParser::DataType::BUILTIN && r_type.type.builtin_type == Variant::NIL) { found = false; } // Check type hint last. For collections we want chance to get the actual value first // This way we can detect types from the content of dictionaries and arrays - if (!found && p_expression->get_datatype().has_type) { + if (!found && p_expression->get_datatype().is_hard_type()) { r_type.type = p_expression->get_datatype(); if (!r_type.assigned_expression) { r_type.assigned_expression = p_expression; @@ -1170,306 +1603,289 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G return found; } -static bool _guess_identifier_type(GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { - - // Look in blocks first - const GDScriptParser::BlockNode *blk = p_context.block; +static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { + // Look in blocks first. int last_assign_line = -1; - const GDScriptParser::Node *last_assigned_expression = nullptr; - GDScriptParser::DataType var_type; - while (blk) { - if (blk->variables.has(p_identifier)) { - if (blk->variables[p_identifier]->line > p_context.line) { - return false; - } + const GDScriptParser::ExpressionNode *last_assigned_expression = nullptr; + GDScriptParser::DataType id_type; + GDScriptParser::SuiteNode *suite = p_context.current_suite; + bool is_function_parameter = false; - var_type = blk->variables[p_identifier]->datatype; + if (suite) { + if (suite->has_local(p_identifier)) { + const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier); - if (!last_assigned_expression && blk->variables[p_identifier]->assign && blk->variables[p_identifier]->assign->type == GDScriptParser::Node::TYPE_OPERATOR) { - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->variables[p_identifier]->assign); - if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN && op->arguments.size() >= 2) { - last_assign_line = op->line; - last_assigned_expression = op->arguments[1]; - } - } - } + id_type = local.get_datatype(); - for (const List<GDScriptParser::Node *>::Element *E = blk->statements.front(); E; E = E->next()) { - const GDScriptParser::Node *expr = E->get(); - if (expr->line > p_context.line || expr->type != GDScriptParser::Node::TYPE_OPERATOR) { - continue; + // Check initializer as the first assignment. + switch (local.type) { + case GDScriptParser::SuiteNode::Local::VARIABLE: + if (local.variable->initializer) { + last_assign_line = local.variable->initializer->end_line; + last_assigned_expression = local.variable->initializer; + } + break; + case GDScriptParser::SuiteNode::Local::CONSTANT: + if (local.constant->initializer) { + last_assign_line = local.constant->initializer->end_line; + last_assigned_expression = local.constant->initializer; + } + break; + case GDScriptParser::SuiteNode::Local::PARAMETER: + if (local.parameter->default_value) { + last_assign_line = local.parameter->default_value->end_line; + last_assigned_expression = local.parameter->default_value; + } + is_function_parameter = true; + break; + default: + break; } + } + } - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(expr); - if (op->op != GDScriptParser::OperatorNode::OP_ASSIGN || op->line < last_assign_line) { - continue; + while (suite) { + for (int i = 0; i < suite->statements.size(); i++) { + if (suite->statements[i]->start_line > p_context.current_line) { + break; } - if (op->arguments.size() >= 2 && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]); - if (id->name == p_identifier) { - last_assign_line = op->line; - last_assigned_expression = op->arguments[1]; - } + switch (suite->statements[i]->type) { + case GDScriptParser::Node::ASSIGNMENT: { + const GDScriptParser::AssignmentNode *assign = static_cast<const GDScriptParser::AssignmentNode *>(suite->statements[i]); + if (assign->end_line > last_assign_line && assign->assignee && assign->assigned_value && assign->assignee->type == GDScriptParser::Node::IDENTIFIER) { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->assignee); + if (id->name == p_identifier) { + last_assign_line = assign->assigned_value->end_line; + last_assigned_expression = assign->assigned_value; + } + } + } break; + default: + // TODO: Check sub blocks (control flow statements) as they might also reassign stuff. + break; } } - if (blk->if_condition && blk->if_condition->type == GDScriptParser::Node::TYPE_OPERATOR && static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition)->op == GDScriptParser::OperatorNode::OP_IS) { - //is used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. - //super dirty hack, but very useful - //credit: Zylann - //TODO: this could be hacked to detect ANDed conditions too.. - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(blk->if_condition); - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name == p_identifier) { - //bingo - GDScriptCompletionContext c = p_context; - c.line = op->line; - c.block = blk; - if (_guess_expression_type(p_context, op->arguments[1], r_type)) { - r_type.type.is_meta_type = false; // Right-hand of `is` will be a meta type, but the left-hand value is not - // Not an assignment, it shouldn't carry any value - r_type.value = Variant(); - r_type.assigned_expression = nullptr; - - return true; + if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::BINARY_OPERATOR && static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition)->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { + // Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. + // Super dirty hack, but very useful. + // Credit: Zylann. + // TODO: this could be hacked to detect ANDed conditions too... + const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition); + if (op->left_operand && op->right_operand && op->left_operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->left_operand)->name == p_identifier) { + // Bingo. + GDScriptParser::CompletionContext c = p_context; + c.current_line = op->left_operand->start_line; + c.current_suite = suite; + GDScriptCompletionIdentifier is_type; + if (_guess_expression_type(c, op->right_operand, is_type)) { + id_type = is_type.type; + id_type.is_meta_type = false; + if (last_assign_line < c.current_line) { + // Override last assignment. + last_assign_line = c.current_line; + last_assigned_expression = nullptr; + } } } } - blk = blk->parent_block; + suite = suite->parent_block; } - if (last_assigned_expression && last_assign_line != p_context.line) { - GDScriptCompletionContext c = p_context; - c.line = last_assign_line; + if (last_assigned_expression && last_assign_line != p_context.current_line) { + GDScriptParser::CompletionContext c = p_context; + c.current_line = last_assign_line; r_type.assigned_expression = last_assigned_expression; if (_guess_expression_type(c, last_assigned_expression, r_type)) { - if (var_type.has_type) { - r_type.type = var_type; - } return true; } } - if (var_type.has_type) { - r_type.type = var_type; - return true; - } - - if (p_context.function) { - for (int i = 0; i < p_context.function->arguments.size(); i++) { - if (p_context.function->arguments[i] == p_identifier) { - if (p_context.function->argument_types[i].has_type) { - r_type.type = p_context.function->argument_types[i]; - return true; - } - - int def_from = p_context.function->arguments.size() - p_context.function->default_values.size(); - if (i >= def_from) { - int def_idx = i - def_from; - if (p_context.function->default_values[def_idx]->type == GDScriptParser::Node::TYPE_OPERATOR) { - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_context.function->default_values[def_idx]); - if (op->arguments.size() < 2) { - return false; - } - GDScriptCompletionContext c = p_context; - c.function = nullptr; - c.block = nullptr; - return _guess_expression_type(c, op->arguments[1], r_type); - } - } - break; - } - } - - GDScriptParser::DataType base_type = p_context._class->base_type; - while (base_type.has_type) { + if (is_function_parameter && p_context.current_function && p_context.current_class) { + // Check if it's override of native function, then we can assume the type from the signature. + GDScriptParser::DataType base_type = p_context.current_class->base_type; + while (base_type.is_set()) { switch (base_type.kind) { - case GDScriptParser::DataType::GDSCRIPT: { - Ref<GDScript> gds = base_type.script_type; - if (gds.is_valid() && gds->has_method(p_context.function->name)) { - GDScriptFunction *func = gds->get_member_functions()[p_context.function->name]; - if (func) { - for (int i = 0; i < func->get_argument_count(); i++) { - if (func->get_argument_name(i) == p_identifier) { - r_type = _type_from_gdtype(func->get_argument_type(i)); - return true; - } - } + case GDScriptParser::DataType::CLASS: + if (base_type.class_type->has_function(p_context.current_function->identifier->name)) { + GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function; + const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier]]; + if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { + id_type = parameter->get_datatype(); } - Ref<GDScript> base_gds = gds->get_base_script(); - if (base_gds.is_valid()) { - base_type.kind = GDScriptParser::DataType::GDSCRIPT; - base_type.script_type = base_gds; - } else { - base_type.kind = GDScriptParser::DataType::NATIVE; - base_type.native_type = gds->get_instance_base_type(); + if (parameter->default_value) { + GDScriptParser::CompletionContext c = p_context; + c.current_function = parent_function; + c.current_class = base_type.class_type; + c.base = nullptr; + if (_guess_expression_type(c, parameter->default_value, r_type)) { + return true; + } } - } else { - base_type.kind = GDScriptParser::DataType::NATIVE; - base_type.native_type = gds->get_instance_base_type(); + base_type = base_type.class_type->base_type; } - } break; + break; case GDScriptParser::DataType::NATIVE: { - List<MethodInfo> methods; - ClassDB::get_method_list(base_type.native_type, &methods); - ClassDB::get_virtual_methods(base_type.native_type, &methods); - - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - if (E->get().name == p_context.function->name) { - MethodInfo &mi = E->get(); - for (List<PropertyInfo>::Element *F = mi.arguments.front(); F; F = F->next()) { - if (F->get().name == p_identifier) { - r_type = _type_from_property(F->get()); - return true; - } + if (id_type.is_set() && !id_type.is_variant()) { + base_type = GDScriptParser::DataType(); + break; + } + StringName real_native = _get_real_class_name(base_type.native_type); + MethodInfo info; + if (ClassDB::get_method_info(real_native, p_context.current_function->identifier->name, &info)) { + for (const List<PropertyInfo>::Element *E = info.arguments.front(); E; E = E->next()) { + if (E->get().name == p_identifier) { + r_type = _type_from_property(E->get()); + return true; } } } - base_type.has_type = false; - } break; - default: { - base_type.has_type = false; + base_type = GDScriptParser::DataType(); } break; + default: + break; } } } - // Check current class (including inheritance) - if (p_context._class) { - GDScriptCompletionIdentifier context_base; - context_base.value = p_context.base; - context_base.type.has_type = true; - context_base.type.kind = GDScriptParser::DataType::CLASS; - context_base.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); - context_base.type.is_meta_type = p_context.function && p_context.function->_static; - - if (_guess_identifier_type_from_base(p_context, context_base, p_identifier, r_type)) { - return true; - } + if (id_type.is_set() && !id_type.is_variant()) { + r_type.type = id_type; + return true; } - // Check named scripts - if (ScriptServer::is_global_class(p_identifier)) { - Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier)); - if (scr.is_valid()) { - r_type = _type_from_variant(scr); - r_type.type.is_meta_type = true; + // Check current class (including inheritance). + if (p_context.current_class) { + GDScriptCompletionIdentifier base; + base.value = p_context.base; + base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + base.type.kind = GDScriptParser::DataType::CLASS; + base.type.class_type = p_context.current_class; + base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; + + if (_guess_identifier_type_from_base(p_context, base, p_identifier, r_type)) { return true; } - return false; } - for (int i = 0; i < 2; i++) { - StringName target_id; - switch (i) { - case 0: - // Check ClassDB - target_id = p_identifier; - break; - case 1: - // ClassDB again for underscore-prefixed classes - target_id = String("_") + p_identifier; - break; - } - - if (ClassDB::class_exists(target_id)) { - r_type.type.has_type = true; - r_type.type.kind = GDScriptParser::DataType::NATIVE; - r_type.type.native_type = target_id; - if (Engine::get_singleton()->has_singleton(target_id)) { - r_type.type.is_meta_type = false; - r_type.value = Engine::get_singleton()->get_singleton_object(target_id); - } else { + // Check global scripts. + if (ScriptServer::is_global_class(p_identifier)) { + String script = ScriptServer::get_global_class_path(p_identifier); + if (script.to_lower().ends_with(".gd")) { + Error err = OK; + Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); + if (err == OK) { + r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_type.type.script_path = script; + r_type.type.class_type = parser->get_parser()->get_tree(); + r_type.type.is_constant = false; + r_type.type.kind = GDScriptParser::DataType::CLASS; + r_type.value = Variant(); + p_context.dependent_parsers.push_back(parser); + return true; + } + } else { + Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier)); + if (scr.is_valid()) { + r_type = _type_from_variant(scr); r_type.type.is_meta_type = true; - const Map<StringName, int>::Element *target_elem = GDScriptLanguage::get_singleton()->get_global_map().find(target_id); - // Check because classes like EditorNode are in ClassDB by now, but unknown to GDScript - if (!target_elem) { - return false; - } - int idx = target_elem->get(); - r_type.value = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + return true; } - return true; } + return false; } - // Check autoload singletons - if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) { + // Check autoloads. + if (ProjectSettings::get_singleton()->has_autoload(p_identifier)) { r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]); return true; } + // Check ClassDB. + StringName class_name = _get_real_class_name(p_identifier); + if (ClassDB::class_exists(class_name) && ClassDB::is_class_exposed(class_name)) { + r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_type.type.kind = GDScriptParser::DataType::NATIVE; + r_type.type.native_type = p_identifier; + r_type.type.is_constant = true; + r_type.type.is_meta_type = !Engine::get_singleton()->has_singleton(class_name); + r_type.value = Variant(); + } + return false; } -static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { +static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { GDScriptParser::DataType base_type = p_base.type; - bool _static = base_type.is_meta_type; - while (base_type.has_type) { + bool is_static = base_type.is_meta_type; + while (base_type.is_set()) { switch (base_type.kind) { - case GDScriptParser::DataType::CLASS: { - if (base_type.class_type->constant_expressions.has(p_identifier)) { - GDScriptParser::ClassNode::Constant c = base_type.class_type->constant_expressions[p_identifier]; - r_type.type = c.type; - if (c.expression->type == GDScriptParser::Node::TYPE_CONSTANT) { - r_type.value = static_cast<const GDScriptParser::ConstantNode *>(c.expression)->value; - } - return true; - } - - if (!_static) { - for (int i = 0; i < base_type.class_type->variables.size(); i++) { - GDScriptParser::ClassNode::Member m = base_type.class_type->variables[i]; - if (m.identifier == p_identifier) { - if (m.expression) { - if (p_context.line == m.expression->line) { - // Variable used in the same expression - return false; - } - if (_guess_expression_type(p_context, m.expression, r_type)) { - return true; - } - if (m.expression->get_datatype().has_type) { - r_type.type = m.expression->get_datatype(); + case GDScriptParser::DataType::CLASS: + if (base_type.class_type->has_member(p_identifier)) { + const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_identifier); + switch (member.type) { + case GDScriptParser::ClassNode::Member::CONSTANT: + r_type.type = member.constant->get_datatype(); + if (member.constant->initializer && member.constant->initializer->is_constant) { + r_type.value = member.constant->initializer->reduced_value; + } + return true; + case GDScriptParser::ClassNode::Member::VARIABLE: + if (!is_static) { + if (member.variable->initializer) { + const GDScriptParser::ExpressionNode *init = member.variable->initializer; + if (init->is_constant) { + r_type.value = init->reduced_value; + r_type = _type_from_variant(init->reduced_value); + return true; + } else if (init->start_line == p_context.current_line) { + return false; + } else if (_guess_expression_type(p_context, init, r_type)) { + return true; + } else if (init->get_datatype().is_set() && !init->get_datatype().is_variant()) { + r_type.type = init->get_datatype(); + return true; + } + } else if (member.variable->get_datatype().is_set() && !member.variable->get_datatype().is_variant()) { + r_type.type = member.variable->get_datatype(); return true; } } - if (m.data_type.has_type) { - r_type.type = m.data_type; - return true; - } + // TODO: Check assignments in constructor. return false; - } - } - } - base_type = base_type.class_type->base_type; - } break; - case GDScriptParser::DataType::GDSCRIPT: { - Ref<GDScript> gds = base_type.script_type; - if (gds.is_valid()) { - if (gds->get_constants().has(p_identifier)) { - r_type = _type_from_variant(gds->get_constants()[p_identifier]); - return true; - } - if (!_static) { - const Set<StringName>::Element *m = gds->get_members().find(p_identifier); - if (m) { - r_type = _type_from_gdtype(gds->get_member_type(p_identifier)); + case GDScriptParser::ClassNode::Member::ENUM: + r_type.type = member.m_enum->get_datatype(); + r_type.enumeration = member.m_enum->identifier->name; return true; - } - } - Ref<GDScript> parent = gds->get_base_script(); - if (parent.is_valid()) { - base_type.script_type = parent; - } else { - base_type.kind = GDScriptParser::DataType::NATIVE; - base_type.native_type = gds->get_instance_base_type(); + case GDScriptParser::ClassNode::Member::ENUM_VALUE: + r_type = _type_from_variant(member.enum_value.value); + return true; + case GDScriptParser::ClassNode::Member::SIGNAL: + r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = Variant::SIGNAL; + return true; + case GDScriptParser::ClassNode::Member::FUNCTION: + if (is_static && !member.function->is_static) { + return false; + } + r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = Variant::CALLABLE; + return true; + case GDScriptParser::ClassNode::Member::CLASS: + r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_type.type.kind = GDScriptParser::DataType::CLASS; + r_type.type.class_type = member.m_class; + return true; + case GDScriptParser::ClassNode::Member::UNDEFINED: + return false; // Unreachable. } - } else { return false; } - } break; + base_type = base_type.class_type->base_type; + break; case GDScriptParser::DataType::SCRIPT: { Ref<Script> scr = base_type.script_type; if (scr.is_valid()) { @@ -1480,7 +1896,7 @@ static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_contex return true; } - if (!_static) { + if (!is_static) { List<PropertyInfo> members; scr->get_script_property_list(&members); for (const List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { @@ -1503,33 +1919,25 @@ static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_contex } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = base_type.native_type; + StringName class_name = _get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { - class_name = String("_") + class_name; - if (!ClassDB::class_exists(class_name)) { - return false; - } + return false; } - // Skip constants since they're all integers. Type does not matter because int has no members + // Skip constants since they're all integers. Type does not matter because int has no members. - List<PropertyInfo> props; - ClassDB::get_property_list(class_name, &props); - for (const List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - const PropertyInfo &prop = E->get(); - if (prop.name == p_identifier) { - StringName getter = ClassDB::get_property_getter(class_name, p_identifier); - if (getter != StringName()) { - MethodBind *g = ClassDB::get_method(class_name, getter); - if (g) { - r_type = _type_from_property(g->get_return_info()); - return true; - } - } else { - r_type = _type_from_property(prop); + PropertyInfo prop; + if (ClassDB::get_property_info(class_name, p_identifier, &prop)) { + StringName getter = ClassDB::get_property_getter(class_name, p_identifier); + if (getter != StringName()) { + MethodBind *g = ClassDB::get_method(class_name, getter); + if (g) { + r_type = _type_from_property(g->get_return_info()); return true; } - break; + } else { + r_type = _type_from_property(prop); + return true; } } return false; @@ -1556,116 +1964,99 @@ static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_contex } break; } } - return false; } -static bool _find_last_return_in_block(const GDScriptCompletionContext &p_context, int &r_last_return_line, const GDScriptParser::Node **r_last_returned_value) { - if (!p_context.block) { - return false; +static void _find_last_return_in_block(GDScriptParser::CompletionContext &p_context, int &r_last_return_line, const GDScriptParser::ExpressionNode **r_last_returned_value) { + if (!p_context.current_suite) { + return; } - for (int i = 0; i < p_context.block->statements.size(); i++) { - if (p_context.block->statements[i]->line < r_last_return_line) { - continue; - } - if (p_context.block->statements[i]->type != GDScriptParser::Node::TYPE_CONTROL_FLOW) { - continue; + for (int i = 0; i < p_context.current_suite->statements.size(); i++) { + if (p_context.current_suite->statements[i]->start_line < r_last_return_line) { + break; } - const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(p_context.block->statements[i]); - if (cf->cf_type == GDScriptParser::ControlFlowNode::CF_RETURN && cf->arguments.size() > 0) { - if (cf->line > r_last_return_line) { - r_last_return_line = cf->line; - *r_last_returned_value = cf->arguments[0]; - } + GDScriptParser::CompletionContext c = p_context; + switch (p_context.current_suite->statements[i]->type) { + case GDScriptParser::Node::FOR: + c.current_suite = static_cast<const GDScriptParser::ForNode *>(p_context.current_suite->statements[i])->loop; + _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); + break; + case GDScriptParser::Node::WHILE: + c.current_suite = static_cast<const GDScriptParser::WhileNode *>(p_context.current_suite->statements[i])->loop; + _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); + break; + case GDScriptParser::Node::IF: { + const GDScriptParser::IfNode *_if = static_cast<const GDScriptParser::IfNode *>(p_context.current_suite->statements[i]); + c.current_suite = _if->true_block; + _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); + if (_if->false_block) { + c.current_suite = _if->false_block; + _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); + } + } break; + case GDScriptParser::Node::MATCH: { + const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(p_context.current_suite->statements[i]); + for (int j = 0; j < match->branches.size(); j++) { + c.current_suite = match->branches[j]->block; + _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); + } + } break; + case GDScriptParser::Node::RETURN: { + const GDScriptParser::ReturnNode *ret = static_cast<const GDScriptParser::ReturnNode *>(p_context.current_suite->statements[i]); + if (ret->return_value) { + if (ret->start_line > r_last_return_line) { + r_last_return_line = ret->start_line; + *r_last_returned_value = ret->return_value; + } + } + } break; + default: + break; } } - - // Recurse into subblocks - for (int i = 0; i < p_context.block->sub_blocks.size(); i++) { - GDScriptCompletionContext c = p_context; - c.block = p_context.block->sub_blocks[i]; - _find_last_return_in_block(c, r_last_return_line, r_last_returned_value); - } - - return false; } -static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) { +static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) { GDScriptParser::DataType base_type = p_base.type; - bool _static = base_type.is_meta_type; + bool is_static = base_type.is_meta_type; - if (_static && p_method == "new") { + if (is_static && p_method == "new") { r_type.type = base_type; r_type.type.is_meta_type = false; r_type.type.is_constant = false; return true; } - while (base_type.has_type) { + while (base_type.is_set() && !base_type.is_variant()) { switch (base_type.kind) { - case GDScriptParser::DataType::CLASS: { - if (!base_type.class_type) { - base_type.has_type = false; - break; - } - - for (int i = 0; i < base_type.class_type->static_functions.size(); i++) { - if (base_type.class_type->static_functions[i]->name == p_method) { + case GDScriptParser::DataType::CLASS: + if (base_type.class_type->has_function(p_method)) { + const GDScriptParser::FunctionNode *method = base_type.class_type->get_member(p_method).function; + if (!is_static || method->is_static) { int last_return_line = -1; - const GDScriptParser::Node *last_returned_value = nullptr; - GDScriptCompletionContext c = p_context; - c._class = base_type.class_type; - c.function = base_type.class_type->static_functions[i]; - c.block = c.function->body; + const GDScriptParser::ExpressionNode *last_returned_value = nullptr; + GDScriptParser::CompletionContext c = p_context; + c.current_class = base_type.class_type; + c.current_function = const_cast<GDScriptParser::FunctionNode *>(method); + c.current_suite = method->body; _find_last_return_in_block(c, last_return_line, &last_returned_value); if (last_returned_value) { - c.line = c.block->end_line; - return _guess_expression_type(c, last_returned_value, r_type); - } - } - } - if (!_static) { - for (int i = 0; i < base_type.class_type->functions.size(); i++) { - if (base_type.class_type->functions[i]->name == p_method) { - int last_return_line = -1; - const GDScriptParser::Node *last_returned_value = nullptr; - GDScriptCompletionContext c = p_context; - c._class = base_type.class_type; - c.function = base_type.class_type->functions[i]; - c.block = c.function->body; - - _find_last_return_in_block(c, last_return_line, &last_returned_value); - if (last_returned_value) { - c.line = c.block->end_line; - return _guess_expression_type(c, last_returned_value, r_type); + c.current_line = c.current_suite->end_line; + if (_guess_expression_type(c, last_returned_value, r_type)) { + return true; + } + if (method->get_datatype().is_set() && !method->get_datatype().is_variant()) { + r_type.type = method->get_datatype(); + return true; } } } } - base_type = base_type.class_type->base_type; - } break; - case GDScriptParser::DataType::GDSCRIPT: { - Ref<GDScript> gds = base_type.script_type; - if (gds.is_valid()) { - if (gds->get_member_functions().has(p_method)) { - r_type = _type_from_gdtype(gds->get_member_functions()[p_method]->get_return_type()); - return true; - } - Ref<GDScript> base_script = gds->get_base_script(); - if (base_script.is_valid()) { - base_type.script_type = base_script; - } else { - base_type.kind = GDScriptParser::DataType::NATIVE; - base_type.native_type = gds->get_instance_base_type(); - } - } else { - return false; - } - } break; + break; case GDScriptParser::DataType::SCRIPT: { Ref<Script> scr = base_type.script_type; if (scr.is_valid()) { @@ -1690,12 +2081,9 @@ static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_con } } break; case GDScriptParser::DataType::NATIVE: { - StringName native = base_type.native_type; + StringName native = _get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(native)) { - native = String("_") + native; - if (!ClassDB::class_exists(native)) { - return false; - } + return false; } MethodBind *mb = ClassDB::get_method(native, p_method); if (mb) { @@ -1728,106 +2116,27 @@ static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_con } } } - return false; -} - -static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx) { - - String arghint = _get_visual_datatype(p_info.return_val, false) + " " + p_info.name + "("; - - int def_args = p_info.arguments.size() - p_info.default_arguments.size(); - int i = 0; - for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E; E = E->next()) { - if (i > 0) { - arghint += ", "; - } - - if (i == p_arg_idx) { - arghint += String::chr(0xFFFF); - } - arghint += E->get().name + ": " + _get_visual_datatype(E->get(), true); - - if (i - def_args >= 0) { - arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string(); - } - - if (i == p_arg_idx) { - arghint += String::chr(0xFFFF); - } - - i++; - } - - if (p_info.flags & METHOD_FLAG_VARARG) { - if (p_info.arguments.size() > 0) { - arghint += ", "; - } - if (p_arg_idx >= p_info.arguments.size()) { - arghint += String::chr(0xFFFF); - } - arghint += "..."; - if (p_arg_idx >= p_info.arguments.size()) { - arghint += String::chr(0xFFFF); - } - } - - arghint += ")"; - - return arghint; -} - -static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) { - - String arghint = p_function->return_type.to_string() + " " + p_function->name.operator String() + "("; - - int def_args = p_function->arguments.size() - p_function->default_values.size(); - for (int i = 0; i < p_function->arguments.size(); i++) { - if (i > 0) { - arghint += ", "; - } - - if (i == p_arg_idx) { - arghint += String::chr(0xFFFF); - } - arghint += p_function->arguments[i].operator String() + ": " + p_function->argument_types[i].to_string(); - - if (i - def_args >= 0) { - String def_val = "<unknown>"; - if (p_function->default_values[i - def_args] && p_function->default_values[i - def_args]->type == GDScriptParser::Node::TYPE_OPERATOR) { - const GDScriptParser::OperatorNode *assign = static_cast<const GDScriptParser::OperatorNode *>(p_function->default_values[i - def_args]); - - if (assign->arguments.size() >= 2) { - if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) { - const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(assign->arguments[1]); - def_val = cn->value.get_construct_string(); - } else if (assign->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->arguments[1]); - def_val = id->name.operator String(); - } - } - } - arghint += " = " + def_val; - } - if (i == p_arg_idx) { - arghint += String::chr(0xFFFF); - } - } - - arghint += ")"; - return arghint; + return false; } -static void _find_enumeration_candidates(const String p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) { - +static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_context, const String &p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) { if (p_enum_hint.find(".") == -1) { - // Global constant + // Global constant or in the current class. StringName current_enum = p_enum_hint; - for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { - if (GlobalConstants::get_global_constant_enum(i) == current_enum) { - ScriptCodeCompletionOption option(GlobalConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM); + if (p_context.current_class && p_context.current_class->has_member(current_enum) && p_context.current_class->get_member(current_enum).type == GDScriptParser::ClassNode::Member::ENUM) { + const GDScriptParser::EnumNode *_enum = p_context.current_class->get_member(current_enum).m_enum; + for (int i = 0; i < _enum->values.size(); i++) { + ScriptCodeCompletionOption option(_enum->values[i].identifier->name, ScriptCodeCompletionOption::KIND_ENUM); r_result.insert(option.display, option); } + } else { + for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { + if (GlobalConstants::get_global_constant_enum(i) == current_enum) { + ScriptCodeCompletionOption option(GlobalConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM); + r_result.insert(option.display, option); + } + } } } else { String class_name = p_enum_hint.get_slice(".", 0); @@ -1847,494 +2156,59 @@ static void _find_enumeration_candidates(const String p_enum_hint, Map<String, S } } -static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Map<String, ScriptCodeCompletionOption> &r_result) { - for (Map<StringName, GDScriptParser::LocalVarNode *>::Element *E = p_context.block->variables.front(); E; E = E->next()) { - if (E->get()->line < p_context.line) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_VARIABLE); - r_result.insert(option.display, option); - } - } - if (p_context.block->parent_block) { - GDScriptCompletionContext c = p_context; - c.block = p_context.block->parent_block; - _find_identifiers_in_block(c, r_result); - } -} - -static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result); - -static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) { - if (!p_parent_only) { - if (!p_static && !p_only_functions) { - for (int i = 0; i < p_context._class->variables.size(); i++) { - ScriptCodeCompletionOption option(p_context._class->variables[i].identifier, ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - - if (!p_only_functions) { - for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_context._class->constant_expressions.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT); - r_result.insert(option.display, option); - } - for (int i = 0; i < p_context._class->subclasses.size(); i++) { - ScriptCodeCompletionOption option(p_context._class->subclasses[i]->name, ScriptCodeCompletionOption::KIND_CLASS); - r_result.insert(option.display, option); - } - } - - for (int i = 0; i < p_context._class->static_functions.size(); i++) { - ScriptCodeCompletionOption option(p_context._class->static_functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); - if (p_context._class->static_functions[i]->arguments.size()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - - if (!p_static) { - for (int i = 0; i < p_context._class->functions.size(); i++) { - ScriptCodeCompletionOption option(p_context._class->functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); - if (p_context._class->functions[i]->arguments.size()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - } - } - - // Parents - GDScriptCompletionIdentifier base_type; - base_type.type = p_context._class->base_type; - base_type.type.is_meta_type = p_static; - base_type.value = p_context.base; - - GDScriptCompletionContext c = p_context; - c.block = nullptr; - c.function = nullptr; - - _find_identifiers_in_base(c, base_type, p_only_functions, r_result); -} - -static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { - GDScriptParser::DataType base_type = p_base.type; - bool _static = base_type.is_meta_type; - - if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) { - ScriptCodeCompletionOption option("new", ScriptCodeCompletionOption::KIND_FUNCTION); - option.insert_text += "("; - r_result.insert(option.display, option); - } - - while (base_type.has_type) { - switch (base_type.kind) { - case GDScriptParser::DataType::CLASS: { - GDScriptCompletionContext c = p_context; - c._class = base_type.class_type; - c.block = nullptr; - c.function = nullptr; - _find_identifiers_in_class(c, _static, p_only_functions, false, r_result); - base_type = base_type.class_type->base_type; - } break; - case GDScriptParser::DataType::GDSCRIPT: { - Ref<GDScript> script = base_type.script_type; - if (script.is_valid()) { - if (!_static && !p_only_functions) { - if (p_context.base && p_context.base->get_script_instance()) { - List<PropertyInfo> members; - p_context.base->get_script_instance()->get_property_list(&members); - for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - if (!p_only_functions) { - for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); - r_result.insert(option.display, option); - } - } - for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { - if (!_static || E->get()->is_static()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); - if (E->get()->get_argument_count()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - } - if (!p_only_functions) { - for (const Map<StringName, Ref<GDScript>>::Element *E = script->get_subclasses().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); - r_result.insert(option.display, option); - } - } - base_type = GDScriptParser::DataType(); - if (script->get_base().is_valid()) { - base_type.has_type = true; - base_type.kind = GDScriptParser::DataType::GDSCRIPT; - base_type.script_type = script->get_base(); - } else { - base_type.has_type = script->get_instance_base_type() != StringName(); - base_type.kind = GDScriptParser::DataType::NATIVE; - base_type.native_type = script->get_instance_base_type(); - } - } else { - return; - } - } break; - case GDScriptParser::DataType::SCRIPT: { - Ref<Script> scr = base_type.script_type; - if (scr.is_valid()) { - if (!_static && !p_only_functions) { - List<PropertyInfo> members; - scr->get_script_property_list(&members); - for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - if (!p_only_functions) { - Map<StringName, Variant> constants; - scr->get_constants(&constants); - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); - r_result.insert(option.display, option); - } - } - - List<MethodInfo> methods; - scr->get_script_method_list(&methods); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); - if (E->get().arguments.size()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - - Ref<Script> base_script = scr->get_base_script(); - if (base_script.is_valid()) { - base_type.script_type = base_script; - } else { - base_type.kind = GDScriptParser::DataType::NATIVE; - base_type.native_type = scr->get_instance_base_type(); - } - } else { - return; - } - } break; - case GDScriptParser::DataType::NATIVE: { - StringName type = base_type.native_type; - if (!ClassDB::class_exists(type)) { - type = String("_") + type; - if (!ClassDB::class_exists(type)) { - return; - } - } - - if (!p_only_functions) { - List<String> constants; - ClassDB::get_integer_constant_list(type, &constants); - for (List<String>::Element *E = constants.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT); - r_result.insert(option.display, option); - } - - if (!_static) { - List<PropertyInfo> pinfo; - ClassDB::get_property_list(type, &pinfo); - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) { - continue; - } - if (E->get().name.find("/") != -1) { - continue; - } - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - } - - if (!_static) { - List<MethodInfo> methods; - bool is_autocompleting_getters = GLOBAL_GET("debug/gdscript/completion/autocomplete_setters_and_getters").booleanize(); - ClassDB::get_method_list(type, &methods, false, !is_autocompleting_getters); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - if (E->get().name.begins_with("_")) { - continue; - } - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); - if (E->get().arguments.size()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - } - - return; - } break; - case GDScriptParser::DataType::BUILTIN: { - Callable::CallError err; - Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err); - if (err.error != Callable::CallError::CALL_OK) { - return; - } - - if (!p_only_functions) { - List<PropertyInfo> members; - p_base.value.get_property_list(&members); - - for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { - if (String(E->get().name).find("/") == -1) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - } - - List<MethodInfo> methods; - tmp.get_method_list(&methods); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); - if (E->get().arguments.size()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - - return; - } break; - default: { - return; - } break; - } - } -} - -static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { - - const GDScriptParser::BlockNode *block = p_context.block; - - if (p_context.function) { - - const GDScriptParser::FunctionNode *f = p_context.function; - - for (int i = 0; i < f->arguments.size(); i++) { - ScriptCodeCompletionOption option(f->arguments[i].operator String(), ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - r_result.insert(option.display, option); - } - } - - if (!p_only_functions && block) { - GDScriptCompletionContext c = p_context; - c.block = block; - _find_identifiers_in_block(c, r_result); - } - - const GDScriptParser::ClassNode *clss = p_context._class; - bool _static = !p_context.function || p_context.function->_static; - - while (clss) { - GDScriptCompletionContext c = p_context; - c._class = clss; - c.block = nullptr; - c.function = nullptr; - _find_identifiers_in_class(c, _static, p_only_functions, false, r_result); - _static = true; - clss = clss->owner; - } - - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - MethodInfo mi = GDScriptFunctions::get_info(GDScriptFunctions::Function(i)); - ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION); - if (mi.arguments.size() || (mi.flags & METHOD_FLAG_VARARG)) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - - static const char *_type_names[Variant::VARIANT_MAX] = { - "null", "bool", "int", "float", "String", "Vector2", "Vector2i", "Rect2", "Rect2i", "Vector3", "Vector3i", "Transform2D", "Plane", "Quat", "AABB", "Basis", "Transform", - "Color", "StringName", "NodePath", "RID", "Object", "Callable", "Signal", "Dictionary", "Array", "PackedByteArray", "PackedInt32Array", "PackedInt64Array", "PackedFloat32Array", "PackedFloat64Array", "PackedStringArray", - "PackedVector2Array", "PackedVector3Array", "PackedColorArray" - }; - - for (int i = 0; i < Variant::VARIANT_MAX; i++) { - ScriptCodeCompletionOption option(_type_names[i], ScriptCodeCompletionOption::KIND_CLASS); - r_result.insert(option.display, option); - } - - static const char *_keywords[] = { - "and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert", - "breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield", - "const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif", - "else", "for", "pass", "return", "match", "while", "remote", "master", "puppet", - "remotesync", "mastersync", "puppetsync", - nullptr - }; - - const char **kw = _keywords; - while (*kw) { - ScriptCodeCompletionOption option(*kw, ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - r_result.insert(option.display, option); - kw++; - } - - // Autoload singletons - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) { - continue; - } - String path = ProjectSettings::get_singleton()->get(s); - if (path.begins_with("*")) { - ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CONSTANT); - r_result.insert(option.display, option); - } - } - - // Named scripts - List<StringName> named_scripts; - ScriptServer::get_global_class_list(&named_scripts); - for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS); - r_result.insert(option.display, option); - } - - // Native classes - for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); - r_result.insert(option.display, option); - } -} - -static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Map<String, ScriptCodeCompletionOption> &r_result, String &r_arghint) { +static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Map<String, ScriptCodeCompletionOption> &r_result, String &r_arghint) { Variant base = p_base.value; GDScriptParser::DataType base_type = p_base.type; const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; - while (base_type.has_type) { + while (base_type.is_set() && !base_type.is_variant()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { - for (int i = 0; i < base_type.class_type->static_functions.size(); i++) { - if (base_type.class_type->static_functions[i]->name == p_method) { - r_arghint = _make_arguments_hint(base_type.class_type->static_functions[i], p_argidx); - return; - } - } - for (int i = 0; i < base_type.class_type->functions.size(); i++) { - if (base_type.class_type->functions[i]->name == p_method) { - r_arghint = _make_arguments_hint(base_type.class_type->functions[i], p_argidx); - return; - } - } + if (base_type.class_type->has_member(p_method)) { + const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_method); - if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) { - for (int i = 0; i < base_type.class_type->_signals.size(); i++) { - ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL); - option.insert_text = quote_style + option.display + quote_style; - r_result.insert(option.display, option); + if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) { + r_arghint = _make_arguments_hint(member.function, p_argidx); + return; } } base_type = base_type.class_type->base_type; } break; - case GDScriptParser::DataType::GDSCRIPT: { - Ref<GDScript> gds = base_type.script_type; - if (gds.is_valid()) { - if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) { - List<MethodInfo> signals; - gds->get_script_signal_list(&signals); - for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL); - option.insert_text = quote_style + option.display + quote_style; - r_result.insert(option.display, option); - } - } - Ref<GDScript> base_script = gds->get_base_script(); - if (base_script.is_valid()) { - base_type.script_type = base_script; - } else { - base_type.kind = GDScriptParser::DataType::NATIVE; - base_type.native_type = gds->get_instance_base_type(); - } - } else { - return; - } - } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = base_type.native_type; + StringName class_name = _get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { - class_name = String("_") + class_name; - if (!ClassDB::class_exists(class_name)) { - base_type.has_type = false; - break; - } + base_type.kind = GDScriptParser::DataType::UNRESOLVED; + break; } - List<MethodInfo> methods; - ClassDB::get_method_list(class_name, &methods); - ClassDB::get_virtual_methods(class_name, &methods); + MethodInfo info; int method_args = 0; - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - if (E->get().name == p_method) { - method_args = E->get().arguments.size(); - if (base.get_type() == Variant::OBJECT) { - Object *obj = base.operator Object *(); - if (obj) { - List<String> options; - obj->get_argument_options(p_method, p_argidx, &options); - for (List<String>::Element *F = options.front(); F; F = F->next()) { - ScriptCodeCompletionOption option(F->get(), ScriptCodeCompletionOption::KIND_FUNCTION); - r_result.insert(option.display, option); - } + if (ClassDB::get_method_info(class_name, p_method, &info)) { + method_args = info.arguments.size(); + if (base.get_type() == Variant::OBJECT) { + Object *obj = base.operator Object *(); + if (obj) { + List<String> options; + obj->get_argument_options(p_method, p_argidx, &options); + for (List<String>::Element *F = options.front(); F; F = F->next()) { + ScriptCodeCompletionOption option(F->get(), ScriptCodeCompletionOption::KIND_FUNCTION); + r_result.insert(option.display, option); } } + } - if (p_argidx < method_args) { - PropertyInfo arg_info = E->get().arguments[p_argidx]; - if (arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { - _find_enumeration_candidates(arg_info.class_name, r_result); - } + if (p_argidx < method_args) { + PropertyInfo arg_info = info.arguments[p_argidx]; + if (arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + _find_enumeration_candidates(p_context, arg_info.class_name, r_result); } - - r_arghint = _make_arguments_hint(E->get(), p_argidx); - break; } - } - if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) { - List<MethodInfo> signals; - ClassDB::get_signal_list(class_name, &signals); - for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL); - option.insert_text = quote_style + option.display + quote_style; - r_result.insert(option.display, option); - } + r_arghint = _make_arguments_hint(info, p_argidx); + return; } if (ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node") && p_argidx == 0) { @@ -2370,7 +2244,7 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con } } - base_type.has_type = false; + base_type.kind = GDScriptParser::DataType::UNRESOLVED; } break; case GDScriptParser::DataType::BUILTIN: { if (base.get_type() == Variant::NIL) { @@ -2390,295 +2264,308 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con } } - base_type.has_type = false; + base_type.kind = GDScriptParser::DataType::UNRESOLVED; } break; default: { - base_type.has_type = false; + base_type.kind = GDScriptParser::DataType::UNRESOLVED; } break; } } } -static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Map<String, ScriptCodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) { - +static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptParser::Node *p_call, int p_argidx, Map<String, ScriptCodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) { const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; - if (!p_node || p_node->type != GDScriptParser::Node::TYPE_OPERATOR) { + if (p_call->type == GDScriptParser::Node::PRELOAD) { + if (p_argidx == 0 && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { + _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result); + } + + MethodInfo mi(PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), "preload", PropertyInfo(Variant::STRING, "path")); + r_arghint = _make_arguments_hint(mi, p_argidx); + return; + } else if (p_call->type != GDScriptParser::Node::CALL) { return; } Variant base; GDScriptParser::DataType base_type; - StringName function; bool _static = false; - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_node); + const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_call); + GDScriptParser::Node::Type callee_type = GDScriptParser::Node::NONE; GDScriptCompletionIdentifier connect_base; - if (op->op != GDScriptParser::OperatorNode::OP_CALL && op->op != GDScriptParser::OperatorNode::OP_PARENT_CALL) { - return; - } - - if (!op->arguments.size()) { - return; - } - - if (op->op == GDScriptParser::OperatorNode::OP_CALL) { - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - // Complete built-in function - const GDScriptParser::BuiltInFunctionNode *fn = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); - MethodInfo mi = GDScriptFunctions::get_info(fn->function); - - if ((mi.name == "load" || mi.name == "preload") && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { - _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result); - } + if (GDScriptParser::get_builtin_function(call->function_name) < GDScriptFunctions::FUNC_MAX) { + MethodInfo info = GDScriptFunctions::get_info(GDScriptParser::get_builtin_function(call->function_name)); - r_arghint = _make_arguments_hint(mi, p_argidx); - return; - - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { - // Complete constructor - const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); - - List<MethodInfo> constructors; - Variant::get_constructor_list(tn->vtype, &constructors); - - int i = 0; - for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) { - if (p_argidx >= E->get().arguments.size()) { - continue; - } - if (i > 0) { - r_arghint += "\n"; - } - r_arghint += _make_arguments_hint(E->get(), p_argidx); - i++; - } - return; - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_SELF) { + if ((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); + } - if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { - return; + r_arghint = _make_arguments_hint(info, p_argidx); + return; + } else if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { + // Complete constructor + List<MethodInfo> constructors; + Variant::get_constructor_list(GDScriptParser::get_builtin_type(call->function_name), &constructors); + + int i = 0; + for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) { + if (p_argidx >= E->get().arguments.size()) { + continue; } - - base = p_context.base; - - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]); - function = id->name; - base_type.has_type = true; - base_type.kind = GDScriptParser::DataType::CLASS; - base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); - _static = p_context.function && p_context.function->_static; - - if (function == "connect" && op->arguments.size() >= 4) { - _guess_expression_type(p_context, op->arguments[3], connect_base); + if (i > 0) { + r_arghint += "\n"; } + r_arghint += _make_arguments_hint(E->get(), p_argidx); + i++; + } + return; + } else if (call->is_super || callee_type == GDScriptParser::Node::IDENTIFIER) { + base = p_context.base; - } else { - if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { - return; - } - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]); - function = id->name; + if (p_context.current_class) { + base_type = p_context.current_class->get_datatype(); + _static = !p_context.current_function || p_context.current_function->is_static; + } + } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) { + const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee); + if (subscript->is_attribute) { GDScriptCompletionIdentifier ci; - if (_guess_expression_type(p_context, op->arguments[0], ci)) { + if (_guess_expression_type(p_context, subscript->base, ci)) { base_type = ci.type; base = ci.value; } else { return; } - _static = ci.type.is_meta_type; - if (function == "connect" && op->arguments.size() >= 4) { - _guess_expression_type(p_context, op->arguments[3], connect_base); - } + _static = base_type.is_meta_type; } } else { - if (!p_context._class || op->arguments.size() < 1 || op->arguments[0]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { - return; - } - base_type.has_type = true; - base_type.kind = GDScriptParser::DataType::CLASS; - base_type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); - base_type.is_meta_type = p_context.function && p_context.function->_static; - base = p_context.base; - - function = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name; - - if (function == "connect" && op->arguments.size() >= 4) { - _guess_expression_type(p_context, op->arguments[3], connect_base); - } + return; } GDScriptCompletionIdentifier ci; ci.type = base_type; ci.value = base; - _find_call_arguments(p_context, ci, function, p_argidx, _static, r_result, r_arghint); - - if (function == "connect" && p_argidx == 2) { - Map<String, ScriptCodeCompletionOption> methods; - _find_identifiers_in_base(p_context, connect_base, true, methods); - for (Map<String, ScriptCodeCompletionOption>::Element *E = methods.front(); E; E = E->next()) { - ScriptCodeCompletionOption &option = E->value(); - option.insert_text = quote_style + option.display + quote_style; - r_result.insert(option.display, option); - } - } + _find_call_arguments(p_context, ci, call->function_name, p_argidx, _static, r_result, r_arghint); r_forced = r_result.size() > 0; } Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; GDScriptParser parser; + GDScriptAnalyzer analyzer(&parser); + + parser.parse(p_code, p_path, true); + analyzer.analyze(); - parser.parse(p_code, p_path.get_base_dir(), false, p_path, true); r_forced = false; Map<String, ScriptCodeCompletionOption> options; - GDScriptCompletionContext context; - context._class = parser.get_completion_class(); - context.block = parser.get_completion_block(); - context.function = parser.get_completion_function(); - context.line = parser.get_completion_line(); - - if (!context._class || context._class->owner == nullptr) { - context.base = p_owner; - context.base_path = p_path.get_base_dir(); - } + GDScriptParser::CompletionContext completion_context = parser.get_completion_context(); + completion_context.base = p_owner; bool is_function = false; - switch (parser.get_completion_type()) { - case GDScriptParser::COMPLETION_NONE: { + switch (completion_context.type) { + case GDScriptParser::COMPLETION_NONE: + break; + case GDScriptParser::COMPLETION_ANNOTATION: { + List<MethodInfo> annotations; + parser.get_annotation_list(&annotations); + for (const List<MethodInfo>::Element *E = annotations.front(); E != nullptr; E = E->next()) { + ScriptCodeCompletionOption option(E->get().name.substr(1), ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + if (E->get().arguments.size() > 0) { + option.insert_text += "("; + } + options.insert(option.display, option); + } + r_forced = true; + } break; + case GDScriptParser::COMPLETION_ANNOTATION_ARGUMENTS: { + if (completion_context.node == nullptr || completion_context.node->type != GDScriptParser::Node::ANNOTATION) { + break; + } + const GDScriptParser::AnnotationNode *annotation = static_cast<const GDScriptParser::AnnotationNode *>(completion_context.node); + _find_annotation_arguments(annotation, completion_context.current_argument, quote_style, options); + r_forced = true; } break; case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { List<StringName> constants; - Variant::get_constants_for_type(parser.get_completion_built_in_constant(), &constants); - for (List<StringName>::Element *E = constants.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + Variant::get_constants_for_type(completion_context.builtin_type, &constants); + for (const List<StringName>::Element *E = constants.front(); E != nullptr; E = E->next()) { + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT); options.insert(option.display, option); } } break; - case GDScriptParser::COMPLETION_PARENT_FUNCTION: { - _find_identifiers_in_class(context, !context.function || context.function->_static, true, true, options); + case GDScriptParser::COMPLETION_INHERIT_TYPE: { + _list_available_types(true, completion_context, options); + r_forced = true; } break; - case GDScriptParser::COMPLETION_FUNCTION: { - is_function = true; - [[fallthrough]]; + case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID: { + ScriptCodeCompletionOption option("void", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + options.insert(option.display, option); } - case GDScriptParser::COMPLETION_IDENTIFIER: { - _find_identifiers(context, is_function, options); + [[fallthrough]]; + case GDScriptParser::COMPLETION_TYPE_NAME: { + _list_available_types(false, completion_context, options); + r_forced = true; } break; - case GDScriptParser::COMPLETION_GET_NODE: { - if (p_owner) { - List<String> opts; - p_owner->get_argument_options("get_node", 0, &opts); - - for (List<String>::Element *E = opts.front(); E; E = E->next()) { - - String opt = E->get().strip_edges(); - if (opt.is_quoted()) { - r_forced = true; - String idopt = opt.unquote(); - if (idopt.replace("/", "_").is_valid_identifier()) { - ScriptCodeCompletionOption option(idopt, ScriptCodeCompletionOption::KIND_NODE_PATH); - options.insert(option.display, option); - } else { - ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH); - options.insert(option.display, option); - } - } + case GDScriptParser::COMPLETION_PROPERTY_DECLARATION_OR_TYPE: { + _list_available_types(false, completion_context, options); + ScriptCodeCompletionOption get("get", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + options.insert(get.display, get); + ScriptCodeCompletionOption set("set", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + options.insert(set.display, set); + r_forced = true; + } break; + case GDScriptParser::COMPLETION_PROPERTY_DECLARATION: { + ScriptCodeCompletionOption get("get", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + options.insert(get.display, get); + ScriptCodeCompletionOption set("set", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + options.insert(set.display, set); + r_forced = true; + } break; + case GDScriptParser::COMPLETION_PROPERTY_METHOD: { + if (!completion_context.current_class) { + break; + } + for (int i = 0; i < completion_context.current_class->members.size(); i++) { + const GDScriptParser::ClassNode::Member &member = completion_context.current_class->members[i]; + if (member.type != GDScriptParser::ClassNode::Member::FUNCTION) { + continue; } - - // Get autoloads - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) { - continue; - } - String name = s.get_slice("/", 1); - ScriptCodeCompletionOption option(quote_style + "/root/" + name + quote_style, ScriptCodeCompletionOption::KIND_NODE_PATH); - options.insert(option.display, option); + if (member.function->is_static) { + continue; } + ScriptCodeCompletionOption option(member.function->identifier->name, ScriptCodeCompletionOption::KIND_FUNCTION); + options.insert(option.display, option); } + r_forced = true; } break; - case GDScriptParser::COMPLETION_METHOD: { - is_function = true; - [[fallthrough]]; - } - case GDScriptParser::COMPLETION_INDEX: { - const GDScriptParser::Node *node = parser.get_completion_node(); - if (node->type != GDScriptParser::Node::TYPE_OPERATOR) { + case GDScriptParser::COMPLETION_ASSIGN: { + GDScriptCompletionIdentifier type; + if (!completion_context.node || completion_context.node->type != GDScriptParser::Node::ASSIGNMENT) { break; } - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(node); - if (op->arguments.size() < 1) { + if (!_guess_expression_type(completion_context, static_cast<const GDScriptParser::AssignmentNode *>(completion_context.node)->assignee, type)) { + _find_identifiers(completion_context, false, options); + r_forced = true; break; } + if (!type.enumeration.empty()) { + _find_enumeration_candidates(completion_context, type.enumeration, options); + r_forced = options.size() > 0; + } else { + _find_identifiers(completion_context, false, options); + r_forced = true; + } + } break; + case GDScriptParser::COMPLETION_METHOD: + is_function = true; + [[fallthrough]]; + case GDScriptParser::COMPLETION_IDENTIFIER: { + _find_identifiers(completion_context, is_function, options); + } break; + case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD: + is_function = true; + [[fallthrough]]; + case GDScriptParser::COMPLETION_ATTRIBUTE: { + r_forced = true; + const GDScriptParser::SubscriptNode *attr = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); + if (attr->base) { + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(completion_context, attr->base, base)) { + break; + } + + _find_identifiers_in_base(base, is_function, options); + } + } break; + case GDScriptParser::COMPLETION_SUBSCRIPT: { + const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); GDScriptCompletionIdentifier base; - if (!_guess_expression_type(context, op->arguments[0], base)) { + if (!_guess_expression_type(completion_context, subscript->base, base)) { break; } - GDScriptCompletionContext c = context; - c.function = nullptr; - c.block = nullptr; + GDScriptParser::CompletionContext c = completion_context; + c.current_function = nullptr; + c.current_suite = nullptr; c.base = base.value.get_type() == Variant::OBJECT ? base.value.operator Object *() : nullptr; if (base.type.kind == GDScriptParser::DataType::CLASS) { - c._class = base.type.class_type; + c.current_class = base.type.class_type; } else { - c._class = nullptr; + c.current_class = nullptr; + } + + _find_identifiers_in_base(base, false, options); + } break; + case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: { + if (!completion_context.current_class) { + break; + } + const GDScriptParser::TypeNode *type = static_cast<const GDScriptParser::TypeNode *>(completion_context.node); + bool found = true; + GDScriptCompletionIdentifier base; + base.type.kind = GDScriptParser::DataType::CLASS; + base.type.type_source = GDScriptParser::DataType::INFERRED; + base.type.is_constant = true; + base.type.class_type = completion_context.current_class; + base.value = completion_context.base; + + for (int i = 0; i < completion_context.current_argument; i++) { + GDScriptCompletionIdentifier ci; + if (!_guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci)) { + found = false; + break; + } + base = ci; } - _find_identifiers_in_base(c, base, is_function, options); + // TODO: Improve this to only list types. + if (found) { + _find_identifiers_in_base(base, false, options); + } + r_forced = true; + } break; + case GDScriptParser::COMPLETION_RESOURCE_PATH: { + if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) { + _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options); + r_forced = true; + } } break; case GDScriptParser::COMPLETION_CALL_ARGUMENTS: { - _find_call_arguments(context, parser.get_completion_node(), parser.get_completion_argument_index(), options, r_forced, r_call_hint); + if (!completion_context.node) { + break; + } + _find_call_arguments(completion_context, completion_context.node, completion_context.current_argument, options, r_forced, r_call_hint); } break; - case GDScriptParser::COMPLETION_VIRTUAL_FUNC: { - GDScriptParser::DataType native_type = context._class->base_type; - while (native_type.has_type && native_type.kind != GDScriptParser::DataType::NATIVE) { + case GDScriptParser::COMPLETION_OVERRIDE_METHOD: { + GDScriptParser::DataType native_type = completion_context.current_class->base_type; + while (native_type.is_set() && native_type.kind != GDScriptParser::DataType::NATIVE) { switch (native_type.kind) { case GDScriptParser::DataType::CLASS: { native_type = native_type.class_type->base_type; } break; - case GDScriptParser::DataType::GDSCRIPT: { - Ref<GDScript> gds = native_type.script_type; - if (gds.is_valid()) { - Ref<GDScript> base = gds->get_base_script(); - if (base.is_valid()) { - native_type.script_type = base; - } else { - native_type.native_type = gds->get_instance_base_type(); - native_type.kind = GDScriptParser::DataType::NATIVE; - } - } else { - native_type.has_type = false; - } - } break; default: { - native_type.has_type = false; + native_type.kind = GDScriptParser::DataType::UNRESOLVED; } break; } } - if (!native_type.has_type) { + if (!native_type.is_set()) { break; } - StringName class_name = native_type.native_type; + StringName class_name = _get_real_class_name(native_type.native_type); if (!ClassDB::class_exists(class_name)) { - class_name = String("_") + class_name; - if (!ClassDB::class_exists(class_name)) { - break; - } + break; } bool use_type_hint = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints").operator bool(); @@ -2686,7 +2573,6 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path List<MethodInfo> virtual_methods; ClassDB::get_virtual_methods(class_name, &virtual_methods); for (List<MethodInfo>::Element *E = virtual_methods.front(); E; E = E->next()) { - MethodInfo &mi = E->get(); String method_hint = mi.name; if (method_hint.find(":") != -1) { @@ -2731,245 +2617,41 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path options.insert(option.display, option); } } break; - case GDScriptParser::COMPLETION_YIELD: { - const GDScriptParser::Node *node = parser.get_completion_node(); - - GDScriptCompletionContext c = context; - c.line = node->line; - GDScriptCompletionIdentifier type; - if (!_guess_expression_type(c, node, type)) { - break; - } + case GDScriptParser::COMPLETION_GET_NODE: { + if (p_owner) { + List<String> opts; + p_owner->get_argument_options("get_node", 0, &opts); - GDScriptParser::DataType base_type = type.type; - while (base_type.has_type) { - switch (base_type.kind) { - case GDScriptParser::DataType::CLASS: { - for (int i = 0; i < base_type.class_type->_signals.size(); i++) { - ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL); - option.insert_text = quote_style + option.display + quote_style; + for (List<String>::Element *E = opts.front(); E; E = E->next()) { + String opt = E->get().strip_edges(); + if (opt.is_quoted()) { + r_forced = true; + String idopt = opt.unquote(); + if (idopt.replace("/", "_").is_valid_identifier()) { + ScriptCodeCompletionOption option(idopt, ScriptCodeCompletionOption::KIND_NODE_PATH); options.insert(option.display, option); - } - base_type = base_type.class_type->base_type; - } break; - case GDScriptParser::DataType::SCRIPT: - case GDScriptParser::DataType::GDSCRIPT: { - Ref<Script> scr = base_type.script_type; - if (scr.is_valid()) { - List<MethodInfo> signals; - scr->get_script_signal_list(&signals); - for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL); - options.insert(option.display, option); - } - Ref<Script> base_script = scr->get_base_script(); - if (base_script.is_valid()) { - base_type.script_type = base_script; - } else { - base_type.kind = GDScriptParser::DataType::NATIVE; - base_type.native_type = scr->get_instance_base_type(); - } } else { - base_type.has_type = false; - } - } break; - case GDScriptParser::DataType::NATIVE: { - base_type.has_type = false; - - StringName class_name = base_type.native_type; - if (!ClassDB::class_exists(class_name)) { - class_name = String("_") + class_name; - if (!ClassDB::class_exists(class_name)) { - break; - } - } - - List<MethodInfo> signals; - ClassDB::get_signal_list(class_name, &signals); - for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL); + ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH); options.insert(option.display, option); } - } break; - default: { - base_type.has_type = false; } } - } - } break; - case GDScriptParser::COMPLETION_RESOURCE_PATH: { - if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) { - _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options); - r_forced = true; - } - } break; - case GDScriptParser::COMPLETION_ASSIGN: { - GDScriptCompletionIdentifier type; - if (!_guess_expression_type(context, parser.get_completion_node(), type)) { - break; - } - if (!type.enumeration.empty()) { - _find_enumeration_candidates(type.enumeration, options); - r_forced = options.size() > 0; - } - } break; - case GDScriptParser::COMPLETION_TYPE_HINT: { - const GDScriptParser::ClassNode *clss = context._class; - while (clss) { - for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = clss->constant_expressions.front(); E; E = E->next()) { - GDScriptCompletionIdentifier constant; - GDScriptCompletionContext c = context; - c.function = nullptr; - c.block = nullptr; - c.line = E->value().expression->line; - if (_guess_expression_type(c, E->value().expression, constant)) { - if (constant.type.has_type && constant.type.is_meta_type) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); - options.insert(option.display, option); - } - } - } - for (int i = 0; i < clss->subclasses.size(); i++) { - if (clss->subclasses[i]->name != StringName()) { - ScriptCodeCompletionOption option(clss->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS); - options.insert(option.display, option); - } - } - clss = clss->owner; - for (int i = 0; i < Variant::VARIANT_MAX; i++) { - ScriptCodeCompletionOption option(Variant::get_type_name((Variant::Type)i), ScriptCodeCompletionOption::KIND_CLASS); - options.insert(option.display, option); - } - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) { - continue; - } - ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CLASS); - options.insert(option.display, option); - } - } + // Get autoloads. + Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - List<StringName> native_classes; - ClassDB::get_class_list(&native_classes); - for (List<StringName>::Element *E = native_classes.front(); E; E = E->next()) { - String class_name = E->get().operator String(); - if (class_name.begins_with("_")) { - class_name = class_name.right(1); - } - if (Engine::get_singleton()->has_singleton(class_name)) { - continue; + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + String name = E->key(); + ScriptCodeCompletionOption option(quote_style + "/root/" + name + quote_style, ScriptCodeCompletionOption::KIND_NODE_PATH); + options.insert(option.display, option); } - ScriptCodeCompletionOption option(class_name, ScriptCodeCompletionOption::KIND_CLASS); - options.insert(option.display, option); } - - // Named scripts - List<StringName> named_scripts; - ScriptServer::get_global_class_list(&named_scripts); - for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS); - options.insert(option.display, option); - } - - if (parser.get_completion_identifier_is_function()) { - ScriptCodeCompletionOption option("void", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - options.insert(option.display, option); - } - r_forced = true; } break; - case GDScriptParser::COMPLETION_TYPE_HINT_INDEX: { - GDScriptCompletionIdentifier base; - String index = parser.get_completion_cursor().operator String(); - if (!_guess_identifier_type(context, index.get_slice(".", 0), base)) { - break; - } - - GDScriptCompletionContext c = context; - c._class = nullptr; - c.function = nullptr; - c.block = nullptr; - bool finding = true; - index = index.right(index.find(".") + 1); - while (index.find(".") != -1) { - String id = index.get_slice(".", 0); - - GDScriptCompletionIdentifier sub_base; - if (!_guess_identifier_type_from_base(c, base, id, sub_base)) { - finding = false; - break; - } - index = index.right(index.find(".") + 1); - base = sub_base; - } - - if (!finding) { + case GDScriptParser::COMPLETION_SUPER_METHOD: { + if (!completion_context.current_class) { break; } - - GDScriptParser::DataType base_type = base.type; - while (base_type.has_type) { - switch (base_type.kind) { - case GDScriptParser::DataType::CLASS: { - if (base_type.class_type) { - for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = base_type.class_type->constant_expressions.front(); E; E = E->next()) { - GDScriptCompletionIdentifier constant; - GDScriptCompletionContext c2 = context; - c2._class = base_type.class_type; - c2.function = nullptr; - c2.block = nullptr; - c2.line = E->value().expression->line; - if (_guess_expression_type(c2, E->value().expression, constant)) { - if (constant.type.has_type && constant.type.is_meta_type) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); - options.insert(option.display, option); - } - } - } - for (int i = 0; i < base_type.class_type->subclasses.size(); i++) { - if (base_type.class_type->subclasses[i]->name != StringName()) { - ScriptCodeCompletionOption option(base_type.class_type->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS); - options.insert(option.display, option); - } - } - - base_type = base_type.class_type->base_type; - } else { - base_type.has_type = false; - } - } break; - case GDScriptParser::DataType::SCRIPT: - case GDScriptParser::DataType::GDSCRIPT: { - Ref<Script> scr = base_type.script_type; - if (scr.is_valid()) { - Map<StringName, Variant> constants; - scr->get_constants(&constants); - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - Ref<Script> const_scr = E->value(); - if (const_scr.is_valid()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); - options.insert(option.display, option); - } - } - Ref<Script> base_script = scr->get_base_script(); - if (base_script.is_valid()) { - base_type.script_type = base_script; - } else { - base_type.has_type = false; - } - } else { - base_type.has_type = false; - } - } break; - default: { - base_type.has_type = false; - } break; - } - } - r_forced = options.size() > 0; + _find_identifiers_in_class(completion_context.current_class, true, false, true, options); } break; } @@ -3010,19 +2692,16 @@ String GDScriptLanguage::_get_indentation() const { } void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_to_line) const { - String indent = _get_indentation(); Vector<String> lines = p_code.split("\n"); List<int> indent_stack; for (int i = 0; i < lines.size(); i++) { - String l = lines[i]; int tc = 0; for (int j = 0; j < l.length(); j++) { if (l[j] == ' ' || l[j] == '\t') { - tc++; } else { break; @@ -3030,8 +2709,9 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t } String st = l.substr(tc, l.length()).strip_edges(); - if (st == "" || st.begins_with("#")) + if (st == "" || st.begins_with("#")) { continue; //ignore! + } int ilevel = 0; if (indent_stack.size()) { @@ -3045,12 +2725,12 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t indent_stack.pop_back(); } - if (indent_stack.size() && indent_stack.back()->get() != tc) + if (indent_stack.size() && indent_stack.back()->get() != tc) { indent_stack.push_back(tc); //this is not right but gets the job done + } } if (i >= p_from_line) { - l = ""; for (int j = 0; j < indent_stack.size(); j++) { l += indent; @@ -3066,8 +2746,9 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t p_code = ""; for (int i = 0; i < lines.size(); i++) { - if (i > 0) + if (i > 0) { p_code += "\n"; + } p_code += lines[i]; } } @@ -3077,45 +2758,19 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, const String &p_symbol, bool p_is_function, GDScriptLanguage::LookupResult &r_result) { GDScriptParser::DataType base_type = p_base; - while (base_type.has_type) { + while (base_type.is_set()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { if (base_type.class_type) { - if (p_is_function) { - for (int i = 0; i < base_type.class_type->functions.size(); i++) { - if (base_type.class_type->functions[i]->name == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = base_type.class_type->functions[i]->line; - return OK; - } - } - for (int i = 0; i < base_type.class_type->static_functions.size(); i++) { - if (base_type.class_type->static_functions[i]->name == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = base_type.class_type->static_functions[i]->line; - return OK; - } - } - } else { - if (base_type.class_type->constant_expressions.has(p_symbol)) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = base_type.class_type->constant_expressions[p_symbol].expression->line; - return OK; - } - - for (int i = 0; i < base_type.class_type->variables.size(); i++) { - if (base_type.class_type->variables[i].identifier == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = base_type.class_type->variables[i].line; - return OK; - } - } + 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; } } break; - case GDScriptParser::DataType::SCRIPT: - case GDScriptParser::DataType::GDSCRIPT: { + case GDScriptParser::DataType::SCRIPT: { Ref<Script> scr = base_type.script_type; if (scr.is_valid()) { int line = scr->get_member_line(p_symbol); @@ -3133,17 +2788,14 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co base_type.native_type = scr->get_instance_base_type(); } } else { - base_type.has_type = false; + base_type.kind = GDScriptParser::DataType::UNRESOLVED; } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = base_type.native_type; + StringName class_name = _get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { - class_name = String("_") + class_name; - if (!ClassDB::class_exists(class_name)) { - base_type.has_type = false; - break; - } + base_type.kind = GDScriptParser::DataType::UNRESOLVED; + break; } if (ClassDB::has_method(class_name, p_symbol, true)) { @@ -3183,15 +2835,11 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } - List<PropertyInfo> properties; - ClassDB::get_property_list(class_name, &properties, true); - for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { - if (E->get().name == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; - r_result.class_name = base_type.native_type; - r_result.class_member = p_symbol; - return OK; - } + if (ClassDB::has_property(class_name, p_symbol, true)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; + r_result.class_name = base_type.native_type; + r_result.class_member = p_symbol; + return OK; } StringName parent = ClassDB::get_parent_class(class_name); @@ -3202,11 +2850,11 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co base_type.native_type = parent; } } else { - base_type.has_type = false; + base_type.kind = GDScriptParser::DataType::UNRESOLVED; } } break; case GDScriptParser::DataType::BUILTIN: { - base_type.has_type = false; + base_type.kind = GDScriptParser::DataType::UNRESOLVED; if (Variant::has_constant(base_type.builtin_type, p_symbol)) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; @@ -3222,7 +2870,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co v = v_ref; } else { Callable::CallError err; - v = Variant::construct(base_type.builtin_type, nullptr, 0, err); + v = Variant::construct(base_type.builtin_type, NULL, 0, err); if (err.error != Callable::CallError::CALL_OK) { break; } @@ -3245,7 +2893,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } break; default: { - base_type.has_type = false; + base_type.kind = GDScriptParser::DataType::UNRESOLVED; } break; } } @@ -3254,7 +2902,6 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { - //before parsing, try the usual stuff if (ClassDB::class_exists(p_symbol)) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; @@ -3295,26 +2942,18 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } GDScriptParser parser; - parser.parse(p_code, p_path.get_base_dir(), false, p_path, true); + parser.parse(p_code, p_path, true); + GDScriptAnalyzer analyzer(&parser); + analyzer.analyze(); - if (parser.get_completion_type() == GDScriptParser::COMPLETION_NONE) { - return ERR_CANT_RESOLVE; - } - - GDScriptCompletionContext context; - context._class = parser.get_completion_class(); - context.function = parser.get_completion_function(); - context.block = parser.get_completion_block(); - context.line = parser.get_completion_line(); - context.base = p_owner; - context.base_path = p_path.get_base_dir(); + GDScriptParser::CompletionContext context = parser.get_completion_context(); - if (context._class && context._class->extends_class.size() > 0) { + if (context.current_class && context.current_class->extends.size() > 0) { bool success = false; - ClassDB::get_integer_constant(context._class->extends_class[0], p_symbol, &success); + ClassDB::get_integer_constant(context.current_class->extends[0], p_symbol, &success); if (success) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; - r_result.class_name = context._class->extends_class[0]; + r_result.class_name = context.current_class->extends[0]; r_result.class_member = p_symbol; return OK; } @@ -3322,58 +2961,40 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol bool is_function = false; - switch (parser.get_completion_type()) { + switch (context.type) { case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; - r_result.class_name = Variant::get_type_name(parser.get_completion_built_in_constant()); + r_result.class_name = Variant::get_type_name(context.builtin_type); r_result.class_member = p_symbol; return OK; } break; - case GDScriptParser::COMPLETION_PARENT_FUNCTION: - case GDScriptParser::COMPLETION_FUNCTION: { + case GDScriptParser::COMPLETION_SUPER_METHOD: + case GDScriptParser::COMPLETION_METHOD: { is_function = true; [[fallthrough]]; } case GDScriptParser::COMPLETION_IDENTIFIER: { - - if (!is_function) { - is_function = parser.get_completion_identifier_is_function(); - } - GDScriptParser::DataType base_type; - if (context._class) { - if (parser.get_completion_type() != GDScriptParser::COMPLETION_PARENT_FUNCTION) { - base_type.has_type = true; - base_type.kind = GDScriptParser::DataType::CLASS; - base_type.class_type = const_cast<GDScriptParser::ClassNode *>(context._class); + if (context.current_class) { + if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { + base_type = context.current_class->get_datatype(); } else { - base_type = context._class->base_type; + base_type = context.current_class->base_type; } } else { break; } - if (!is_function && context.block) { - // Lookup local variables - const GDScriptParser::BlockNode *block = context.block; - while (block) { - if (block->variables.has(p_symbol)) { - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = block->variables[p_symbol]->line; - return OK; - } - block = block->parent_block; - } - } - - if (context.function && context.function->name != StringName()) { - // Lookup function arguments - for (int i = 0; i < context.function->arguments.size(); i++) { - if (context.function->arguments[i] == p_symbol) { + if (!is_function && context.current_suite) { + // Lookup local variables. + const GDScriptParser::SuiteNode *suite = context.current_suite; + while (suite) { + if (suite->has_local(p_symbol)) { r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = context.function->line; + r_result.location = suite->get_local(p_symbol).start_line; return OK; } + suite = suite->parent_block; } } @@ -3382,40 +3003,27 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } if (!is_function) { - // Guess in autoloads as singletons - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - - String s = E->get().name; - if (!s.begins_with("autoload/")) - continue; - String name = s.get_slice("/", 1); - if (name == String(p_symbol)) { - - String path = ProjectSettings::get_singleton()->get(s); - if (path.begins_with("*")) { - String script = path.substr(1, path.length()); - - if (!script.ends_with(".gd")) { - // Not a script, try find the script anyway, - // may have some success - script = script.get_basename() + ".gd"; - } - - if (FileAccess::exists(script)) { + // Guess in autoloads as singletons. + if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) { + const ProjectSettings::AutoloadInfo &singleton = ProjectSettings::get_singleton()->get_autoload(p_symbol); + if (singleton.is_singleton) { + String script = singleton.path; + if (!script.ends_with(".gd")) { + // Not a script, try find the script anyway, + // may have some success. + script = script.get_basename() + ".gd"; + } - r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; - r_result.location = 0; - r_result.script = ResourceLoader::load(script); - return OK; - } + if (FileAccess::exists(script)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = 0; + r_result.script = ResourceLoader::load(script); + return OK; } } } - // Global + // Global. Map<StringName, int> classes = GDScriptLanguage::get_singleton()->get_global_map(); if (classes.has(p_symbol)) { Variant value = GDScriptLanguage::get_singleton()->get_global_array()[classes[p_symbol]]; @@ -3442,7 +3050,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol // We cannot determine the exact nature of the identifier here // Otherwise these codes would work StringName enumName = ClassDB::get_integer_constant_enum("@GlobalScope", p_symbol, true); - if (enumName != nullptr) { + if (enumName != NULL) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM; r_result.class_name = "@GlobalScope"; r_result.class_member = enumName; @@ -3462,17 +3070,20 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } } } break; - case GDScriptParser::COMPLETION_METHOD: { + case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD: { is_function = true; [[fallthrough]]; } - case GDScriptParser::COMPLETION_INDEX: { - const GDScriptParser::Node *node = parser.get_completion_node(); - if (node->type != GDScriptParser::Node::TYPE_OPERATOR) { + case GDScriptParser::COMPLETION_ATTRIBUTE: { + if (context.node->type != GDScriptParser::Node::SUBSCRIPT) { + break; + } + const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(context.node); + if (!subscript->is_attribute) { break; } GDScriptCompletionIdentifier base; - if (!_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], base)) { + if (!_guess_expression_type(context, subscript->base, base)) { break; } @@ -3480,13 +3091,21 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol return OK; } } break; - case GDScriptParser::COMPLETION_VIRTUAL_FUNC: { - GDScriptParser::DataType base_type = context._class->base_type; + case GDScriptParser::COMPLETION_OVERRIDE_METHOD: { + GDScriptParser::DataType base_type = context.current_class->base_type; if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) { return OK; } } break; + case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID: + case GDScriptParser::COMPLETION_TYPE_NAME: { + GDScriptParser::DataType base_type = context.current_class->get_datatype(); + + if (_lookup_symbol_from_base(base_type, p_symbol, false, r_result) == OK) { + return OK; + } + } break; default: { } } diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index ca4d6f6de9..a4e37a79f8 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -35,12 +35,10 @@ #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)) { @@ -51,7 +49,6 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta return &self; } break; case ADDR_TYPE_CLASS: { - return &static_ref; } break; case ADDR_TYPE_MEMBER: { @@ -65,7 +62,6 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta return &p_instance->members.write[address]; } break; case ADDR_TYPE_CLASS_CONSTANT: { - //todo change to index! GDScript *s = p_script; #ifdef DEBUG_ENABLED @@ -76,7 +72,6 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta while (s) { GDScript *o = s; while (o) { - Map<StringName, Variant>::Element *E = o->constants.find(*sn); if (E) { return &E->get(); @@ -133,7 +128,6 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta #ifdef DEBUG_ENABLED static String _get_var_type(const Variant *p_var) { - String basestr; if (p_var->get_type() == Variant::OBJECT) { @@ -146,10 +140,11 @@ static String _get_var_type(const Variant *p_var) { basestr = "previously freed"; } } else { - if (bobj->get_script_instance()) + if (bobj->get_script_instance()) { basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")"; - else + } else { basestr = bobj->get_class(); + } } } else { @@ -161,7 +156,6 @@ static String _get_var_type(const Variant *p_var) { #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) { @@ -216,12 +210,12 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_CONSTRUCT_DICTIONARY, \ &&OPCODE_CALL, \ &&OPCODE_CALL_RETURN, \ + &&OPCODE_CALL_ASYNC, \ &&OPCODE_CALL_BUILT_IN, \ &&OPCODE_CALL_SELF, \ &&OPCODE_CALL_SELF_BASE, \ - &&OPCODE_YIELD, \ - &&OPCODE_YIELD_SIGNAL, \ - &&OPCODE_YIELD_RESUME, \ + &&OPCODE_AWAIT, \ + &&OPCODE_AWAIT_RESUME, \ &&OPCODE_JUMP, \ &&OPCODE_JUMP_IF, \ &&OPCODE_JUMP_IF_NOT, \ @@ -233,7 +227,8 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_BREAKPOINT, \ &&OPCODE_LINE, \ &&OPCODE_END \ - }; + }; \ + static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum."); #define OPCODE(m_op) \ m_op: @@ -259,11 +254,9 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const #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(); } @@ -288,35 +281,29 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a int line = _initial_line; if (p_state) { - //use existing (supplied) state (yielded) + //use existing (supplied) state (awaited) stack = (Variant *)p_state->stack.ptr(); call_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check line = p_state->line; ip = p_state->ip; alloca_size = p_state->stack.size(); - script = p_state->script.ptr(); + script = p_state->script; p_instance = p_state->instance; defarg = p_state->defarg; self = p_state->self; - //stack[p_state->result_pos]=p_state->result; //assign stack with result } 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; } } @@ -324,11 +311,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a 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) { @@ -337,15 +322,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } if (!argument_types[i].is_type(*p_args[i], true)) { - if (argument_types[i].is_type(Variant(), true)) { - memnew_placement(&stack[i], Variant); - continue; - } else { - 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(); - } + 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); @@ -362,10 +342,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } if (_call_size) { - call_args = (Variant **)&aptr[sizeof(Variant) * _stack_size]; } else { - call_args = nullptr; } @@ -376,7 +354,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a 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; @@ -387,12 +364,15 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } + static_ref = script; + String err_text; #ifdef DEBUG_ENABLED - if (EngineDebugger::is_active()) + if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line); + } #define GD_ERR_BREAK(m_cond) \ { \ @@ -432,7 +412,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a profile.frame_call_count++; } bool exit_ok = false; - bool yielded = false; + bool awaited = false; #endif #ifdef DEBUG_ENABLED @@ -443,9 +423,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif OPCODE_SWITCH(_code_ptr[ip]) { - OPCODE(OPCODE_OPERATOR) { - CHECK_SPACE(5); bool valid; @@ -465,7 +443,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif #ifdef DEBUG_ENABLED if (!valid) { - if (ret.get_type() == Variant::STRING) { //return a string when invalid with the error err_text = ret; @@ -482,7 +459,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_EXTENDS_TEST) { - CHECK_SPACE(4); GET_VARIANT_PTR(a, 1); @@ -491,7 +467,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #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; } @@ -499,7 +474,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a 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); @@ -528,11 +502,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a //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; @@ -544,12 +516,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } 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; } @@ -564,7 +534,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_IS_BUILTIN) { - CHECK_SPACE(4); GET_VARIANT_PTR(value, 1); @@ -579,7 +548,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_SET) { - CHECK_SPACE(3); GET_VARIANT_PTR(dst, 1); @@ -606,7 +574,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_GET) { - CHECK_SPACE(3); GET_VARIANT_PTR(src, 1); @@ -639,7 +606,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_SET_NAMED) { - CHECK_SPACE(3); GET_VARIANT_PTR(dst, 1); @@ -665,7 +631,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_GET_NAMED) { - CHECK_SPACE(4); GET_VARIANT_PTR(src, 1); @@ -700,7 +665,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_SET_MEMBER) { - CHECK_SPACE(3); int indexname = _code_ptr[ip + 1]; GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); @@ -725,7 +689,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_GET_MEMBER) { - CHECK_SPACE(3); int indexname = _code_ptr[ip + 1]; GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); @@ -746,7 +709,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_ASSIGN) { - CHECK_SPACE(3); GET_VARIANT_PTR(dst, 1); GET_VARIANT_PTR(src, 2); @@ -758,7 +720,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_ASSIGN_TRUE) { - CHECK_SPACE(2); GET_VARIANT_PTR(dst, 1); @@ -769,7 +730,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_ASSIGN_FALSE) { - CHECK_SPACE(2); GET_VARIANT_PTR(dst, 1); @@ -780,7 +740,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) { - CHECK_SPACE(4); GET_VARIANT_PTR(dst, 2); GET_VARIANT_PTR(src, 3); @@ -810,7 +769,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { - CHECK_SPACE(4); GET_VARIANT_PTR(dst, 2); GET_VARIANT_PTR(src, 3); @@ -839,7 +797,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) { - CHECK_SPACE(4); GET_VARIANT_PTR(dst, 2); GET_VARIANT_PTR(src, 3); @@ -856,7 +813,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } 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() + @@ -890,7 +846,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a 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); @@ -913,7 +868,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CAST_TO_NATIVE) { - CHECK_SPACE(4); GET_VARIANT_PTR(to_type, 1); GET_VARIANT_PTR(src, 2); @@ -941,7 +895,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CAST_TO_SCRIPT) { - CHECK_SPACE(4); GET_VARIANT_PTR(to_type, 1); GET_VARIANT_PTR(src, 2); @@ -961,11 +914,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a 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) { @@ -989,7 +940,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CONSTRUCT) { - CHECK_SPACE(2); Variant::Type t = Variant::Type(_code_ptr[ip + 1]); int argc = _code_ptr[ip + 2]; @@ -1006,7 +956,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #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; } @@ -1018,7 +967,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CONSTRUCT_ARRAY) { - CHECK_SPACE(1); int argc = _code_ptr[ip + 1]; Array array; //arrays are always shared @@ -1039,7 +987,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CONSTRUCT_DICTIONARY) { - CHECK_SPACE(1); int argc = _code_ptr[ip + 1]; Dictionary dict; //arrays are always shared @@ -1047,7 +994,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a 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; @@ -1061,11 +1007,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_CALL_ASYNC) OPCODE(OPCODE_CALL_RETURN) OPCODE(OPCODE_CALL) { - CHECK_SPACE(4); - bool call_ret = _code_ptr[ip] == OPCODE_CALL_RETURN; + bool call_ret = _code_ptr[ip] != OPCODE_CALL; +#ifdef DEBUG_ENABLED + bool call_async = _code_ptr[ip] == OPCODE_CALL_ASYNC; +#endif int argc = _code_ptr[ip + 1]; GET_VARIANT_PTR(base, 2); @@ -1094,11 +1043,25 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif Callable::CallError err; if (call_ret) { - GET_VARIANT_PTR(ret, argc); base->call_ptr(*methodname, (const Variant **)argptrs, argc, ret, err); - } else { +#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 @@ -1107,7 +1070,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } if (err.error != Callable::CallError::CALL_OK) { - String methodstr = *methodname; String basestr = _get_var_type(base); @@ -1119,14 +1081,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } } 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; } @@ -1143,7 +1102,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CALL_BUILT_IN) { - CHECK_SPACE(4); GDScriptFunctions::Function func = GDScriptFunctions::Function(_code_ptr[ip + 1]); @@ -1167,7 +1125,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #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 @@ -1183,18 +1140,15 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a 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; } @@ -1220,19 +1174,17 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a while (gds->base.ptr()) { gds = gds->base.ptr(); E = gds->member_functions.find(*methodname); - if (E) + 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; @@ -1243,7 +1195,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a err.error = Callable::CallError::CALL_OK; } } else { - if (*methodname != GDScriptLanguage::get_singleton()->strings._init) { err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; } else { @@ -1252,7 +1203,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } if (err.error != Callable::CallError::CALL_OK) { - String methodstr = *methodname; err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs); @@ -1263,97 +1213,96 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; - OPCODE(OPCODE_YIELD) - OPCODE(OPCODE_YIELD_SIGNAL) { + OPCODE(OPCODE_AWAIT) { + int ipofs = 2; + CHECK_SPACE(3); - int ipofs = 1; - if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) { - CHECK_SPACE(4); - ipofs += 2; - } else { - CHECK_SPACE(2); - } + //do the oneshot connect + GET_VARIANT_PTR(argobj, 1); - Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState); - gdfs->function = this; + Signal sig; + bool is_signal = true; - gdfs->state.stack.resize(alloca_size); - //copy variant stack - for (int i = 0; i < _stack_size; i++) { - memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i])); - } - gdfs->state.stack_size = _stack_size; - gdfs->state.self = self; - gdfs->state.alloca_size = alloca_size; - gdfs->state.script = Ref<GDScript>(_script); - gdfs->state.ip = ip + ipofs; - gdfs->state.line = line; - gdfs->state.instance_id = (p_instance && p_instance->get_owner()) ? p_instance->get_owner()->get_instance_id() : ObjectID(); - //gdfs->state.result_pos=ip+ipofs-1; - gdfs->state.defarg = defarg; - gdfs->state.instance = p_instance; - gdfs->function = this; - - retvalue = gdfs; - - if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) { - //do the oneshot connect - GET_VARIANT_PTR(argobj, 1); - GET_VARIANT_PTR(argname, 2); + { + Variant result = *argobj; -#ifdef DEBUG_ENABLED - if (argobj->get_type() != Variant::OBJECT) { - err_text = "First argument of yield() not of type object."; - OPCODE_BREAK; - } - if (argname->get_type() != Variant::STRING) { - err_text = "Second argument of yield() not a string (for signal name)."; - OPCODE_BREAK; - } -#endif + if (argobj->get_type() == Variant::OBJECT) { + bool was_freed = false; + Object *obj = argobj->get_validated_object_with_check(was_freed); -#ifdef DEBUG_ENABLED - bool was_freed; - Object *obj = argobj->get_validated_object_with_check(was_freed); - String signal = argname->operator String(); + if (was_freed) { + err_text = "Trying to await on a freed object."; + OPCODE_BREAK; + } - if (was_freed) { - err_text = "First argument of yield() is a previously freed instance."; - 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 (!obj) { - err_text = "First argument of yield() is null."; - OPCODE_BREAK; + if (result.get_type() != Variant::SIGNAL) { + ip += 4; // Skip OPCODE_AWAIT_RESUME and its data. + // The stack pointer should be the same, so we don't need to set a return value. + is_signal = false; + } else { + sig = result; } - if (signal.length() == 0) { + } - err_text = "Second argument of yield() is an empty string (for signal name)."; - OPCODE_BREAK; + if (is_signal) { + Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState); + gdfs->function = this; + + gdfs->state.stack.resize(alloca_size); + //copy variant stack + for (int i = 0; i < _stack_size; i++) { + memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i])); } + 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 = obj->connect_compat(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT); + Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT); if (err != OK) { - err_text = "Error connecting to signal: " + signal + " during yield()."; + err_text = "Error connecting to signal: " + sig.get_name() + " during await."; OPCODE_BREAK; } -#else - Object *obj = argobj->operator Object *(); - String signal = argname->operator String(); - - obj->connect_compat(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT); -#endif - } #ifdef DEBUG_ENABLED - exit_ok = true; - yielded = true; + exit_ok = true; + awaited = true; #endif - OPCODE_BREAK; + OPCODE_BREAK; + } } + DISPATCH_OPCODE; // Needed for synchronous calls (when result is immediately available). - OPCODE(OPCODE_YIELD_RESUME) { - + OPCODE(OPCODE_AWAIT_RESUME) { CHECK_SPACE(2); #ifdef DEBUG_ENABLED if (!p_state) { @@ -1368,7 +1317,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_JUMP) { - CHECK_SPACE(2); int to = _code_ptr[ip + 1]; @@ -1378,7 +1326,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_JUMP_IF) { - CHECK_SPACE(3); GET_VARIANT_PTR(test, 1); @@ -1396,7 +1343,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_JUMP_IF_NOT) { - CHECK_SPACE(3); GET_VARIANT_PTR(test, 1); @@ -1414,14 +1360,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a 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; @@ -1432,7 +1376,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } OPCODE(OPCODE_ITERATE_BEGIN) { - CHECK_SPACE(8); //space for this a regular iterate GET_VARIANT_PTR(counter, 1); @@ -1465,7 +1408,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_ITERATE) { - CHECK_SPACE(4); GET_VARIANT_PTR(counter, 1); @@ -1502,11 +1444,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED GET_VARIANT_PTR(test, 1); - GET_VARIANT_PTR(message, 2); bool result = test->booleanize(); if (!result) { - const String &message_str = *message; + 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 { @@ -1541,15 +1486,17 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a bool do_break = false; if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) { - - if (EngineDebugger::get_script_debugger()->get_depth() <= 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) + } + if (EngineDebugger::get_script_debugger()->get_lines_left() <= 0) { do_break = true; + } } - if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source)) + if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source)) { do_break = true; + } if (do_break) { GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true); @@ -1577,20 +1524,24 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODES_END #ifdef DEBUG_ENABLED - if (exit_ok) + 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 != "") + 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) + } else if (script) { err_file = script->path; - if (err_file == "") + } + 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 != "") + 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)."; @@ -1617,19 +1568,21 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time; } - // Check if this is the last time the function is resuming from yield - // Will be true if never yielded as well + // Check if this is the last time the function is resuming from await + // Will be true if never awaited as well // When it's the last resume it will postpone the exit from stack, // so the debugger knows which function triggered the resume of the next function (if any) - if (!p_state || yielded) { - if (EngineDebugger::is_active()) + 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++) + for (int i = 0; i < _stack_size; i++) { stack[i].~Variant(); + } } #ifdef DEBUG_ENABLED @@ -1640,32 +1593,28 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } const int *GDScriptFunction::get_code() const { - return _code_ptr; } -int GDScriptFunction::get_code_size() const { +int GDScriptFunction::get_code_size() const { return _code_size; } Variant GDScriptFunction::get_constant(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, constants.size(), "<errconst>"); return constants[p_idx]; } StringName GDScriptFunction::get_global_name(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, global_names.size(), "<errgname>"); return global_names[p_idx]; } int GDScriptFunction::get_default_argument_count() const { - return _default_arg_count; } -int GDScriptFunction::get_default_argument_addr(int p_idx) const { +int GDScriptFunction::get_default_argument_addr(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), -1); return default_arguments[p_idx]; } @@ -1680,45 +1629,38 @@ GDScriptDataType GDScriptFunction::get_argument_type(int p_idx) const { } StringName GDScriptFunction::get_name() const { - return name; } int GDScriptFunction::get_max_stack_size() const { - return _stack_size; } struct _GDFKC { - int order; List<int> pos; }; struct _GDFKCS { - int order; StringName id; int pos; bool operator<(const _GDFKCS &p_r) const { - return order < p_r.order; } }; void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const { - int oc = 0; Map<StringName, _GDFKC> sdmap; for (const List<StackDebug>::Element *E = stack_debug.front(); E; E = E->next()) { - const StackDebug &sd = E->get(); - if (sd.line > p_line) + if (sd.line > p_line) { break; + } if (sd.added) { - if (!sdmap.has(sd.identifier)) { _GDFKC d; d.order = oc++; @@ -1729,18 +1671,17 @@ void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<String sdmap[sd.identifier].pos.push_back(sd.pos); } } else { - ERR_CONTINUE(!sdmap.has(sd.identifier)); sdmap[sd.identifier].pos.pop_back(); - if (sdmap[sd.identifier].pos.empty()) + if (sdmap[sd.identifier].pos.empty()) { sdmap.erase(sd.identifier); + } } } List<_GDFKCS> stackpositions; for (Map<StringName, _GDFKC>::Element *E = sdmap.front(); E; E = E->next()) { - _GDFKCS spp; spp.id = E->key(); spp.order = E->get().order; @@ -1751,7 +1692,6 @@ void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<String stackpositions.sort(); for (List<_GDFKCS>::Element *E = stackpositions.front(); E; E = E->next()) { - Pair<StringName, int> p; p.first = E->get().id; p.second = E->get().pos; @@ -1761,7 +1701,6 @@ 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; @@ -1800,7 +1739,6 @@ GDScriptFunction::~GDScriptFunction() { ///////////////////// Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - Variant arg; r_error.error = Callable::CallError::CALL_OK; @@ -1833,28 +1771,48 @@ Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_ar } bool GDScriptFunctionState::is_valid(bool p_extended_check) const { - - if (function == nullptr) + if (function == nullptr) { return false; + } if (p_extended_check) { - //class instance gone? - if (state.instance_id.is_valid() && !ObjectDB::get_instance(state.instance_id)) + MutexLock lock(GDScriptLanguage::get_singleton()->lock); + + // Script gone? + if (!scripts_list.in_list()) { return false; + } + // Class instance gone? (if not static function) + if (state.instance && !instances_list.in_list()) { + return false; + } } return true; } Variant GDScriptFunctionState::resume(const Variant &p_arg) { - ERR_FAIL_COND_V(!function, Variant()); - if (state.instance_id.is_valid() && !ObjectDB::get_instance(state.instance_id)) { + { + MutexLock lock(GDScriptLanguage::singleton->lock); + + if (!scripts_list.in_list()) { #ifdef DEBUG_ENABLED - ERR_FAIL_V_MSG(Variant(), "Resumed function '" + String(function->get_name()) + "()' after yield, but class instance is gone. At script: " + state.script->get_path() + ":" + itos(state.line)); + ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but script is gone. At script: " + state.script_path + ":" + itos(state.line)); #else - return Variant(); + return Variant(); +#endif + } + if (state.instance && !instances_list.in_list()) { +#ifdef DEBUG_ENABLED + ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line)); +#else + return Variant(); #endif + } + // Do these now to avoid locking again after the call + scripts_list.remove_from_list(); + instances_list.remove_from_list(); } state.result = p_arg; @@ -1864,7 +1822,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) { bool completed = true; // If the return value is a GDScriptFunctionState reference, - // then the function did yield again after resuming. + // then the function did awaited again after resuming. if (ret.is_ref()) { GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret); if (gdfs && gdfs->function == function) { @@ -1884,13 +1842,8 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) { } #ifdef DEBUG_ENABLED - if (EngineDebugger::is_active()) + if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->exit_function(); - if (state.stack_size) { - //free stack - Variant *stack = (Variant *)state.stack.ptr(); - for (int i = 0; i < state.stack_size; i++) - stack[i].~Variant(); } #endif } @@ -1898,8 +1851,17 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) { return ret; } -void GDScriptFunctionState::_bind_methods() { +void GDScriptFunctionState::_clear_stack() { + if (state.stack_size) { + Variant *stack = (Variant *)state.stack.ptr(); + for (int i = 0; i < state.stack_size; i++) { + stack[i].~Variant(); + } + state.stack_size = 0; + } +} +void GDScriptFunctionState::_bind_methods() { ClassDB::bind_method(D_METHOD("resume", "arg"), &GDScriptFunctionState::resume, DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("is_valid", "extended_check"), &GDScriptFunctionState::is_valid, DEFVAL(false)); ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "_signal_callback", &GDScriptFunctionState::_signal_callback, MethodInfo("_signal_callback")); @@ -1907,18 +1869,18 @@ void GDScriptFunctionState::_bind_methods() { ADD_SIGNAL(MethodInfo("completed", PropertyInfo(Variant::NIL, "result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT))); } -GDScriptFunctionState::GDScriptFunctionState() { - +GDScriptFunctionState::GDScriptFunctionState() : + scripts_list(this), + instances_list(this) { function = nullptr; } GDScriptFunctionState::~GDScriptFunctionState() { + _clear_stack(); - if (function != nullptr) { - //never called, deinitialize stack - for (int i = 0; i < state.stack_size; i++) { - Variant *v = (Variant *)&state.stack[sizeof(Variant) * i]; - v->~Variant(); - } + { + MutexLock lock(GDScriptLanguage::singleton->lock); + scripts_list.remove_from_list(); + instances_list.remove_from_list(); } } diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index acfc0a95b4..771baf6a08 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -43,20 +43,25 @@ class GDScriptInstance; class GDScript; struct GDScriptDataType { - bool has_type; - enum { + enum Kind { UNINITIALIZED, BUILTIN, NATIVE, SCRIPT, GDSCRIPT, - } kind; - Variant::Type builtin_type; + }; + + Kind kind = UNINITIALIZED; + + bool has_type = false; + Variant::Type builtin_type = Variant::NIL; StringName native_type; Ref<Script> script_type; bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const { - if (!has_type) return true; // Can't type check + if (!has_type) { + return true; // Can't type check + } switch (kind) { case UNINITIALIZED: @@ -146,10 +151,7 @@ struct GDScriptDataType { return info; } - GDScriptDataType() : - has_type(false), - kind(UNINITIALIZED), - builtin_type(Variant::NIL) {} + GDScriptDataType() {} }; class GDScriptFunction { @@ -178,12 +180,12 @@ public: OPCODE_CONSTRUCT_DICTIONARY, OPCODE_CALL, OPCODE_CALL_RETURN, + OPCODE_CALL_ASYNC, OPCODE_CALL_BUILT_IN, OPCODE_CALL_SELF, OPCODE_CALL_SELF_BASE, - OPCODE_YIELD, - OPCODE_YIELD_SIGNAL, - OPCODE_YIELD_RESUME, + OPCODE_AWAIT, + OPCODE_AWAIT_RESUME, OPCODE_JUMP, OPCODE_JUMP_IF, OPCODE_JUMP_IF_NOT, @@ -214,7 +216,6 @@ public: }; struct StackDebug { - int line; int pos; bool added; @@ -292,14 +293,16 @@ private: public: struct CallState { - - ObjectID instance_id; + GDScript *script; GDScriptInstance *instance; +#ifdef DEBUG_ENABLED + StringName function_name; + String script_path; +#endif Vector<uint8_t> stack; int stack_size; Variant self; uint32_t alloca_size; - Ref<GDScript> script; int ip; int line; int defarg; @@ -347,7 +350,6 @@ public: }; class GDScriptFunctionState : public Reference { - GDCLASS(GDScriptFunctionState, Reference); friend class GDScriptFunction; GDScriptFunction *function; @@ -355,12 +357,18 @@ class GDScriptFunctionState : public Reference { Variant _signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error); Ref<GDScriptFunctionState> first_state; + SelfList<GDScriptFunctionState> scripts_list; + SelfList<GDScriptFunctionState> instances_list; + protected: static void _bind_methods(); public: bool is_valid(bool p_extended_check = false) const; Variant resume(const Variant &p_arg = Variant()); + + void _clear_stack(); + GDScriptFunctionState(); ~GDScriptFunctionState(); }; diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp index 9154d6eb89..fefbf906f0 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -41,7 +41,6 @@ #include "gdscript.h" const char *GDScriptFunctions::get_func_name(Function p_func) { - ERR_FAIL_INDEX_V(p_func, FUNC_MAX, ""); static const char *_names[FUNC_MAX] = { @@ -140,7 +139,6 @@ const char *GDScriptFunctions::get_func_name(Function p_func) { } void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_count, Variant &r_ret, Callable::CallError &r_error) { - r_error.error = Callable::CallError::CALL_OK; #ifdef DEBUG_ENABLED @@ -148,12 +146,14 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ if (p_arg_count < m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \ r_error.argument = m_count; \ + r_error.expected = m_count; \ r_ret = Variant(); \ return; \ } \ if (p_arg_count > m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \ r_error.argument = m_count; \ + r_error.expected = m_count; \ r_ret = Variant(); \ return; \ } @@ -176,7 +176,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ //using a switch, so the compiler generates a jumptable switch (p_func) { - case MATH_SIN: { VALIDATE_ARG_COUNT(1); VALIDATE_ARG_NUM(0); @@ -269,15 +268,12 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ case MATH_ABS: { VALIDATE_ARG_COUNT(1); if (p_args[0]->get_type() == Variant::INT) { - int64_t i = *p_args[0]; r_ret = ABS(i); } else if (p_args[0]->get_type() == Variant::FLOAT) { - double r = *p_args[0]; r_ret = Math::abs(r); } else { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::FLOAT; @@ -287,15 +283,12 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ case MATH_SIGN: { VALIDATE_ARG_COUNT(1); if (p_args[0]->get_type() == Variant::INT) { - 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]; r_ret = r < 0.0 ? -1.0 : (r > 0.0 ? +1.0 : 0.0); } else { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::FLOAT; @@ -362,13 +355,13 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ const double t = (double)*p_args[2]; switch (p_args[0]->get_type() == p_args[1]->get_type() ? p_args[0]->get_type() : Variant::FLOAT) { case Variant::VECTOR2: { - r_ret = ((Vector2)*p_args[0]).linear_interpolate((Vector2)*p_args[1], t); + r_ret = ((Vector2)*p_args[0]).lerp((Vector2)*p_args[1], t); } break; case Variant::VECTOR3: { - r_ret = (p_args[0]->operator Vector3()).linear_interpolate(p_args[1]->operator Vector3(), t); + r_ret = (p_args[0]->operator Vector3()).lerp(p_args[1]->operator Vector3(), t); } break; case Variant::COLOR: { - r_ret = ((Color)*p_args[0]).linear_interpolate((Color)*p_args[1], t); + r_ret = ((Color)*p_args[0]).lerp((Color)*p_args[1], t); } break; default: { VALIDATE_ARG_NUM(0); @@ -505,7 +498,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ case LOGIC_MAX: { VALIDATE_ARG_COUNT(2); if (p_args[0]->get_type() == Variant::INT && p_args[1]->get_type() == Variant::INT) { - int64_t a = *p_args[0]; int64_t b = *p_args[1]; r_ret = MAX(a, b); @@ -523,7 +515,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ case LOGIC_MIN: { VALIDATE_ARG_COUNT(2); if (p_args[0]->get_type() == Variant::INT && p_args[1]->get_type() == Variant::INT) { - int64_t a = *p_args[0]; int64_t b = *p_args[1]; r_ret = MIN(a, b); @@ -540,7 +531,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ case LOGIC_CLAMP: { VALIDATE_ARG_COUNT(3); if (p_args[0]->get_type() == Variant::INT && p_args[1]->get_type() == Variant::INT && p_args[2]->get_type() == Variant::INT) { - int64_t a = *p_args[0]; int64_t b = *p_args[1]; int64_t c = *p_args[2]; @@ -595,7 +585,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ 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; @@ -603,7 +592,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ 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; @@ -624,7 +612,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ VALIDATE_ARG_NUM(1); int type = *p_args[1]; if (type < 0 || type >= Variant::VARIANT_MAX) { - r_ret = RTR("Invalid type argument to convert(), use TYPE_* constants."); r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; @@ -632,18 +619,15 @@ 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); } } break; case TYPE_OF: { - VALIDATE_ARG_COUNT(1); r_ret = p_args[0]->get_type(); } break; case TYPE_EXISTS: { - VALIDATE_ARG_COUNT(1); r_ret = ClassDB::class_exists(*p_args[0]); @@ -655,11 +639,9 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ r_ret = String(result); } break; case TEXT_ORD: { - VALIDATE_ARG_COUNT(1); if (p_args[0]->get_type() != Variant::STRING) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING; @@ -670,7 +652,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ String str = p_args[0]->operator String(); if (str.length() != 1) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING; @@ -691,23 +672,21 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } String str; for (int i = 0; i < p_arg_count; i++) { - String os = p_args[i]->operator String(); - if (i == 0) + if (i == 0) { str = os; - else + } else { str += os; + } } r_ret = str; } break; case TEXT_PRINT: { - String str; for (int i = 0; i < p_arg_count; i++) { - str += p_args[i]->operator String(); } @@ -716,12 +695,11 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case TEXT_PRINT_TABBED: { - String str; for (int i = 0; i < p_arg_count; i++) { - - if (i) + if (i) { str += "\t"; + } str += p_args[i]->operator String(); } @@ -730,12 +708,11 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case TEXT_PRINT_SPACED: { - String str; for (int i = 0; i < p_arg_count; i++) { - - if (i) + if (i) { str += " "; + } str += p_args[i]->operator String(); } @@ -745,10 +722,8 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case TEXT_PRINTERR: { - String str; for (int i = 0; i < p_arg_count; i++) { - str += p_args[i]->operator String(); } @@ -759,7 +734,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ case TEXT_PRINTRAW: { String str; for (int i = 0; i < p_arg_count; i++) { - str += p_args[i]->operator String(); } @@ -770,7 +744,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ case TEXT_PRINT_DEBUG: { String str; for (int i = 0; i < p_arg_count; i++) { - str += p_args[i]->operator String(); } @@ -922,18 +895,15 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case GEN_RANGE: { - switch (p_arg_count) { - case 0: { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = 1; + r_error.expected = 1; r_ret = Variant(); } break; case 1: { - VALIDATE_ARG_NUM(0); int count = *p_args[0]; Array arr; @@ -955,7 +925,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ r_ret = arr; } break; case 2: { - VALIDATE_ARG_NUM(0); VALIDATE_ARG_NUM(1); @@ -973,12 +942,12 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ r_ret = Variant(); return; } - for (int i = from; i < to; i++) + for (int i = from; i < to; i++) { arr[i - from] = i; + } r_ret = arr; } break; case 3: { - VALIDATE_ARG_NUM(0); VALIDATE_ARG_NUM(1); VALIDATE_ARG_NUM(2); @@ -987,7 +956,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ int to = *p_args[1]; int incr = *p_args[2]; if (incr == 0) { - r_ret = RTR("Step argument is zero!"); r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; return; @@ -1006,10 +974,8 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ //calculate how many int count = 0; if (incr > 0) { - count = ((to - from - 1) / incr) + 1; } else { - count = ((from - to - 1) / -incr) + 1; } @@ -1027,7 +993,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ arr[idx++] = i; } } else { - int idx = 0; for (int i = from; i > to; i += incr) { arr[idx++] = i; @@ -1037,9 +1002,9 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ r_ret = arr; } break; default: { - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_error.argument = 3; + r_error.expected = 3; r_ret = Variant(); } break; @@ -1059,7 +1024,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case INST2DICT: { - VALIDATE_ARG_COUNT(1); if (p_args[0]->get_type() == Variant::NIL) { @@ -1069,24 +1033,20 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ r_error.argument = 0; r_ret = Variant(); } else { - Object *obj = *p_args[0]; if (!obj) { r_ret = Variant(); } else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::DICTIONARY; r_ret = RTR("Not a script with an instance"); return; } else { - GDScriptInstance *ins = static_cast<GDScriptInstance *>(obj->get_script_instance()); Ref<GDScript> base = ins->get_script(); if (base.is_null()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::DICTIONARY; @@ -1098,7 +1058,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ Vector<StringName> sname; while (p->_owner) { - sname.push_back(p->name); p = p->_owner; } @@ -1132,11 +1091,9 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case DICT2INST: { - VALIDATE_ARG_COUNT(1); if (p_args[0]->get_type() != Variant::DICTIONARY) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::DICTIONARY; @@ -1148,7 +1105,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ Dictionary d = *p_args[0]; if (!d.has("@path")) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::OBJECT; @@ -1159,7 +1115,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ Ref<Script> scr = ResourceLoader::load(d["@path"]); if (!scr.is_valid()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::OBJECT; @@ -1170,7 +1125,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ Ref<GDScript> gdscr = scr; if (!gdscr.is_valid()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::OBJECT; @@ -1185,10 +1139,8 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } for (int i = 0; i < sub.get_name_count(); i++) { - gdscr = gdscr->subclasses[sub.get_name(i)]; if (!gdscr.is_valid()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::OBJECT; @@ -1197,8 +1149,12 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ return; } } + r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error); - r_ret = gdscr->_new(nullptr, 0, r_error); + if (r_error.error != Callable::CallError::CALL_OK) { + r_ret = Variant(); + return; + } GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(r_ret)->get_script_instance()); Ref<GDScript> gd_ref = ins->get_script(); @@ -1211,7 +1167,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case VALIDATE_JSON: { - VALIDATE_ARG_COUNT(1); if (p_args[0]->get_type() != Variant::STRING) { @@ -1235,7 +1190,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case PARSE_JSON: { - VALIDATE_ARG_COUNT(1); if (p_args[0]->get_type() != Variant::STRING) { @@ -1263,13 +1217,11 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ r_ret = JSON::print(*p_args[0]); } break; case HASH: { - VALIDATE_ARG_COUNT(1); r_ret = p_args[0]->hash(); } break; case COLOR8: { - if (p_arg_count < 3) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = 3; @@ -1300,7 +1252,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case COLORN: { - if (p_arg_count < 1) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = 1; @@ -1335,7 +1286,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ ScriptLanguage *script = GDScriptLanguage::get_singleton(); for (int i = 0; i < script->debug_get_stack_level_count(); i++) { - print_line("Frame " + itos(i) + " - " + script->debug_get_stack_level_source(i) + ":" + itos(script->debug_get_stack_level_line(i)) + " in function '" + script->debug_get_stack_level_function(i) + "'"); }; } break; @@ -1346,7 +1296,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ ScriptLanguage *script = GDScriptLanguage::get_singleton(); Array ret; for (int i = 0; i < script->debug_get_stack_level_count(); i++) { - Dictionary frame; frame["source"] = script->debug_get_stack_level_source(i); frame["function"] = script->debug_get_stack_level_function(i); @@ -1357,7 +1306,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case INSTANCE_FROM_ID: { - VALIDATE_ARG_COUNT(1); if (p_args[0]->get_type() != Variant::INT && p_args[0]->get_type() != Variant::FLOAT) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; @@ -1372,66 +1320,53 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case LEN: { - VALIDATE_ARG_COUNT(1); switch (p_args[0]->get_type()) { case Variant::STRING: { - String d = *p_args[0]; r_ret = d.length(); } break; case Variant::DICTIONARY: { - Dictionary d = *p_args[0]; r_ret = d.size(); } break; case Variant::ARRAY: { - Array d = *p_args[0]; r_ret = d.size(); } break; case Variant::PACKED_BYTE_ARRAY: { - Vector<uint8_t> d = *p_args[0]; r_ret = d.size(); } break; case Variant::PACKED_INT32_ARRAY: { - Vector<int32_t> d = *p_args[0]; r_ret = d.size(); } break; case Variant::PACKED_INT64_ARRAY: { - Vector<int64_t> d = *p_args[0]; r_ret = d.size(); } break; case Variant::PACKED_FLOAT32_ARRAY: { - Vector<float> d = *p_args[0]; r_ret = d.size(); } break; case Variant::PACKED_FLOAT64_ARRAY: { - Vector<double> d = *p_args[0]; r_ret = d.size(); } break; case Variant::PACKED_STRING_ARRAY: { - Vector<String> d = *p_args[0]; r_ret = d.size(); } break; case Variant::PACKED_VECTOR2_ARRAY: { - Vector<Vector2> d = *p_args[0]; r_ret = d.size(); } break; case Variant::PACKED_VECTOR3_ARRAY: { - Vector<Vector3> d = *p_args[0]; r_ret = d.size(); } break; case Variant::PACKED_COLOR_ARRAY: { - Vector<Color> d = *p_args[0]; r_ret = d.size(); } break; @@ -1446,7 +1381,6 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case IS_INSTANCE_VALID: { - VALIDATE_ARG_COUNT(1); if (p_args[0]->get_type() != Variant::OBJECT) { r_ret = false; @@ -1457,19 +1391,16 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case FUNC_MAX: { - ERR_FAIL(); } break; } } bool GDScriptFunctions::is_deterministic(Function p_func) { - //man i couldn't have chosen a worse function name, //way too controversial.. switch (p_func) { - case MATH_SIN: case MATH_COS: case MATH_TAN: @@ -1533,12 +1464,10 @@ bool GDScriptFunctions::is_deterministic(Function p_func) { } MethodInfo GDScriptFunctions::get_info(Function p_func) { - #ifdef DEBUG_ENABLED //using a switch, so the compiler generates a jumptable switch (p_func) { - case MATH_SIN: { MethodInfo mi("sin", PropertyInfo(Variant::FLOAT, "s")); mi.return_val.type = Variant::FLOAT; @@ -1707,7 +1636,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case MATH_SMOOTHSTEP: { - MethodInfo mi("smoothstep", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "weight")); + MethodInfo mi("smoothstep", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "s")); mi.return_val.type = Variant::FLOAT; return mi; } break; @@ -1813,7 +1742,6 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case OBJ_WEAKREF: { - MethodInfo mi("weakref", PropertyInfo(Variant::OBJECT, "obj")); mi.return_val.type = Variant::OBJECT; mi.return_val.class_name = "WeakRef"; @@ -1822,7 +1750,6 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } 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"; @@ -1830,42 +1757,36 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } 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; mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; return mi; } break; case TYPE_OF: { - MethodInfo mi("typeof", PropertyInfo(Variant::NIL, "what", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::INT; return mi; } break; case TYPE_EXISTS: { - MethodInfo mi("type_exists", PropertyInfo(Variant::STRING, "type")); mi.return_val.type = Variant::BOOL; return mi; } break; case TEXT_CHAR: { - MethodInfo mi("char", PropertyInfo(Variant::INT, "code")); mi.return_val.type = Variant::STRING; return mi; } break; case TEXT_ORD: { - MethodInfo mi("ord", PropertyInfo(Variant::STRING, "char")); mi.return_val.type = Variant::INT; return mi; } break; case TEXT_STR: { - MethodInfo mi("str"); mi.return_val.type = Variant::STRING; mi.flags |= METHOD_FLAG_VARARG; @@ -1873,7 +1794,6 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } break; case TEXT_PRINT: { - MethodInfo mi("print"); mi.return_val.type = Variant::NIL; mi.flags |= METHOD_FLAG_VARARG; @@ -1881,7 +1801,6 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } break; case TEXT_PRINT_TABBED: { - MethodInfo mi("printt"); mi.return_val.type = Variant::NIL; mi.flags |= METHOD_FLAG_VARARG; @@ -1889,7 +1808,6 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } break; case TEXT_PRINT_SPACED: { - MethodInfo mi("prints"); mi.return_val.type = Variant::NIL; mi.flags |= METHOD_FLAG_VARARG; @@ -1897,7 +1815,6 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } break; case TEXT_PRINTERR: { - MethodInfo mi("printerr"); mi.return_val.type = Variant::NIL; mi.flags |= METHOD_FLAG_VARARG; @@ -1905,7 +1822,6 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } break; case TEXT_PRINTRAW: { - MethodInfo mi("printraw"); mi.return_val.type = Variant::NIL; mi.flags |= METHOD_FLAG_VARARG; @@ -1913,7 +1829,6 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } break; case TEXT_PRINT_DEBUG: { - MethodInfo mi("print_debug"); mi.return_val.type = Variant::NIL; mi.flags |= METHOD_FLAG_VARARG; @@ -1921,41 +1836,35 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { } break; case PUSH_ERROR: { - MethodInfo mi(Variant::NIL, "push_error", PropertyInfo(Variant::STRING, "message")); mi.return_val.type = Variant::NIL; return mi; } break; case PUSH_WARNING: { - MethodInfo mi(Variant::NIL, "push_warning", PropertyInfo(Variant::STRING, "message")); mi.return_val.type = Variant::NIL; return mi; } break; case VAR_TO_STR: { - MethodInfo mi("var2str", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::STRING; return mi; } break; case STR_TO_VAR: { - MethodInfo mi(Variant::NIL, "str2var", PropertyInfo(Variant::STRING, "string")); mi.return_val.type = Variant::NIL; mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; return mi; } break; case VAR_TO_BYTES: { - MethodInfo mi("var2bytes", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::BOOL, "full_objects")); mi.default_arguments.push_back(false); mi.return_val.type = Variant::PACKED_BYTE_ARRAY; return mi; } break; case BYTES_TO_VAR: { - MethodInfo mi(Variant::NIL, "bytes2var", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "bytes"), PropertyInfo(Variant::BOOL, "allow_objects")); mi.default_arguments.push_back(false); mi.return_val.type = Variant::NIL; @@ -1963,65 +1872,55 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case GEN_RANGE: { - MethodInfo mi("range"); mi.return_val.type = Variant::ARRAY; mi.flags |= METHOD_FLAG_VARARG; return mi; } break; case RESOURCE_LOAD: { - MethodInfo mi("load", PropertyInfo(Variant::STRING, "path")); mi.return_val.type = Variant::OBJECT; mi.return_val.class_name = "Resource"; return mi; } break; case INST2DICT: { - MethodInfo mi("inst2dict", PropertyInfo(Variant::OBJECT, "inst")); mi.return_val.type = Variant::DICTIONARY; return mi; } break; case DICT2INST: { - MethodInfo mi("dict2inst", PropertyInfo(Variant::DICTIONARY, "dict")); mi.return_val.type = Variant::OBJECT; return mi; } break; case VALIDATE_JSON: { - MethodInfo mi("validate_json", PropertyInfo(Variant::STRING, "json")); mi.return_val.type = Variant::STRING; return mi; } break; case PARSE_JSON: { - MethodInfo mi(Variant::NIL, "parse_json", PropertyInfo(Variant::STRING, "json")); mi.return_val.type = Variant::NIL; mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; return mi; } break; case TO_JSON: { - MethodInfo mi("to_json", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::STRING; return mi; } break; case HASH: { - MethodInfo mi("hash", PropertyInfo(Variant::NIL, "var", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); mi.return_val.type = Variant::INT; return mi; } break; case COLOR8: { - MethodInfo mi("Color8", PropertyInfo(Variant::INT, "r8"), PropertyInfo(Variant::INT, "g8"), PropertyInfo(Variant::INT, "b8"), PropertyInfo(Variant::INT, "a8")); mi.default_arguments.push_back(255); mi.return_val.type = Variant::COLOR; return mi; } break; case COLORN: { - MethodInfo mi("ColorN", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::FLOAT, "alpha")); mi.default_arguments.push_back(1.0f); mi.return_val.type = Variant::COLOR; @@ -2055,7 +1954,6 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; default: { - ERR_FAIL_V(MethodInfo()); } break; } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a99d4b96f7..0967f74285 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -30,8690 +30,3822 @@ #include "gdscript_parser.h" -#include "core/core_string_names.h" -#include "core/engine.h" #include "core/io/resource_loader.h" +#include "core/math/math_defs.h" #include "core/os/file_access.h" -#include "core/print_string.h" #include "core/project_settings.h" -#include "core/reference.h" -#include "core/script_language.h" #include "gdscript.h" -template <class T> -T *GDScriptParser::alloc_node() { - - T *t = memnew(T); - - t->next = list; - list = t; - - if (!head) - head = t; - - t->line = tokenizer->get_token_line(); - t->column = tokenizer->get_token_column(); - return t; -} - #ifdef DEBUG_ENABLED -static String _find_function_name(const GDScriptParser::OperatorNode *p_call); +#include "core/os/os.h" +#include "core/string_builder.h" #endif // DEBUG_ENABLED -bool GDScriptParser::_end_statement() { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) { - tokenizer->advance(); - return true; //handle next - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { - return true; //will be handled properly +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +static HashMap<StringName, Variant::Type> builtin_types; +Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { + if (builtin_types.empty()) { + builtin_types["bool"] = Variant::BOOL; + builtin_types["int"] = Variant::INT; + builtin_types["float"] = Variant::FLOAT; + builtin_types["String"] = Variant::STRING; + builtin_types["Vector2"] = Variant::VECTOR2; + builtin_types["Vector2i"] = Variant::VECTOR2I; + builtin_types["Rect2"] = Variant::RECT2; + builtin_types["Rect2i"] = Variant::RECT2I; + builtin_types["Transform2D"] = Variant::TRANSFORM2D; + builtin_types["Vector3"] = Variant::VECTOR3; + builtin_types["Vector3i"] = Variant::VECTOR3I; + builtin_types["AABB"] = Variant::AABB; + builtin_types["Plane"] = Variant::PLANE; + builtin_types["Quat"] = Variant::QUAT; + builtin_types["Basis"] = Variant::BASIS; + builtin_types["Transform"] = Variant::TRANSFORM; + builtin_types["Color"] = Variant::COLOR; + builtin_types["RID"] = Variant::_RID; + builtin_types["Object"] = Variant::OBJECT; + builtin_types["StringName"] = Variant::STRING_NAME; + builtin_types["NodePath"] = Variant::NODE_PATH; + builtin_types["Dictionary"] = Variant::DICTIONARY; + builtin_types["Callable"] = Variant::CALLABLE; + builtin_types["Signal"] = Variant::SIGNAL; + builtin_types["Array"] = Variant::ARRAY; + builtin_types["PackedByteArray"] = Variant::PACKED_BYTE_ARRAY; + builtin_types["PackedInt32Array"] = Variant::PACKED_INT32_ARRAY; + builtin_types["PackedInt64Array"] = Variant::PACKED_INT64_ARRAY; + builtin_types["PackedFloat32Array"] = Variant::PACKED_FLOAT32_ARRAY; + builtin_types["PackedFloat64Array"] = Variant::PACKED_FLOAT64_ARRAY; + builtin_types["PackedStringArray"] = Variant::PACKED_STRING_ARRAY; + builtin_types["PackedVector2Array"] = Variant::PACKED_VECTOR2_ARRAY; + builtin_types["PackedVector3Array"] = Variant::PACKED_VECTOR3_ARRAY; + builtin_types["PackedColorArray"] = Variant::PACKED_COLOR_ARRAY; + // NIL is not here, hence the -1. + if (builtin_types.size() != Variant::VARIANT_MAX - 1) { + ERR_PRINT("Outdated parser: amount of built-in types don't match the amount of types in Variant."); + } + } + + if (builtin_types.has(p_type)) { + return builtin_types[p_type]; + } + return Variant::VARIANT_MAX; +} + +void GDScriptParser::cleanup() { + builtin_types.clear(); +} + +GDScriptFunctions::Function GDScriptParser::get_builtin_function(const StringName &p_name) { + for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { + if (p_name == GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) { + return GDScriptFunctions::Function(i); + } + } + return GDScriptFunctions::FUNC_MAX; +} + +void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { + List<StringName> keys; + valid_annotations.get_key_list(&keys); + for (const List<StringName>::Element *E = keys.front(); E != nullptr; E = E->next()) { + r_annotations->push_back(valid_annotations[E->get()].info); } +} - return false; +GDScriptParser::GDScriptParser() { + // Register valid annotations. + // TODO: Should this be static? + // TODO: Validate applicable types (e.g. a VARIABLE annotation that only applies to string variables). + register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); + register_annotation(MethodInfo("@icon", { Variant::STRING, "icon_path" }), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); + register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); + // Export annotations. + register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_TYPE_STRING, Variant::NIL>); + register_annotation(MethodInfo("@export_enum", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, 0, true); + register_annotation(MethodInfo("@export_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, 1, true); + register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); + register_annotation(MethodInfo("@export_global_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, 1, true); + register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>); + register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); + register_annotation(MethodInfo("@export_placeholder"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); + register_annotation(MethodInfo("@export_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, 3); + register_annotation(MethodInfo("@export_exp_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_RANGE, Variant::FLOAT>, 3); + register_annotation(MethodInfo("@export_exp_easing", { Variant::STRING, "hint1" }, { Variant::STRING, "hint2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, 2); + register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); + register_annotation(MethodInfo("@export_node_path", { Variant::STRING, "type" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, 1, true); + register_annotation(MethodInfo("@export_flags", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, 0, true); + register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>); + register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>); + register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); + register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); + // Networking. + register_annotation(MethodInfo("@remote"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_REMOTE>); + register_annotation(MethodInfo("@master"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTER>); + register_annotation(MethodInfo("@puppet"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>); + register_annotation(MethodInfo("@remotesync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_REMOTESYNC>); + register_annotation(MethodInfo("@mastersync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTERSYNC>); + register_annotation(MethodInfo("@puppetsync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPETSYNC>); + // TODO: Warning annotations. } -bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { +GDScriptParser::~GDScriptParser() { + clear(); +} - if (tokenizer->get_token() != GDScriptTokenizer::TK_COLON) { - // report location at the previous token (on the previous line) - int error_line = tokenizer->get_token_line(-1); - int error_column = tokenizer->get_token_column(-1); - _set_error("':' expected at end of line.", error_line, error_column); - return false; +void GDScriptParser::clear() { + while (list != nullptr) { + Node *element = list; + list = list->next; + memdelete(element); } - tokenizer->advance(); - if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { - return false; - } + head = nullptr; + list = nullptr; + _is_tool = false; + for_completion = false; + errors.clear(); + multiline_stack.clear(); +} - if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) { - // be more python-like - IndentLevel current_level = indent_level.back()->get(); - indent_level.push_back(current_level); - return true; - //_set_error("newline expected after ':'."); - //return false; +void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { + // TODO: Improve error reporting by pointing at source code. + // TODO: Errors might point at more than one place at once (e.g. show previous declaration). + panic_mode = true; + // TODO: Improve positional information. + if (p_origin == nullptr) { + errors.push_back({ p_message, current.start_line, current.start_column }); + } else { + errors.push_back({ p_message, p_origin->start_line, p_origin->leftmost_column }); } +} - while (true) { - if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) { - - return false; //wtf - } else if (tokenizer->get_token(1) == GDScriptTokenizer::TK_EOF) { - return false; - } else if (tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) { - - int indent = tokenizer->get_token_line_indent(); - int tabs = tokenizer->get_token_line_tab_indent(); - IndentLevel current_level = indent_level.back()->get(); - IndentLevel new_indent(indent, tabs); - if (new_indent.is_mixed(current_level)) { - _set_error("Mixed tabs and spaces in indentation."); - return false; - } +#ifdef DEBUG_ENABLED +void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) { + Vector<String> symbols; + if (!p_symbol1.empty()) { + symbols.push_back(p_symbol1); + } + if (!p_symbol2.empty()) { + symbols.push_back(p_symbol2); + } + if (!p_symbol3.empty()) { + symbols.push_back(p_symbol3); + } + if (!p_symbol4.empty()) { + symbols.push_back(p_symbol4); + } + push_warning(p_source, p_code, symbols); +} - if (indent <= current_level.indent) { - return false; - } +void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) { + if (is_ignoring_warnings) { + return; + } + if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && script_path.begins_with("res://addons/")) { + return; + } - indent_level.push_back(new_indent); - tokenizer->advance(); - return true; + String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower(); + if (ignored_warnings.has(warn_name)) { + return; + } + if (!GLOBAL_GET("debug/gdscript/warnings/" + warn_name)) { + return; + } - } else if (p_block) { + GDScriptWarning warning; + warning.code = p_code; + warning.symbols = p_symbols; + warning.start_line = p_source->start_line; + warning.end_line = p_source->end_line; + warning.leftmost_column = p_source->leftmost_column; + warning.rightmost_column = p_source->rightmost_column; - NewLineNode *nl = alloc_node<NewLineNode>(); - nl->line = tokenizer->get_token_line(); - p_block->statements.push_back(nl); + List<GDScriptWarning>::Element *before = nullptr; + for (List<GDScriptWarning>::Element *E = warnings.front(); E != nullptr; E = E->next()) { + if (E->get().start_line > warning.start_line) { + break; } + before = E; + } + if (before) { + warnings.insert_after(before, warning); + } else { + warnings.push_front(warning); + } +} +#endif - tokenizer->advance(); // go to next newline +void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) { + if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { + return; + } + if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) { + return; } + CompletionContext context; + context.type = p_type; + context.current_class = current_class; + context.current_function = current_function; + context.current_suite = current_suite; + context.current_line = tokenizer.get_cursor_line(); + context.current_argument = p_argument; + context.node = p_node; + completion_context = context; } -bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete, bool p_parsing_constant) { +void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) { + if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { + return; + } + if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) { + return; + } + CompletionContext context; + context.type = p_type; + context.current_class = current_class; + context.current_function = current_function; + context.current_suite = current_suite; + context.current_line = tokenizer.get_cursor_line(); + context.builtin_type = p_builtin_type; + completion_context = context; +} - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - tokenizer->advance(); - } else { +void GDScriptParser::push_completion_call(Node *p_call) { + if (!for_completion) { + return; + } + CompletionCall call; + call.call = p_call; + call.argument = 0; + completion_call_stack.push_back(call); + if (previous.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_BEGINNING) { + completion_call = call; + } +} - parenthesis++; - int argidx = 0; +void GDScriptParser::pop_completion_call() { + if (!for_completion) { + return; + } + ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to pop empty completion call stack"); + completion_call_stack.pop_back(); +} - while (true) { +void GDScriptParser::set_last_completion_call_arg(int p_argument) { + if (!for_completion || passed_cursor) { + return; + } + ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to set argument on empty completion call stack"); + completion_call_stack.back()->get().argument = p_argument; +} - if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { - _make_completable_call(argidx); - completion_node = p_parent; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING && tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) { - //completing a string argument.. - completion_cursor = tokenizer->get_token_constant(); +Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) { + clear(); - _make_completable_call(argidx); - completion_node = p_parent; - tokenizer->advance(1); - return false; - } + String source = p_source_code; + int cursor_line = -1; + int cursor_column = -1; + for_completion = p_for_completion; - Node *arg = _parse_expression(p_parent, p_static, false, p_parsing_constant); - if (!arg) { - return false; + int tab_size = 4; +#ifdef TOOLS_ENABLED + if (EditorSettings::get_singleton()) { + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + } +#endif // TOOLS_ENABLED + + if (p_for_completion) { + // Remove cursor sentinel char. + const Vector<String> lines = p_source_code.split("\n"); + cursor_line = 1; + cursor_column = 1; + for (int i = 0; i < lines.size(); i++) { + bool found = false; + const String &line = lines[i]; + for (int j = 0; j < line.size(); j++) { + if (line[j] == CharType(0xFFFF)) { + found = true; + break; + } else if (line[j] == '\t') { + cursor_column += tab_size - 1; + } + cursor_column++; } - - p_args.push_back(arg); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - tokenizer->advance(); + if (found) { break; + } + cursor_line++; + cursor_column = 1; + } - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { + source = source.replace_first(String::chr(0xFFFF), String()); + } - if (tokenizer->get_token(1) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { + tokenizer.set_source_code(source); + tokenizer.set_cursor_position(cursor_line, cursor_column); + script_path = p_script_path; + current = tokenizer.scan(); + // Avoid error as the first token. + while (current.type == GDScriptTokenizer::Token::ERROR) { + push_error(current.literal); + current = tokenizer.scan(); + } - _set_error("Expression expected"); - return false; - } + push_multiline(false); // Keep one for the whole parsing. + parse_program(); + pop_multiline(); - tokenizer->advance(); - argidx++; - } else { - // something is broken - _set_error("Expected ',' or ')'"); - return false; - } - } - parenthesis--; +#ifdef DEBUG_ENABLED + if (multiline_stack.size() > 0) { + ERR_PRINT("Parser bug: Imbalanced multiline stack."); } +#endif - return true; + if (errors.empty()) { + return OK; + } else { + return ERR_PARSE_ERROR; + } } -void GDScriptParser::_make_completable_call(int p_arg) { - - completion_cursor = StringName(); - completion_type = COMPLETION_CALL_ARGUMENTS; - completion_class = current_class; - completion_function = current_function; - completion_line = tokenizer->get_token_line(); - completion_argument = p_arg; - completion_block = current_block; - completion_found = true; - tokenizer->advance(); +GDScriptTokenizer::Token GDScriptParser::advance() { + if (current.type == GDScriptTokenizer::Token::TK_EOF) { + ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream."); + } + if (for_completion && !completion_call_stack.empty()) { + if (completion_call.call == nullptr && tokenizer.is_past_cursor()) { + completion_call = completion_call_stack.back()->get(); + passed_cursor = true; + } + } + previous = current; + current = tokenizer.scan(); + while (current.type == GDScriptTokenizer::Token::ERROR) { + push_error(current.literal); + current = tokenizer.scan(); + } + return previous; } -bool GDScriptParser::_get_completable_identifier(CompletionType p_type, StringName &identifier) { - - identifier = StringName(); - if (tokenizer->is_token_literal()) { - identifier = tokenizer->get_token_literal(); - tokenizer->advance(); +bool GDScriptParser::match(GDScriptTokenizer::Token::Type p_token_type) { + if (!check(p_token_type)) { + return false; } - if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { - - completion_cursor = identifier; - completion_type = p_type; - completion_class = current_class; - completion_function = current_function; - completion_line = tokenizer->get_token_line(); - completion_block = current_block; - completion_found = true; - completion_ident_is_call = false; - tokenizer->advance(); + advance(); + return true; +} - if (tokenizer->is_token_literal()) { - identifier = identifier.operator String() + tokenizer->get_token_literal().operator String(); - tokenizer->advance(); - } +bool GDScriptParser::check(GDScriptTokenizer::Token::Type p_token_type) { + if (p_token_type == GDScriptTokenizer::Token::IDENTIFIER) { + return current.is_identifier(); + } + return current.type == p_token_type; +} - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - completion_ident_is_call = true; - } +bool GDScriptParser::consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message) { + if (match(p_token_type)) { return true; } - + push_error(p_error_message); return false; } -GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_static, bool p_allow_assign, bool p_parsing_constant) { - - //Vector<Node*> expressions; - //Vector<OperatorNode::Operator> operators; - - Vector<Expression> expression; - - Node *expr = nullptr; - - int op_line = tokenizer->get_token_line(); // when operators are created at the bottom, the line might have been changed (\n found) - - while (true) { - - /*****************/ - /* Parse Operand */ - /*****************/ +bool GDScriptParser::is_at_end() { + return check(GDScriptTokenizer::Token::TK_EOF); +} - if (parenthesis > 0) { - //remove empty space (only allowed if inside parenthesis - while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { - tokenizer->advance(); - } +void GDScriptParser::synchronize() { + panic_mode = false; + while (!is_at_end()) { + if (previous.type == GDScriptTokenizer::Token::NEWLINE || previous.type == GDScriptTokenizer::Token::SEMICOLON) { + return; } - // Check that the next token is not TK_CURSOR and if it is, the offset should be incremented. - int next_valid_offset = 1; - if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_CURSOR) { - next_valid_offset++; - // There is a chunk of the identifier that also needs to be ignored (not always there!) - if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_IDENTIFIER) { - next_valid_offset++; - } + switch (current.type) { + case GDScriptTokenizer::Token::CLASS: + case GDScriptTokenizer::Token::FUNC: + case GDScriptTokenizer::Token::STATIC: + case GDScriptTokenizer::Token::VAR: + case GDScriptTokenizer::Token::CONST: + case GDScriptTokenizer::Token::SIGNAL: + //case GDScriptTokenizer::Token::IF: // Can also be inside expressions. + case GDScriptTokenizer::Token::FOR: + case GDScriptTokenizer::Token::WHILE: + case GDScriptTokenizer::Token::MATCH: + case GDScriptTokenizer::Token::RETURN: + case GDScriptTokenizer::Token::ANNOTATION: + return; + default: + // Do nothing. + break; } - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - //subexpression () - tokenizer->advance(); - parenthesis++; - Node *subexpr = _parse_expression(p_parent, p_static, p_allow_assign, p_parsing_constant); - parenthesis--; - if (!subexpr) - return nullptr; - - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - - _set_error("Expected ')' in expression"); - return nullptr; - } - - tokenizer->advance(); - expr = subexpr; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_DOLLAR) { - tokenizer->advance(); - - String path; - - bool need_identifier = true; - bool done = false; - int line = tokenizer->get_token_line(); - - while (!done) { - - switch (tokenizer->get_token()) { - case GDScriptTokenizer::TK_CURSOR: { - completion_type = COMPLETION_GET_NODE; - completion_class = current_class; - completion_function = current_function; - completion_line = tokenizer->get_token_line(); - completion_cursor = path; - completion_argument = 0; - completion_block = current_block; - completion_found = true; - tokenizer->advance(); - } break; - case GDScriptTokenizer::TK_CONSTANT: { - - if (!need_identifier) { - done = true; - break; - } - - if (tokenizer->get_token_constant().get_type() != Variant::STRING) { - _set_error("Expected string constant or identifier after '$' or '/'."); - return nullptr; - } - - path += String(tokenizer->get_token_constant()); - tokenizer->advance(); - need_identifier = false; - - } break; - case GDScriptTokenizer::TK_OP_DIV: { - - if (need_identifier) { - done = true; - break; - } + advance(); + } +} - path += "/"; - tokenizer->advance(); - need_identifier = true; - - } break; - default: { - // Instead of checking for TK_IDENTIFIER, we check with is_token_literal, as this allows us to use match/sync/etc. as a name - if (need_identifier && tokenizer->is_token_literal()) { - path += String(tokenizer->get_token_literal()); - tokenizer->advance(); - need_identifier = false; - } else { - done = true; - } +void GDScriptParser::push_multiline(bool p_state) { + multiline_stack.push_back(p_state); + tokenizer.set_multiline_mode(p_state); + if (p_state) { + // Consume potential whitespace tokens already waiting in line. + while (current.type == GDScriptTokenizer::Token::NEWLINE || current.type == GDScriptTokenizer::Token::INDENT || current.type == GDScriptTokenizer::Token::DEDENT) { + current = tokenizer.scan(); // Don't call advance() here, as we don't want to change the previous token. + } + } +} - break; - } - } - } +void GDScriptParser::pop_multiline() { + ERR_FAIL_COND_MSG(multiline_stack.size() == 0, "Parser bug: trying to pop from multiline stack without available value."); + multiline_stack.pop_back(); + tokenizer.set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false); +} - if (path == "") { - _set_error("Path expected after $."); - return nullptr; - } +bool GDScriptParser::is_statement_end() { + return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON); +} - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_CALL; - op->line = line; - op->arguments.push_back(alloc_node<SelfNode>()); - op->arguments[0]->line = line; - - IdentifierNode *funcname = alloc_node<IdentifierNode>(); - funcname->name = "get_node"; - funcname->line = line; - op->arguments.push_back(funcname); - - ConstantNode *nodepath = alloc_node<ConstantNode>(); - nodepath->value = NodePath(StringName(path)); - nodepath->datatype = _type_from_variant(nodepath->value); - nodepath->line = line; - op->arguments.push_back(nodepath); - - expr = op; - - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { - tokenizer->advance(); - continue; //no point in cursor in the middle of expression - - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) { - - //constant defined by tokenizer - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = tokenizer->get_token_constant(); - constant->datatype = _type_from_variant(constant->value); - tokenizer->advance(); - expr = constant; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_PI) { - - //constant defined by tokenizer - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = Math_PI; - constant->datatype = _type_from_variant(constant->value); - tokenizer->advance(); - expr = constant; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_TAU) { +void GDScriptParser::end_statement(const String &p_context) { + bool found = false; + while (is_statement_end()) { + // Remove sequential newlines/semicolons. + found = true; + advance(); + } + if (!found) { + push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name())); + } +} - //constant defined by tokenizer - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = Math_TAU; - constant->datatype = _type_from_variant(constant->value); - tokenizer->advance(); - expr = constant; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_INF) { +void GDScriptParser::parse_program() { + if (current.type == GDScriptTokenizer::Token::TK_EOF) { + // Empty file. + push_error("Source file is empty."); + return; + } - //constant defined by tokenizer - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = Math_INF; - constant->datatype = _type_from_variant(constant->value); - tokenizer->advance(); - expr = constant; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_NAN) { + head = alloc_node<ClassNode>(); + current_class = head; - //constant defined by tokenizer - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = Math_NAN; - constant->datatype = _type_from_variant(constant->value); - tokenizer->advance(); - expr = constant; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_PRELOAD) { - - //constant defined by tokenizer - tokenizer->advance(); - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - _set_error("Expected '(' after 'preload'"); - return nullptr; + 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.)"); } - tokenizer->advance(); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { - completion_cursor = StringName(); - completion_node = p_parent; - completion_type = COMPLETION_RESOURCE_PATH; - completion_class = current_class; - completion_function = current_function; - completion_line = tokenizer->get_token_line(); - completion_block = current_block; - completion_argument = 0; - completion_found = true; - tokenizer->advance(); - } - - String path; - bool found_constant = false; - bool valid = false; - ConstantNode *cn; - - Node *subexpr = _parse_and_reduce_expression(p_parent, p_static); - if (subexpr) { - if (subexpr->type == Node::TYPE_CONSTANT) { - cn = static_cast<ConstantNode *>(subexpr); - found_constant = true; - } - if (subexpr->type == Node::TYPE_IDENTIFIER) { - IdentifierNode *in = static_cast<IdentifierNode *>(subexpr); - - // Try to find the constant expression by the identifier - if (current_class->constant_expressions.has(in->name)) { - Node *cn_exp = current_class->constant_expressions[in->name].expression; - if (cn_exp->type == Node::TYPE_CONSTANT) { - cn = static_cast<ConstantNode *>(cn_exp); - found_constant = true; - } - } - } + // @tool annotation has no specific target. + annotation->apply(this, nullptr); + } else { + annotation_stack.push_back(annotation); + } + } - if (found_constant && cn->value.get_type() == Variant::STRING) { - valid = true; - path = (String)cn->value; + for (bool should_break = false; !should_break;) { + // Order here doesn't matter, but there should be only one of each at most. + switch (current.type) { + case GDScriptTokenizer::Token::CLASS_NAME: + if (!annotation_stack.empty()) { + push_error(R"("class_name" should be used before annotations.)"); } - } - - if (!valid) { - _set_error("expected string constant as 'preload' argument."); - return nullptr; - } - - if (!path.is_abs_path() && base_path != "") - path = base_path.plus_file(path); - path = path.replace("///", "//").simplify_path(); - if (path == self_path) { - - _set_error("Can't preload itself (use 'get_script()')."); - return nullptr; - } - - Ref<Resource> res; - dependencies.push_back(path); - if (!dependencies_only) { - if (!validating) { - - //this can be too slow for just validating code - if (for_completion && ScriptCodeCompletionCache::get_singleton() && FileAccess::exists(path)) { - res = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path); - } else if (!for_completion || FileAccess::exists(path)) { - res = ResourceLoader::load(path); - } + advance(); + if (head->identifier != nullptr) { + push_error(R"("class_name" can only be used once.)"); } else { - - if (!FileAccess::exists(path)) { - _set_error("Can't preload resource at path: " + path); - return nullptr; - } else if (ScriptCodeCompletionCache::get_singleton()) { - res = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path); - } + parse_class_name(); } - if (!res.is_valid()) { - _set_error("Can't preload resource at path: " + path); - return nullptr; - } - } - - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ')' after 'preload' path"); - return nullptr; - } - - Ref<GDScript> gds = res; - if (gds.is_valid() && !gds->is_valid()) { - _set_error("Couldn't fully preload the script, possible cyclic reference or compilation error. Use \"load()\" instead if a cyclic reference is intended."); - return nullptr; - } - - tokenizer->advance(); - - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = res; - constant->datatype = _type_from_variant(constant->value); - - expr = constant; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_YIELD) { - - if (!current_function) { - _set_error("\"yield()\" can only be used inside function blocks."); - return nullptr; - } - - current_function->has_yield = true; - - tokenizer->advance(); - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - _set_error("Expected \"(\" after \"yield\"."); - return nullptr; - } - - tokenizer->advance(); - - OperatorNode *yield = alloc_node<OperatorNode>(); - yield->op = OperatorNode::OP_YIELD; - - while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { - tokenizer->advance(); - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - expr = yield; - tokenizer->advance(); - } else { - - parenthesis++; - - Node *object = _parse_and_reduce_expression(p_parent, p_static); - if (!object) - return nullptr; - yield->arguments.push_back(object); - - if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - _set_error("Expected \",\" after the first argument of \"yield\"."); - return nullptr; + break; + case GDScriptTokenizer::Token::EXTENDS: + if (!annotation_stack.empty()) { + push_error(R"("extends" should be used before annotations.)"); } - - tokenizer->advance(); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { - - completion_cursor = StringName(); - completion_node = object; - completion_type = COMPLETION_YIELD; - completion_class = current_class; - completion_function = current_function; - completion_line = tokenizer->get_token_line(); - completion_argument = 0; - completion_block = current_block; - completion_found = true; - tokenizer->advance(); + advance(); + if (head->extends_used) { + push_error(R"("extends" can only be used once.)"); + } else { + parse_extends(); + end_statement("superclass"); } + break; + default: + should_break = true; + break; + } - Node *signal = _parse_and_reduce_expression(p_parent, p_static); - if (!signal) - return nullptr; - yield->arguments.push_back(signal); + if (panic_mode) { + synchronize(); + } + } - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected \")\" after the second argument of \"yield\"."); - return nullptr; + if (match(GDScriptTokenizer::Token::ANNOTATION)) { + // Check for @icon annotation. + AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); + if (annotation != nullptr) { + if (annotation->name == "@icon") { + if (previous.type != GDScriptTokenizer::Token::NEWLINE) { + push_error(R"(Expected newline after "@icon" annotation.)"); } - - parenthesis--; - - tokenizer->advance(); - - expr = yield; - } - - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_SELF) { - - if (p_static) { - _set_error("\"self\" isn't allowed in a static function or constant expression."); - return nullptr; - } - //constant defined by tokenizer - SelfNode *self = alloc_node<SelfNode>(); - tokenizer->advance(); - expr = self; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) { - - Variant::Type bi_type = tokenizer->get_token_type(); - tokenizer->advance(2); - - StringName identifier; - - if (_get_completable_identifier(COMPLETION_BUILT_IN_TYPE_CONSTANT, identifier)) { - - completion_built_in_constant = bi_type; - } - - if (identifier == StringName()) { - - _set_error("Built-in type constant or static function expected after \".\"."); - return nullptr; + annotation->apply(this, head); + } else { + annotation_stack.push_back(annotation); } - if (!Variant::has_constant(bi_type, identifier)) { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN && - Variant::is_method_const(bi_type, identifier) && - Variant::get_method_return_type(bi_type, identifier) == bi_type) { - - tokenizer->advance(); + } + } - OperatorNode *construct = alloc_node<OperatorNode>(); - construct->op = OperatorNode::OP_CALL; + parse_class_body(); - TypeNode *tn = alloc_node<TypeNode>(); - tn->vtype = bi_type; - construct->arguments.push_back(tn); + if (!check(GDScriptTokenizer::Token::TK_EOF)) { + push_error("Expected end of file."); + } - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_CALL; - op->arguments.push_back(construct); + clear_unused_annotations(); +} - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = identifier; - op->arguments.push_back(id); +GDScriptParser::ClassNode *GDScriptParser::parse_class() { + ClassNode *n_class = alloc_node<ClassNode>(); - if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) - return nullptr; + ClassNode *previous_class = current_class; + current_class = n_class; + n_class->outer = previous_class; - expr = op; - } else { - // Object is a special case - bool valid = false; - if (bi_type == Variant::OBJECT) { - int object_constant = ClassDB::get_integer_constant("Object", identifier, &valid); - if (valid) { - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = object_constant; - cn->datatype = _type_from_variant(cn->value); - expr = cn; - } - } - if (!valid) { - _set_error("Static constant '" + identifier.operator String() + "' not present in built-in type " + Variant::get_type_name(bi_type) + "."); - return nullptr; - } - } - } else { + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) { + n_class->identifier = parse_identifier(); + } - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = Variant::get_constant_value(bi_type, identifier); - cn->datatype = _type_from_variant(cn->value); - expr = cn; - } + if (match(GDScriptTokenizer::Token::EXTENDS)) { + parse_extends(); + } - } else if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) { - // We check with is_token_literal, as this allows us to use match/sync/etc. as a name - //function or constructor - - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_CALL; - - //Do a quick Array and Dictionary Check. Replace if either require no arguments. - bool replaced = false; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) { - Variant::Type ct = tokenizer->get_token_type(); - if (!p_parsing_constant) { - if (ct == Variant::ARRAY) { - if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - ArrayNode *arr = alloc_node<ArrayNode>(); - expr = arr; - replaced = true; - tokenizer->advance(3); - } - } - if (ct == Variant::DICTIONARY) { - if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - DictionaryNode *dict = alloc_node<DictionaryNode>(); - expr = dict; - replaced = true; - tokenizer->advance(3); - } - } - } + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after class declaration.)"); + consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected newline after class declaration.)"); - if (!replaced) { - TypeNode *tn = alloc_node<TypeNode>(); - tn->vtype = tokenizer->get_token_type(); - op->arguments.push_back(tn); - tokenizer->advance(2); - } - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_FUNC) { + if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) { + current_class = previous_class; + return n_class; + } - BuiltInFunctionNode *bn = alloc_node<BuiltInFunctionNode>(); - bn->function = tokenizer->get_token_built_in_func(); - op->arguments.push_back(bn); - tokenizer->advance(2); - } else { + parse_class_body(); - SelfNode *self = alloc_node<SelfNode>(); - op->arguments.push_back(self); + consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)"); - StringName identifier; - if (_get_completable_identifier(COMPLETION_FUNCTION, identifier)) { - } + current_class = previous_class; + return n_class; +} - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = identifier; - op->arguments.push_back(id); - tokenizer->advance(1); - } +void GDScriptParser::parse_class_name() { + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the global class name after "class_name".)")) { + current_class->identifier = parse_identifier(); + } - if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { - _make_completable_call(0); - completion_node = op; - } - if (!replaced) { - if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) - return nullptr; - expr = op; - } - } else if (tokenizer->is_token_literal(0, true)) { - // We check with is_token_literal, as this allows us to use match/sync/etc. as a name - //identifier (reference) - - const ClassNode *cln = current_class; - bool bfn = false; - StringName identifier; - int id_line = tokenizer->get_token_line(); - if (_get_completable_identifier(COMPLETION_IDENTIFIER, identifier)) { + // TODO: Move this to annotation + if (match(GDScriptTokenizer::Token::COMMA)) { + // Icon path. + if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected class icon path string after ",".)")) { + if (previous.literal.get_type() != Variant::STRING) { + push_error(vformat(R"(Only strings can be used for the class icon path, found "%s" instead.)", Variant::get_type_name(previous.literal.get_type()))); } + current_class->icon_path = previous.literal; + } + } - BlockNode *b = current_block; - while (!bfn && b) { - if (b->variables.has(identifier)) { - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = identifier; - id->declared_block = b; - id->line = id_line; - expr = id; - bfn = true; + if (match(GDScriptTokenizer::Token::EXTENDS)) { + // Allow extends on the same line. + parse_extends(); + end_statement("superclass"); + } else { + end_statement("class_name statement"); + } +} -#ifdef DEBUG_ENABLED - LocalVarNode *lv = b->variables[identifier]; - switch (tokenizer->get_token()) { - case GDScriptTokenizer::TK_OP_ASSIGN_ADD: - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: - case GDScriptTokenizer::TK_OP_ASSIGN_DIV: - case GDScriptTokenizer::TK_OP_ASSIGN_MOD: - case GDScriptTokenizer::TK_OP_ASSIGN_MUL: - case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: - case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: - case GDScriptTokenizer::TK_OP_ASSIGN_SUB: { - if (lv->assignments == 0) { - if (!lv->datatype.has_type) { - _set_error("Using assignment with operation on a variable that was never assigned."); - return nullptr; - } - _add_warning(GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, -1, identifier.operator String()); - } - [[fallthrough]]; - } - case GDScriptTokenizer::TK_OP_ASSIGN: { - lv->assignments += 1; - lv->usages--; // Assignment is not really usage - } break; - default: { - lv->usages++; - } - } -#endif // DEBUG_ENABLED - break; - } - b = b->parent_block; - } +void GDScriptParser::parse_extends() { + current_class->extends_used = true; - if (!bfn && p_parsing_constant) { - if (cln->constant_expressions.has(identifier)) { - expr = cln->constant_expressions[identifier].expression; - bfn = true; - } else if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { - //check from constants - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = GDScriptLanguage::get_singleton()->get_global_array()[GDScriptLanguage::get_singleton()->get_global_map()[identifier]]; - constant->datatype = _type_from_variant(constant->value); - constant->line = id_line; - expr = constant; - bfn = true; - } + int chain_index = 0; - if (!bfn && GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { - //check from singletons - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = GDScriptLanguage::get_singleton()->get_named_globals_map()[identifier]; - expr = constant; - bfn = true; - } + if (match(GDScriptTokenizer::Token::LITERAL)) { + if (previous.literal.get_type() != Variant::STRING) { + push_error(vformat(R"(Only strings or identifiers can be used after "extends", found "%s" instead.)", Variant::get_type_name(previous.literal.get_type()))); + } + current_class->extends_path = previous.literal; - if (!dependencies_only) { - if (!bfn && ScriptServer::is_global_class(identifier)) { - Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); - if (scr.is_valid() && scr->is_valid()) { - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = scr; - expr = constant; - bfn = true; - } - } + if (!match(GDScriptTokenizer::Token::PERIOD)) { + end_statement("superclass path"); + return; + } + } - // Check parents for the constant - if (!bfn) { - // Using current_class instead of cln here, since cln is const* - _determine_inheritance(current_class, false); - if (cln->base_type.has_type && cln->base_type.kind == DataType::GDSCRIPT && cln->base_type.script_type->is_valid()) { - Map<StringName, Variant> parent_constants; - current_class->base_type.script_type->get_constants(&parent_constants); - if (parent_constants.has(identifier)) { - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = parent_constants[identifier]; - expr = constant; - bfn = true; - } - } - } - } - } + make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++); - if (!bfn) { -#ifdef DEBUG_ENABLED - if (current_function) { - int arg_idx = current_function->arguments.find(identifier); - if (arg_idx != -1) { - switch (tokenizer->get_token()) { - case GDScriptTokenizer::TK_OP_ASSIGN_ADD: - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: - case GDScriptTokenizer::TK_OP_ASSIGN_DIV: - case GDScriptTokenizer::TK_OP_ASSIGN_MOD: - case GDScriptTokenizer::TK_OP_ASSIGN_MUL: - case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: - case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: - case GDScriptTokenizer::TK_OP_ASSIGN_SUB: - case GDScriptTokenizer::TK_OP_ASSIGN: { - // Assignment is not really usage - } break; - default: { - current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1; - } - } - } - } -#endif // DEBUG_ENABLED - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = identifier; - id->line = id_line; - expr = id; - } + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) { + return; + } + current_class->extends.push_back(previous.literal); - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ADD || tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB || tokenizer->get_token() == GDScriptTokenizer::TK_OP_NOT || tokenizer->get_token() == GDScriptTokenizer::TK_OP_BIT_INVERT) { + while (match(GDScriptTokenizer::Token::PERIOD)) { + make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) { + return; + } + current_class->extends.push_back(previous.literal); + } +} - //single prefix operators like !expr +expr -expr ++expr --expr - alloc_node<OperatorNode>(); - Expression e; - e.is_op = true; +template <class T> +void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) { + advance(); + T *member = (this->*p_parse_function)(); + if (member == nullptr) { + return; + } + // Consume annotations. + while (!annotation_stack.empty()) { + AnnotationNode *last_annotation = annotation_stack.back()->get(); + if (last_annotation->applies_to(p_target)) { + last_annotation->apply(this, member); + member->annotations.push_front(last_annotation); + annotation_stack.pop_back(); + } else { + push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind)); + clear_unused_annotations(); + return; + } + } + if (member->identifier != nullptr) { + // Enums may be unnamed. + // TODO: Consider names in outer scope too, for constants and classes (and static functions?) + if (current_class->members_indices.has(member->identifier->name)) { + push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier); + } else { + current_class->add_member(member); + } + } +} - switch (tokenizer->get_token()) { - case GDScriptTokenizer::TK_OP_ADD: e.op = OperatorNode::OP_POS; break; - case GDScriptTokenizer::TK_OP_SUB: e.op = OperatorNode::OP_NEG; break; - case GDScriptTokenizer::TK_OP_NOT: e.op = OperatorNode::OP_NOT; break; - case GDScriptTokenizer::TK_OP_BIT_INVERT: e.op = OperatorNode::OP_BIT_INVERT; break; - default: { +void GDScriptParser::parse_class_body() { + bool class_end = false; + while (!class_end && !is_at_end()) { + switch (current.type) { + case GDScriptTokenizer::Token::VAR: + parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable"); + break; + case GDScriptTokenizer::Token::CONST: + parse_class_member(&GDScriptParser::parse_constant, AnnotationInfo::CONSTANT, "constant"); + break; + case GDScriptTokenizer::Token::SIGNAL: + parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal"); + break; + case GDScriptTokenizer::Token::STATIC: + case GDScriptTokenizer::Token::FUNC: + parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function"); + break; + case GDScriptTokenizer::Token::CLASS: + parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class"); + break; + case GDScriptTokenizer::Token::ENUM: + parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum"); + break; + case GDScriptTokenizer::Token::ANNOTATION: { + advance(); + AnnotationNode *annotation = parse_annotation(AnnotationInfo::CLASS_LEVEL); + if (annotation != nullptr) { + annotation_stack.push_back(annotation); } + break; } + case GDScriptTokenizer::Token::PASS: + advance(); + end_statement(R"("pass")"); + break; + case GDScriptTokenizer::Token::DEDENT: + class_end = true; + break; + default: + push_error(vformat(R"(Unexpected "%s" in class body.)", current.get_name())); + advance(); + break; + } + if (panic_mode) { + synchronize(); + } + } +} - tokenizer->advance(); +GDScriptParser::VariableNode *GDScriptParser::parse_variable() { + return parse_variable(true); +} - if (e.op != OperatorNode::OP_NOT && tokenizer->get_token() == GDScriptTokenizer::TK_OP_NOT) { - _set_error("Misplaced 'not'."); - return nullptr; - } +GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_property) { + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) { + return nullptr; + } - expression.push_back(e); - continue; //only exception, must continue... + VariableNode *variable = alloc_node<VariableNode>(); + variable->identifier = parse_identifier(); - /* - Node *subexpr=_parse_expression(op,p_static); - if (!subexpr) - return nullptr; - op->arguments.push_back(subexpr); - expr=op;*/ + if (match(GDScriptTokenizer::Token::COLON)) { + if (check(GDScriptTokenizer::Token::NEWLINE)) { + if (p_allow_property) { + advance(); - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_IS && tokenizer->get_token(1) == GDScriptTokenizer::TK_BUILT_IN_TYPE) { - // 'is' operator with built-in type - if (!expr) { - _set_error("Expected identifier before 'is' operator"); + return parse_property(variable, true); + } else { + push_error(R"(Expected type after ":")"); return nullptr; } - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_IS_BUILTIN; - op->arguments.push_back(expr); - - tokenizer->advance(); - - TypeNode *tn = alloc_node<TypeNode>(); - tn->vtype = tokenizer->get_token_type(); - op->arguments.push_back(tn); - tokenizer->advance(); - - expr = op; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_OPEN) { - // array - tokenizer->advance(); - - ArrayNode *arr = alloc_node<ArrayNode>(); - bool expecting_comma = false; - - while (true) { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { - - _set_error("Unterminated array"); - return nullptr; - - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) { - tokenizer->advance(); - break; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { - - tokenizer->advance(); //ignore newline - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - if (!expecting_comma) { - _set_error("expression or ']' expected"); - return nullptr; - } - - expecting_comma = false; - tokenizer->advance(); //ignore newline - } else { - //parse expression - if (expecting_comma) { - _set_error("',' or ']' expected"); - return nullptr; + } else if (check((GDScriptTokenizer::Token::EQUAL))) { + // Infer type. + variable->infer_datatype = true; + } else { + if (p_allow_property) { + make_completion_context(COMPLETION_PROPERTY_DECLARATION_OR_TYPE, variable); + if (check(GDScriptTokenizer::Token::IDENTIFIER)) { + // Check if get or set. + if (current.get_identifier() == "get" || current.get_identifier() == "set") { + return parse_property(variable, false); } - Node *n = _parse_expression(arr, p_static, p_allow_assign, p_parsing_constant); - if (!n) - return nullptr; - arr->elements.push_back(n); - expecting_comma = true; } } - expr = arr; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) { - // array - tokenizer->advance(); - - DictionaryNode *dict = alloc_node<DictionaryNode>(); - - enum DictExpect { - - DICT_EXPECT_KEY, - DICT_EXPECT_COLON, - DICT_EXPECT_VALUE, - DICT_EXPECT_COMMA - - }; - - Node *key = nullptr; - Set<Variant> keys; - - DictExpect expecting = DICT_EXPECT_KEY; - - while (true) { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { - - _set_error("Unterminated dictionary"); - return nullptr; - - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { + // Parse type. + variable->datatype_specifier = parse_type(); + } + } - if (expecting == DICT_EXPECT_COLON) { - _set_error("':' expected"); - return nullptr; - } - if (expecting == DICT_EXPECT_VALUE) { - _set_error("value expected"); - return nullptr; - } - tokenizer->advance(); - break; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { + if (match(GDScriptTokenizer::Token::EQUAL)) { + // Initializer. + variable->initializer = parse_expression(false); + variable->assignments++; + } - tokenizer->advance(); //ignore newline - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { + if (p_allow_property && match(GDScriptTokenizer::Token::COLON)) { + if (match(GDScriptTokenizer::Token::NEWLINE)) { + return parse_property(variable, true); + } else { + return parse_property(variable, false); + } + } - if (expecting == DICT_EXPECT_KEY) { - _set_error("key or '}' expected"); - return nullptr; - } - if (expecting == DICT_EXPECT_VALUE) { - _set_error("value expected"); - return nullptr; - } - if (expecting == DICT_EXPECT_COLON) { - _set_error("':' expected"); - return nullptr; - } + end_statement("variable declaration"); - expecting = DICT_EXPECT_KEY; - tokenizer->advance(); //ignore newline + variable->export_info.name = variable->identifier->name; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { + return variable; +} - if (expecting == DICT_EXPECT_KEY) { - _set_error("key or '}' expected"); - return nullptr; - } - if (expecting == DICT_EXPECT_VALUE) { - _set_error("value expected"); - return nullptr; - } - if (expecting == DICT_EXPECT_COMMA) { - _set_error("',' or '}' expected"); - return nullptr; - } +GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_variable, bool p_need_indent) { + if (p_need_indent) { + if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block for property after ":".)")) { + return nullptr; + } + } - expecting = DICT_EXPECT_VALUE; - tokenizer->advance(); //ignore newline - } else { + VariableNode *property = p_variable; - if (expecting == DICT_EXPECT_COMMA) { - _set_error("',' or '}' expected"); - return nullptr; - } - if (expecting == DICT_EXPECT_COLON) { - _set_error("':' expected"); - return nullptr; - } + make_completion_context(COMPLETION_PROPERTY_DECLARATION, property); - if (expecting == DICT_EXPECT_KEY) { - - if (tokenizer->is_token_literal() && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { - // We check with is_token_literal, as this allows us to use match/sync/etc. as a name - //lua style identifier, easier to write - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = tokenizer->get_token_literal(); - cn->datatype = _type_from_variant(cn->value); - key = cn; - tokenizer->advance(2); - expecting = DICT_EXPECT_VALUE; - } else { - //python/js style more flexible - key = _parse_expression(dict, p_static, p_allow_assign, p_parsing_constant); - if (!key) - return nullptr; - expecting = DICT_EXPECT_COLON; - } - } + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected "get" or "set" for property declaration.)")) { + return nullptr; + } - if (expecting == DICT_EXPECT_VALUE) { - Node *value = _parse_expression(dict, p_static, p_allow_assign, p_parsing_constant); - if (!value) - return nullptr; - expecting = DICT_EXPECT_COMMA; + IdentifierNode *function = parse_identifier(); - if (key->type == GDScriptParser::Node::TYPE_CONSTANT) { - Variant const &keyName = static_cast<const GDScriptParser::ConstantNode *>(key)->value; + if (check(GDScriptTokenizer::Token::EQUAL)) { + p_variable->property = VariableNode::PROP_SETGET; + } else { + p_variable->property = VariableNode::PROP_INLINE; + if (!p_need_indent) { + push_error("Property with inline code must go to an indented block."); + } + } - if (keys.has(keyName)) { - _set_error("Duplicate key found in Dictionary literal"); - return nullptr; - } - keys.insert(keyName); - } + bool getter_used = false; + bool setter_used = false; - DictionaryNode::Pair pair; - pair.key = key; - pair.value = value; - dict->elements.push_back(pair); - key = nullptr; - } - } + // Run with a loop because order doesn't matter. + for (int i = 0; i < 2; i++) { + if (function->name == "set") { + if (setter_used) { + push_error(R"(Properties can only have one setter.)"); + } else { + parse_property_setter(property); + setter_used = true; } - - expr = dict; - - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR)) { - // We check with is_token_literal, as this allows us to use match/sync/etc. as a name - // parent call - - tokenizer->advance(); //goto identifier - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_PARENT_CALL; - - /*SelfNode *self = alloc_node<SelfNode>(); - op->arguments.push_back(self); - forbidden for now */ - StringName identifier; - bool is_completion = _get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier) && for_completion; - - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = identifier; - op->arguments.push_back(id); - - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - if (!is_completion) { - _set_error("Expected '(' for parent function call."); - return nullptr; - } + } else if (function->name == "get") { + if (getter_used) { + push_error(R"(Properties can only have one getter.)"); } else { - tokenizer->advance(); - if (!_parse_arguments(op, op->arguments, p_static, false, p_parsing_constant)) { - return nullptr; - } + parse_property_getter(property); + getter_used = true; } - - expr = op; - - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && expression.size() > 0 && expression[expression.size() - 1].is_op && expression[expression.size() - 1].op == OperatorNode::OP_IS) { - Expression e = expression[expression.size() - 1]; - e.op = OperatorNode::OP_IS_BUILTIN; - expression.write[expression.size() - 1] = e; - - TypeNode *tn = alloc_node<TypeNode>(); - tn->vtype = tokenizer->get_token_type(); - expr = tn; - tokenizer->advance(); } else { - - //find list [ or find dictionary { - _set_error("Error parsing expression, misplaced: " + String(tokenizer->get_token_name(tokenizer->get_token()))); - return nullptr; //nothing + // TODO: Update message to only have the missing one if it's the case. + push_error(R"(Expected "get" or "set" for property declaration.)"); } - ERR_FAIL_COND_V_MSG(!expr, nullptr, "GDScriptParser bug, couldn't figure out what expression is."); - - /******************/ - /* Parse Indexing */ - /******************/ - - while (true) { - - //expressions can be indexed any number of times - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) { - - //indexing using "." - - if (tokenizer->get_token(1) != GDScriptTokenizer::TK_CURSOR && !tokenizer->is_token_literal(1)) { - // We check with is_token_literal, as this allows us to use match/sync/etc. as a name - _set_error("Expected identifier as member"); - return nullptr; - } else if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - //call!! - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_CALL; - - tokenizer->advance(); - - IdentifierNode *id = alloc_node<IdentifierNode>(); - StringName identifier; - if (_get_completable_identifier(COMPLETION_METHOD, identifier)) { - completion_node = op; - //indexing stuff - } - - id->name = identifier; - - op->arguments.push_back(expr); // call what - op->arguments.push_back(id); // call func - //get arguments - tokenizer->advance(1); - if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { - _make_completable_call(0); - completion_node = op; - } - if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) - return nullptr; - expr = op; - - } else { - //simple indexing! - - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_INDEX_NAMED; - tokenizer->advance(); - - StringName identifier; - if (_get_completable_identifier(COMPLETION_INDEX, identifier)) { - - if (identifier == StringName()) { - identifier = "@temp"; //so it parses alright - } - completion_node = op; - - //indexing stuff + if (i == 0 && p_variable->property == VariableNode::PROP_SETGET) { + if (match(GDScriptTokenizer::Token::COMMA)) { + // Consume potential newline. + if (match(GDScriptTokenizer::Token::NEWLINE)) { + if (!p_need_indent) { + push_error(R"(Inline setter/getter setting cannot span across multiple lines (use "\\"" if needed).)"); } - - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = identifier; - - op->arguments.push_back(expr); - op->arguments.push_back(id); - - expr = op; - } - - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_OPEN) { - //indexing using "[]" - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_INDEX; - - tokenizer->advance(1); - - Node *subexpr = _parse_expression(op, p_static, p_allow_assign, p_parsing_constant); - if (!subexpr) { - return nullptr; - } - - if (tokenizer->get_token() != GDScriptTokenizer::TK_BRACKET_CLOSE) { - _set_error("Expected ']'"); - return nullptr; } - - op->arguments.push_back(expr); - op->arguments.push_back(subexpr); - tokenizer->advance(1); - expr = op; - - } else + } else { break; - } - - /*****************/ - /* Parse Casting */ - /*****************/ - - bool has_casting = expr->type == Node::TYPE_CAST; - if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_AS) { - if (has_casting) { - _set_error("Unexpected 'as'."); - return nullptr; } - CastNode *cn = alloc_node<CastNode>(); - if (!_parse_type(cn->cast_type)) { - _set_error("Expected type after 'as'."); - return nullptr; - } - has_casting = true; - cn->source_node = expr; - expr = cn; - } - - /******************/ - /* Parse Operator */ - /******************/ - - if (parenthesis > 0) { - //remove empty space (only allowed if inside parenthesis - while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { - tokenizer->advance(); - } - } - - Expression e; - e.is_op = false; - e.node = expr; - expression.push_back(e); - - // determine which operator is next - - OperatorNode::Operator op; - bool valid = true; - -//assign, if allowed is only allowed on the first operator -#define _VALIDATE_ASSIGN \ - if (!p_allow_assign || has_casting) { \ - _set_error("Unexpected assign."); \ - return nullptr; \ - } \ - p_allow_assign = false; - - switch (tokenizer->get_token()) { //see operator - - case GDScriptTokenizer::TK_OP_IN: op = OperatorNode::OP_IN; break; - case GDScriptTokenizer::TK_OP_EQUAL: op = OperatorNode::OP_EQUAL; break; - case GDScriptTokenizer::TK_OP_NOT_EQUAL: op = OperatorNode::OP_NOT_EQUAL; break; - case GDScriptTokenizer::TK_OP_LESS: op = OperatorNode::OP_LESS; break; - case GDScriptTokenizer::TK_OP_LESS_EQUAL: op = OperatorNode::OP_LESS_EQUAL; break; - case GDScriptTokenizer::TK_OP_GREATER: op = OperatorNode::OP_GREATER; break; - case GDScriptTokenizer::TK_OP_GREATER_EQUAL: op = OperatorNode::OP_GREATER_EQUAL; break; - case GDScriptTokenizer::TK_OP_AND: op = OperatorNode::OP_AND; break; - case GDScriptTokenizer::TK_OP_OR: op = OperatorNode::OP_OR; break; - case GDScriptTokenizer::TK_OP_ADD: op = OperatorNode::OP_ADD; break; - case GDScriptTokenizer::TK_OP_SUB: op = OperatorNode::OP_SUB; break; - case GDScriptTokenizer::TK_OP_MUL: op = OperatorNode::OP_MUL; break; - case GDScriptTokenizer::TK_OP_DIV: op = OperatorNode::OP_DIV; break; - case GDScriptTokenizer::TK_OP_MOD: - op = OperatorNode::OP_MOD; - break; - //case GDScriptTokenizer::TK_OP_NEG: op=OperatorNode::OP_NEG ; break; - case GDScriptTokenizer::TK_OP_SHIFT_LEFT: op = OperatorNode::OP_SHIFT_LEFT; break; - case GDScriptTokenizer::TK_OP_SHIFT_RIGHT: op = OperatorNode::OP_SHIFT_RIGHT; break; - case GDScriptTokenizer::TK_OP_ASSIGN: { - _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN; - - if (tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) { - //code complete assignment - completion_type = COMPLETION_ASSIGN; - completion_node = expr; - completion_class = current_class; - completion_function = current_function; - completion_line = tokenizer->get_token_line(); - completion_block = current_block; - completion_found = true; - tokenizer->advance(); - } - - } break; - case GDScriptTokenizer::TK_OP_ASSIGN_ADD: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_ADD; break; - case GDScriptTokenizer::TK_OP_ASSIGN_SUB: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SUB; break; - case GDScriptTokenizer::TK_OP_ASSIGN_MUL: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MUL; break; - case GDScriptTokenizer::TK_OP_ASSIGN_DIV: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_DIV; break; - case GDScriptTokenizer::TK_OP_ASSIGN_MOD: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MOD; break; - case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_LEFT; break; - case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_RIGHT; break; - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_AND; break; - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_OR; break; - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_XOR; break; - case GDScriptTokenizer::TK_OP_BIT_AND: op = OperatorNode::OP_BIT_AND; break; - case GDScriptTokenizer::TK_OP_BIT_OR: op = OperatorNode::OP_BIT_OR; break; - case GDScriptTokenizer::TK_OP_BIT_XOR: op = OperatorNode::OP_BIT_XOR; break; - case GDScriptTokenizer::TK_PR_IS: op = OperatorNode::OP_IS; break; - case GDScriptTokenizer::TK_CF_IF: op = OperatorNode::OP_TERNARY_IF; break; - case GDScriptTokenizer::TK_CF_ELSE: op = OperatorNode::OP_TERNARY_ELSE; break; - default: valid = false; break; } - if (valid) { - e.is_op = true; - e.op = op; - expression.push_back(e); - tokenizer->advance(); - } else { + if (!match(GDScriptTokenizer::Token::IDENTIFIER)) { break; } + function = parse_identifier(); } - /* Reduce the set set of expressions and place them in an operator tree, respecting precedence */ - - while (expression.size() > 1) { - - int next_op = -1; - int min_priority = 0xFFFFF; - bool is_unary = false; - bool is_ternary = false; - - for (int i = 0; i < expression.size(); i++) { + if (p_variable->property == VariableNode::PROP_SETGET) { + end_statement("property declaration"); + } - if (!expression[i].is_op) { + if (p_need_indent) { + consume(GDScriptTokenizer::Token::DEDENT, R"(Expected end of indented block for property.)"); + } + return property; +} - continue; +void GDScriptParser::parse_property_setter(VariableNode *p_variable) { + switch (p_variable->property) { + case VariableNode::PROP_INLINE: + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)"); + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name after "(".)")) { + p_variable->setter_parameter = parse_identifier(); } + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*"); + consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*"); - int priority; - - bool unary = false; - bool ternary = false; - bool error = false; - bool right_to_left = false; - - switch (expression[i].op) { + p_variable->setter = parse_suite("setter definition"); + break; - case OperatorNode::OP_IS: - case OperatorNode::OP_IS_BUILTIN: - priority = -1; - break; //before anything + case VariableNode::PROP_SETGET: + consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")"); + make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected setter function name after "=".)")) { + p_variable->setter_pointer = parse_identifier(); + } + break; + case VariableNode::PROP_NONE: + break; // Unreachable. + } +} - case OperatorNode::OP_BIT_INVERT: - priority = 0; - unary = true; - break; - case OperatorNode::OP_NEG: - case OperatorNode::OP_POS: - priority = 1; - unary = true; - break; +void GDScriptParser::parse_property_getter(VariableNode *p_variable) { + switch (p_variable->property) { + case VariableNode::PROP_INLINE: + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)"); - case OperatorNode::OP_MUL: priority = 2; break; - case OperatorNode::OP_DIV: priority = 2; break; - case OperatorNode::OP_MOD: priority = 2; break; + p_variable->getter = parse_suite("getter definition"); + break; + case VariableNode::PROP_SETGET: + consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")"); + make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected getter function name after "=".)")) { + p_variable->getter_pointer = parse_identifier(); + } + break; + case VariableNode::PROP_NONE: + break; // Unreachable. + } +} - case OperatorNode::OP_ADD: priority = 3; break; - case OperatorNode::OP_SUB: priority = 3; break; +GDScriptParser::ConstantNode *GDScriptParser::parse_constant() { + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) { + return nullptr; + } - case OperatorNode::OP_SHIFT_LEFT: priority = 4; break; - case OperatorNode::OP_SHIFT_RIGHT: priority = 4; break; + ConstantNode *constant = alloc_node<ConstantNode>(); + constant->identifier = parse_identifier(); - case OperatorNode::OP_BIT_AND: priority = 5; break; - case OperatorNode::OP_BIT_XOR: priority = 6; break; - case OperatorNode::OP_BIT_OR: priority = 7; break; + if (match(GDScriptTokenizer::Token::COLON)) { + if (check((GDScriptTokenizer::Token::EQUAL))) { + // Infer type. + constant->infer_datatype = true; + } else { + // Parse type. + constant->datatype_specifier = parse_type(); + } + } - case OperatorNode::OP_LESS: priority = 8; break; - case OperatorNode::OP_LESS_EQUAL: priority = 8; break; - case OperatorNode::OP_GREATER: priority = 8; break; - case OperatorNode::OP_GREATER_EQUAL: priority = 8; break; + if (consume(GDScriptTokenizer::Token::EQUAL, R"(Expected initializer after constant name.)")) { + // Initializer. + constant->initializer = parse_expression(false); - case OperatorNode::OP_EQUAL: priority = 8; break; - case OperatorNode::OP_NOT_EQUAL: priority = 8; break; + if (constant->initializer == nullptr) { + push_error(R"(Expected initializer expression for constant.)"); + return nullptr; + } + } - case OperatorNode::OP_IN: priority = 10; break; + end_statement("constant declaration"); - case OperatorNode::OP_NOT: - priority = 11; - unary = true; - break; - case OperatorNode::OP_AND: priority = 12; break; - case OperatorNode::OP_OR: priority = 13; break; + return constant; +} - case OperatorNode::OP_TERNARY_IF: - priority = 14; - ternary = true; - right_to_left = true; - break; - case OperatorNode::OP_TERNARY_ELSE: - priority = 14; - error = true; - // Rigth-to-left should be false in this case, otherwise it would always error. - break; +GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() { + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name.)")) { + return nullptr; + } - case OperatorNode::OP_ASSIGN: priority = 15; break; - case OperatorNode::OP_ASSIGN_ADD: priority = 15; break; - case OperatorNode::OP_ASSIGN_SUB: priority = 15; break; - case OperatorNode::OP_ASSIGN_MUL: priority = 15; break; - case OperatorNode::OP_ASSIGN_DIV: priority = 15; break; - case OperatorNode::OP_ASSIGN_MOD: priority = 15; break; - case OperatorNode::OP_ASSIGN_SHIFT_LEFT: priority = 15; break; - case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: priority = 15; break; - case OperatorNode::OP_ASSIGN_BIT_AND: priority = 15; break; - case OperatorNode::OP_ASSIGN_BIT_OR: priority = 15; break; - case OperatorNode::OP_ASSIGN_BIT_XOR: priority = 15; break; - - default: { - _set_error("GDScriptParser bug, invalid operator in expression: " + itos(expression[i].op)); - return nullptr; - } - } + ParameterNode *parameter = alloc_node<ParameterNode>(); + parameter->identifier = parse_identifier(); - if (priority < min_priority || (right_to_left && priority == min_priority)) { - // < is used for left to right (default) - // <= is used for right to left - if (error) { - _set_error("Unexpected operator"); - return nullptr; - } - next_op = i; - min_priority = priority; - is_unary = unary; - is_ternary = ternary; - } + if (match(GDScriptTokenizer::Token::COLON)) { + if (check((GDScriptTokenizer::Token::EQUAL))) { + // Infer type. + parameter->infer_datatype = true; + } else { + // Parse type. + make_completion_context(COMPLETION_TYPE_NAME, parameter); + parameter->datatype_specifier = parse_type(); } + } - if (next_op == -1) { + if (match(GDScriptTokenizer::Token::EQUAL)) { + // Default value. + parameter->default_value = parse_expression(false); + } - _set_error("Yet another parser bug...."); - ERR_FAIL_V(nullptr); - } + return parameter; +} - // OK! create operator.. - if (is_unary) { +GDScriptParser::SignalNode *GDScriptParser::parse_signal() { + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) { + return nullptr; + } - int expr_pos = next_op; - while (expression[expr_pos].is_op) { + SignalNode *signal = alloc_node<SignalNode>(); + signal->identifier = parse_identifier(); - expr_pos++; - if (expr_pos == expression.size()) { - //can happen.. - _set_error("Unexpected end of expression..."); - return nullptr; - } + if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + while (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { + ParameterNode *parameter = parse_parameter(); + if (parameter == nullptr) { + break; } - - //consecutively do unary operators - for (int i = expr_pos - 1; i >= next_op; i--) { - - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = expression[i].op; - op->arguments.push_back(expression[i + 1].node); - op->line = op_line; //line might have been changed from a \n - expression.write[i].is_op = false; - expression.write[i].node = op; - expression.remove(i + 1); + if (parameter->default_value != nullptr) { + push_error(R"(Signal parameters cannot have a default value.)"); } - - } else if (is_ternary) { - if (next_op < 1 || next_op >= (expression.size() - 1)) { - _set_error("Parser bug..."); - ERR_FAIL_V(nullptr); + if (signal->parameters_indices.has(parameter->identifier->name)) { + push_error(vformat(R"(Parameter with name "%s" was already declared for this signal.)", parameter->identifier->name)); + } else { + signal->parameters_indices[parameter->identifier->name] = signal->parameters.size(); + signal->parameters.push_back(parameter); } + } + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*"); + } - if (next_op >= (expression.size() - 2) || expression[next_op + 2].op != OperatorNode::OP_TERNARY_ELSE) { - _set_error("Expected else after ternary if."); - return nullptr; - } - if (next_op >= (expression.size() - 3)) { - _set_error("Expected value after ternary else."); - return nullptr; - } + end_statement("signal declaration"); - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = expression[next_op].op; - op->line = op_line; //line might have been changed from a \n + return signal; +} - if (expression[next_op - 1].is_op) { +GDScriptParser::EnumNode *GDScriptParser::parse_enum() { + EnumNode *enum_node = alloc_node<EnumNode>(); + bool named = false; - _set_error("Parser bug..."); - ERR_FAIL_V(nullptr); - } + if (check(GDScriptTokenizer::Token::IDENTIFIER)) { + advance(); + enum_node->identifier = parse_identifier(); + named = true; + } - if (expression[next_op + 1].is_op) { - // this is not invalid and can really appear - // but it becomes invalid anyway because no binary op - // can be followed by a unary op in a valid combination, - // due to how precedence works, unaries will always disappear first + push_multiline(true); + consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")")); - _set_error("Unexpected two consecutive operators after ternary if."); - return nullptr; - } + int current_value = 0; - if (expression[next_op + 3].is_op) { - // this is not invalid and can really appear - // but it becomes invalid anyway because no binary op - // can be followed by a unary op in a valid combination, - // due to how precedence works, unaries will always disappear first + do { + if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) { + break; // Allow trailing comma. + } + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) { + EnumNode::Value item; + item.identifier = parse_identifier(); - _set_error("Unexpected two consecutive operators after ternary else."); - return nullptr; + if (!named) { + // TODO: Abstract this recursive member check. + ClassNode *parent = current_class; + while (parent != nullptr) { + if (parent->members_indices.has(item.identifier->name)) { + push_error(vformat(R"(Name "%s" is already used as a class %s.)", item.identifier->name, parent->get_member(item.identifier->name).get_type_name())); + break; + } + parent = parent->outer; + } } - op->arguments.push_back(expression[next_op + 1].node); //next expression goes as first - op->arguments.push_back(expression[next_op - 1].node); //left expression goes as when-true - op->arguments.push_back(expression[next_op + 3].node); //expression after next goes as when-false - - //replace all 3 nodes by this operator and make it an expression - expression.write[next_op - 1].node = op; - expression.remove(next_op); - expression.remove(next_op); - expression.remove(next_op); - expression.remove(next_op); - } else { + if (match(GDScriptTokenizer::Token::EQUAL)) { + if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected integer value after "=".)")) { + item.custom_value = parse_literal(); - if (next_op < 1 || next_op >= (expression.size() - 1)) { - _set_error("Parser bug..."); - ERR_FAIL_V(nullptr); + 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; + } + } } - - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = expression[next_op].op; - op->line = op_line; //line might have been changed from a \n - - if (expression[next_op - 1].is_op) { - - _set_error("Parser bug..."); - ERR_FAIL_V(nullptr); + item.value = current_value++; + enum_node->values.push_back(item); + if (!named) { + // Add as member of current class. + current_class->add_member(item); } + } + } while (match(GDScriptTokenizer::Token::COMMA)); - if (expression[next_op + 1].is_op) { - // this is not invalid and can really appear - // but it becomes invalid anyway because no binary op - // can be followed by a unary op in a valid combination, - // due to how precedence works, unaries will always disappear first + pop_multiline(); + consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); - _set_error("Unexpected two consecutive operators."); - return nullptr; - } + end_statement("enum"); - op->arguments.push_back(expression[next_op - 1].node); //expression goes as left - op->arguments.push_back(expression[next_op + 1].node); //next expression goes as right + return enum_node; +} - //replace all 3 nodes by this operator and make it an expression - expression.write[next_op - 1].node = op; - expression.remove(next_op); - expression.remove(next_op); +GDScriptParser::FunctionNode *GDScriptParser::parse_function() { + bool _static = false; + if (previous.type == GDScriptTokenizer::Token::STATIC) { + // TODO: Improve message if user uses "static" with "var" or "const" + if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) { + return nullptr; } + _static = true; } - return expression[0].node; -} - -GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to_const) { - - switch (p_node->type) { - - case Node::TYPE_BUILT_IN_FUNCTION: { - //many may probably be optimizable - return p_node; - } break; - case Node::TYPE_ARRAY: { - - ArrayNode *an = static_cast<ArrayNode *>(p_node); - bool all_constants = true; - - for (int i = 0; i < an->elements.size(); i++) { - - an->elements.write[i] = _reduce_expression(an->elements[i], p_to_const); - if (an->elements[i]->type != Node::TYPE_CONSTANT) - all_constants = false; - } - - if (all_constants && p_to_const) { - //reduce constant array expression + FunctionNode *function = alloc_node<FunctionNode>(); + make_completion_context(COMPLETION_OVERRIDE_METHOD, function); - ConstantNode *cn = alloc_node<ConstantNode>(); - Array arr; - arr.resize(an->elements.size()); - for (int i = 0; i < an->elements.size(); i++) { - ConstantNode *acn = static_cast<ConstantNode *>(an->elements[i]); - arr[i] = acn->value; - } - cn->value = arr; - cn->datatype = _type_from_variant(cn->value); - return cn; - } + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) { + return nullptr; + } - return an; + FunctionNode *previous_function = current_function; + current_function = function; - } break; - case Node::TYPE_DICTIONARY: { + function->identifier = parse_identifier(); + function->is_static = _static; - DictionaryNode *dn = static_cast<DictionaryNode *>(p_node); - bool all_constants = true; + push_multiline(true); + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)"); - for (int i = 0; i < dn->elements.size(); i++) { + SuiteNode *body = alloc_node<SuiteNode>(); + SuiteNode *previous_suite = current_suite; + current_suite = body; - dn->elements.write[i].key = _reduce_expression(dn->elements[i].key, p_to_const); - if (dn->elements[i].key->type != Node::TYPE_CONSTANT) - all_constants = false; - dn->elements.write[i].value = _reduce_expression(dn->elements[i].value, p_to_const); - if (dn->elements[i].value->type != Node::TYPE_CONSTANT) - all_constants = false; + if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { + bool default_used = false; + do { + if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { + // Allow for trailing comma. + break; } - - if (all_constants && p_to_const) { - //reduce constant array expression - - ConstantNode *cn = alloc_node<ConstantNode>(); - Dictionary dict; - for (int i = 0; i < dn->elements.size(); i++) { - ConstantNode *key_c = static_cast<ConstantNode *>(dn->elements[i].key); - ConstantNode *value_c = static_cast<ConstantNode *>(dn->elements[i].value); - - dict[key_c->value] = value_c->value; - } - cn->value = dict; - cn->datatype = _type_from_variant(cn->value); - return cn; + ParameterNode *parameter = parse_parameter(); + if (parameter == nullptr) { + break; } - - return dn; - - } break; - case Node::TYPE_OPERATOR: { - - OperatorNode *op = static_cast<OperatorNode *>(p_node); - - bool all_constants = true; - int last_not_constant = -1; - - for (int i = 0; i < op->arguments.size(); i++) { - - op->arguments.write[i] = _reduce_expression(op->arguments[i], p_to_const); - if (op->arguments[i]->type != Node::TYPE_CONSTANT) { - all_constants = false; - last_not_constant = i; + if (parameter->default_value != nullptr) { + default_used = true; + } else { + if (default_used) { + push_error("Cannot have a mandatory parameters after optional parameters."); + continue; } } - - if (op->op == OperatorNode::OP_IS) { - //nothing much - return op; + if (function->parameters_indices.has(parameter->identifier->name)) { + push_error(vformat(R"(Parameter with name "%s" was already declared for this function.)", parameter->identifier->name)); + } else { + function->parameters_indices[parameter->identifier->name] = function->parameters.size(); + function->parameters.push_back(parameter); + body->add_local(parameter); } - if (op->op == OperatorNode::OP_PARENT_CALL) { - //nothing much - return op; - - } else if (op->op == OperatorNode::OP_CALL) { - //can reduce base type constructors - if ((op->arguments[0]->type == Node::TYPE_TYPE || (op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && GDScriptFunctions::is_deterministic(static_cast<BuiltInFunctionNode *>(op->arguments[0])->function))) && last_not_constant == 0) { - - //native type constructor or intrinsic function - const Variant **vptr = nullptr; - Vector<Variant *> ptrs; - if (op->arguments.size() > 1) { - - ptrs.resize(op->arguments.size() - 1); - for (int i = 0; i < ptrs.size(); i++) { - - ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[i + 1]); - ptrs.write[i] = &cn->value; - } - - vptr = (const Variant **)&ptrs[0]; - } - - Callable::CallError ce; - Variant v; - - if (op->arguments[0]->type == Node::TYPE_TYPE) { - TypeNode *tn = static_cast<TypeNode *>(op->arguments[0]); - v = Variant::construct(tn->vtype, vptr, ptrs.size(), ce); - - } else { - GDScriptFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function; - GDScriptFunctions::call(func, vptr, ptrs.size(), v, ce); - } - - if (ce.error != Callable::CallError::CALL_OK) { - - String errwhere; - if (op->arguments[0]->type == Node::TYPE_TYPE) { - TypeNode *tn = static_cast<TypeNode *>(op->arguments[0]); - errwhere = "'" + Variant::get_type_name(tn->vtype) + "' constructor"; - - } else { - GDScriptFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function; - errwhere = String("'") + GDScriptFunctions::get_func_name(func) + "' intrinsic function"; - } - - switch (ce.error) { - - case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { - - _set_error("Invalid argument (#" + itos(ce.argument + 1) + ") for " + errwhere + "."); - - } break; - case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: { - - _set_error("Too many arguments for " + errwhere + "."); - } break; - case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: { - - _set_error("Too few arguments for " + errwhere + "."); - } break; - default: { - _set_error("Invalid arguments for " + errwhere + "."); - - } break; - } - - error_line = op->line; - - return p_node; - } + } while (match(GDScriptTokenizer::Token::COMMA)); + } - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = v; - cn->datatype = _type_from_variant(v); - return cn; - } + pop_multiline(); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after function parameters.)*"); - return op; //don't reduce yet + if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) { + make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function); + function->return_type = parse_type(true); + } - } else if (op->op == OperatorNode::OP_YIELD) { - return op; + // TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens. + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after function declaration.)"); - } else if (op->op == OperatorNode::OP_INDEX) { - //can reduce indices into constant arrays or dictionaries + current_suite = previous_suite; + function->body = parse_suite("function declaration", body); - if (all_constants) { + current_function = previous_function; + return function; +} - ConstantNode *ca = static_cast<ConstantNode *>(op->arguments[0]); - ConstantNode *cb = static_cast<ConstantNode *>(op->arguments[1]); +GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_valid_targets) { + AnnotationNode *annotation = alloc_node<AnnotationNode>(); - bool valid; + annotation->name = previous.literal; - Variant v = ca->value.get(cb->value, &valid); - if (!valid) { - _set_error("invalid index in constant expression"); - error_line = op->line; - return op; - } + make_completion_context(COMPLETION_ANNOTATION, annotation); - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = v; - cn->datatype = _type_from_variant(v); - return cn; - } + bool valid = true; - return op; - - } else if (op->op == OperatorNode::OP_INDEX_NAMED) { - - if (op->arguments[0]->type == Node::TYPE_CONSTANT && op->arguments[1]->type == Node::TYPE_IDENTIFIER) { + if (!valid_annotations.has(annotation->name)) { + push_error(vformat(R"(Unrecognized annotation: "%s".)", annotation->name)); + valid = false; + } - ConstantNode *ca = static_cast<ConstantNode *>(op->arguments[0]); - IdentifierNode *ib = static_cast<IdentifierNode *>(op->arguments[1]); + annotation->info = &valid_annotations[annotation->name]; - bool valid; - Variant v = ca->value.get_named(ib->name, &valid); - if (!valid) { - _set_error("invalid index '" + String(ib->name) + "' in constant expression"); - error_line = op->line; - return op; - } + if (!annotation->applies_to(p_valid_targets)) { + push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name)); + valid = false; + } - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = v; - cn->datatype = _type_from_variant(v); - return cn; + if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + // Arguments. + push_completion_call(annotation); + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true); + if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { + int argument_index = 0; + do { + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true); + set_last_completion_call_arg(argument_index++); + ExpressionNode *argument = parse_expression(false); + if (argument == nullptr) { + valid = false; + continue; } + annotation->arguments.push_back(argument); + } while (match(GDScriptTokenizer::Token::COMMA)); - return op; - } - - //validate assignment (don't assign to constant expression - switch (op->op) { - - case OperatorNode::OP_ASSIGN: - case OperatorNode::OP_ASSIGN_ADD: - case OperatorNode::OP_ASSIGN_SUB: - case OperatorNode::OP_ASSIGN_MUL: - case OperatorNode::OP_ASSIGN_DIV: - case OperatorNode::OP_ASSIGN_MOD: - case OperatorNode::OP_ASSIGN_SHIFT_LEFT: - case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: - case OperatorNode::OP_ASSIGN_BIT_AND: - case OperatorNode::OP_ASSIGN_BIT_OR: - case OperatorNode::OP_ASSIGN_BIT_XOR: { - - if (op->arguments[0]->type == Node::TYPE_CONSTANT) { - _set_error("Can't assign to constant", tokenizer->get_token_line() - 1); - error_line = op->line; - return op; - } else if (op->arguments[0]->type == Node::TYPE_SELF) { - _set_error("Can't assign to self.", op->line); - error_line = op->line; - return op; - } - - if (op->arguments[0]->type == Node::TYPE_OPERATOR) { - OperatorNode *on = static_cast<OperatorNode *>(op->arguments[0]); - if (on->op != OperatorNode::OP_INDEX && on->op != OperatorNode::OP_INDEX_NAMED) { - _set_error("Can't assign to an expression", tokenizer->get_token_line() - 1); - error_line = op->line; - return op; - } - } + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*"); + } + pop_completion_call(); + } - } break; - default: { - break; - } - } - //now se if all are constants - if (!all_constants) - return op; //nothing to reduce from here on -#define _REDUCE_UNARY(m_vop) \ - bool valid = false; \ - Variant res; \ - Variant::evaluate(m_vop, static_cast<ConstantNode *>(op->arguments[0])->value, Variant(), res, valid); \ - if (!valid) { \ - _set_error("Invalid operand for unary operator"); \ - error_line = op->line; \ - return p_node; \ - } \ - ConstantNode *cn = alloc_node<ConstantNode>(); \ - cn->value = res; \ - cn->datatype = _type_from_variant(res); \ - return cn; - -#define _REDUCE_BINARY(m_vop) \ - bool valid = false; \ - Variant res; \ - Variant::evaluate(m_vop, static_cast<ConstantNode *>(op->arguments[0])->value, static_cast<ConstantNode *>(op->arguments[1])->value, res, valid); \ - if (!valid) { \ - _set_error("Invalid operands for operator"); \ - error_line = op->line; \ - return p_node; \ - } \ - ConstantNode *cn = alloc_node<ConstantNode>(); \ - cn->value = res; \ - cn->datatype = _type_from_variant(res); \ - return cn; - - switch (op->op) { - - //unary operators - case OperatorNode::OP_NEG: { - _REDUCE_UNARY(Variant::OP_NEGATE); - } break; - case OperatorNode::OP_POS: { - _REDUCE_UNARY(Variant::OP_POSITIVE); - } break; - case OperatorNode::OP_NOT: { - _REDUCE_UNARY(Variant::OP_NOT); - } break; - case OperatorNode::OP_BIT_INVERT: { - _REDUCE_UNARY(Variant::OP_BIT_NEGATE); - } break; - //binary operators (in precedence order) - case OperatorNode::OP_IN: { - _REDUCE_BINARY(Variant::OP_IN); - } break; - case OperatorNode::OP_EQUAL: { - _REDUCE_BINARY(Variant::OP_EQUAL); - } break; - case OperatorNode::OP_NOT_EQUAL: { - _REDUCE_BINARY(Variant::OP_NOT_EQUAL); - } break; - case OperatorNode::OP_LESS: { - _REDUCE_BINARY(Variant::OP_LESS); - } break; - case OperatorNode::OP_LESS_EQUAL: { - _REDUCE_BINARY(Variant::OP_LESS_EQUAL); - } break; - case OperatorNode::OP_GREATER: { - _REDUCE_BINARY(Variant::OP_GREATER); - } break; - case OperatorNode::OP_GREATER_EQUAL: { - _REDUCE_BINARY(Variant::OP_GREATER_EQUAL); - } break; - case OperatorNode::OP_AND: { - _REDUCE_BINARY(Variant::OP_AND); - } break; - case OperatorNode::OP_OR: { - _REDUCE_BINARY(Variant::OP_OR); - } break; - case OperatorNode::OP_ADD: { - _REDUCE_BINARY(Variant::OP_ADD); - } break; - case OperatorNode::OP_SUB: { - _REDUCE_BINARY(Variant::OP_SUBTRACT); - } break; - case OperatorNode::OP_MUL: { - _REDUCE_BINARY(Variant::OP_MULTIPLY); - } break; - case OperatorNode::OP_DIV: { - _REDUCE_BINARY(Variant::OP_DIVIDE); - } break; - case OperatorNode::OP_MOD: { - _REDUCE_BINARY(Variant::OP_MODULE); - } break; - case OperatorNode::OP_SHIFT_LEFT: { - _REDUCE_BINARY(Variant::OP_SHIFT_LEFT); - } break; - case OperatorNode::OP_SHIFT_RIGHT: { - _REDUCE_BINARY(Variant::OP_SHIFT_RIGHT); - } break; - case OperatorNode::OP_BIT_AND: { - _REDUCE_BINARY(Variant::OP_BIT_AND); - } break; - case OperatorNode::OP_BIT_OR: { - _REDUCE_BINARY(Variant::OP_BIT_OR); - } break; - case OperatorNode::OP_BIT_XOR: { - _REDUCE_BINARY(Variant::OP_BIT_XOR); - } break; - case OperatorNode::OP_TERNARY_IF: { - if (static_cast<ConstantNode *>(op->arguments[0])->value.booleanize()) { - return op->arguments[1]; - } else { - return op->arguments[2]; - } - } break; - default: { - ERR_FAIL_V(op); - } - } + match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional. - } break; - default: { - return p_node; - } break; + if (valid) { + valid = validate_annotation_arguments(annotation); } + + return valid ? annotation : nullptr; } -GDScriptParser::Node *GDScriptParser::_parse_and_reduce_expression(Node *p_parent, bool p_static, bool p_reduce_const, bool p_allow_assign) { +void GDScriptParser::clear_unused_annotations() { + for (const List<AnnotationNode *>::Element *E = annotation_stack.front(); E != nullptr; E = E->next()) { + AnnotationNode *annotation = E->get(); + push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation); + } - Node *expr = _parse_expression(p_parent, p_static, p_allow_assign, p_reduce_const); - if (!expr || error_set) - return nullptr; - expr = _reduce_expression(expr, p_reduce_const); - if (!expr || error_set) - return nullptr; - return expr; + annotation_stack.clear(); } -bool GDScriptParser::_recover_from_completion() { +bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, int p_optional_arguments, bool p_is_vararg) { + ERR_FAIL_COND_V_MSG(valid_annotations.has(p_info.name), false, vformat(R"(Annotation "%s" already registered.)", p_info.name)); - if (!completion_found) { - return false; //can't recover if no completion - } - //skip stuff until newline - while (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE && tokenizer->get_token() != GDScriptTokenizer::TK_EOF && tokenizer->get_token() != GDScriptTokenizer::TK_ERROR) { - tokenizer->advance(); - } - completion_found = false; - error_set = false; - if (tokenizer->get_token() == GDScriptTokenizer::TK_ERROR) { - error_set = true; + AnnotationInfo new_annotation; + new_annotation.info = p_info; + new_annotation.info.default_arguments.resize(p_optional_arguments); + if (p_is_vararg) { + new_annotation.info.flags |= METHOD_FLAG_VARARG; } + new_annotation.apply = p_apply; + new_annotation.target_kind = p_target_kinds; + valid_annotations[p_info.name] = new_annotation; return true; } -GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) { +GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite) { + SuiteNode *suite = p_suite != nullptr ? p_suite : alloc_node<SuiteNode>(); + suite->parent_block = current_suite; + current_suite = suite; - PatternNode *pattern = alloc_node<PatternNode>(); + bool multiline = false; - GDScriptTokenizer::Token token = tokenizer->get_token(); - if (error_set) - return nullptr; - - if (token == GDScriptTokenizer::TK_EOF) { - return nullptr; + if (check(GDScriptTokenizer::Token::NEWLINE)) { + multiline = true; } - switch (token) { - // array - case GDScriptTokenizer::TK_BRACKET_OPEN: { - tokenizer->advance(); - pattern->pt_type = GDScriptParser::PatternNode::PT_ARRAY; - while (true) { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) { - tokenizer->advance(); - break; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) { - // match everything - tokenizer->advance(2); - PatternNode *sub_pattern = alloc_node<PatternNode>(); - sub_pattern->pt_type = GDScriptParser::PatternNode::PT_IGNORE_REST; - pattern->array.push_back(sub_pattern); - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA && tokenizer->get_token(1) == GDScriptTokenizer::TK_BRACKET_CLOSE) { - tokenizer->advance(2); - break; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) { - tokenizer->advance(1); - break; - } else { - _set_error("'..' pattern only allowed at the end of an array pattern"); - return nullptr; - } - } + if (multiline) { + consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after %s.)", p_context)); - PatternNode *sub_pattern = _parse_pattern(p_static); - if (!sub_pattern) { - return nullptr; - } + if (!consume(GDScriptTokenizer::Token::INDENT, vformat(R"(Expected indented block after %s.)", p_context))) { + current_suite = suite->parent_block; + return suite; + } + } - pattern->array.push_back(sub_pattern); + do { + Node *statement = parse_statement(); + if (statement == nullptr) { + continue; + } + suite->statements.push_back(statement); - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - tokenizer->advance(); - continue; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) { - tokenizer->advance(); - break; - } else { - _set_error("Not a valid pattern"); - return nullptr; - } - } - } break; - // bind - case GDScriptTokenizer::TK_PR_VAR: { - tokenizer->advance(); - if (!tokenizer->is_token_literal()) { - _set_error("Expected identifier for binding variable name."); - return nullptr; - } - pattern->pt_type = GDScriptParser::PatternNode::PT_BIND; - pattern->bind = tokenizer->get_token_literal(); - // Check if variable name is already used - BlockNode *bl = current_block; - while (bl) { - if (bl->variables.has(pattern->bind)) { - _set_error("Binding name of '" + pattern->bind.operator String() + "' is already declared in this scope."); - return nullptr; + // Register locals. + switch (statement->type) { + case Node::VARIABLE: { + VariableNode *variable = static_cast<VariableNode *>(statement); + const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name); + if (local.type != SuiteNode::Local::UNDEFINED) { + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name)); } - bl = bl->parent_block; + current_suite->add_local(variable); + break; } - // Create local variable for proper identifier detection later - LocalVarNode *lv = alloc_node<LocalVarNode>(); - lv->name = pattern->bind; - current_block->variables.insert(lv->name, lv); - tokenizer->advance(); - } break; - // dictionary - case GDScriptTokenizer::TK_CURLY_BRACKET_OPEN: { - tokenizer->advance(); - pattern->pt_type = GDScriptParser::PatternNode::PT_DICTIONARY; - while (true) { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { - tokenizer->advance(); - break; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) { - // match everything - tokenizer->advance(2); - PatternNode *sub_pattern = alloc_node<PatternNode>(); - sub_pattern->pt_type = PatternNode::PT_IGNORE_REST; - pattern->array.push_back(sub_pattern); - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA && tokenizer->get_token(1) == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { - tokenizer->advance(2); - break; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { - tokenizer->advance(1); - break; + case Node::CONSTANT: { + ConstantNode *constant = static_cast<ConstantNode *>(statement); + const SuiteNode::Local &local = current_suite->get_local(constant->identifier->name); + if (local.type != SuiteNode::Local::UNDEFINED) { + String name; + if (local.type == SuiteNode::Local::CONSTANT) { + name = "constant"; } else { - _set_error("'..' pattern only allowed at the end of a dictionary pattern"); - return nullptr; + name = "variable"; } + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name)); } + current_suite->add_local(constant); + break; + } + default: + break; + } - Node *key = _parse_and_reduce_expression(pattern, p_static); - if (!key) { - _set_error("Not a valid key in pattern"); - return nullptr; - } + } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()); - if (key->type != GDScriptParser::Node::TYPE_CONSTANT) { - _set_error("Not a constant expression as key"); - return nullptr; - } + if (multiline) { + consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context)); + } - if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { - tokenizer->advance(); + current_suite = suite->parent_block; + return suite; +} - PatternNode *value = _parse_pattern(p_static); - if (!value) { - _set_error("Expected pattern in dictionary value"); - return nullptr; - } +GDScriptParser::Node *GDScriptParser::parse_statement() { + Node *result = nullptr; +#ifdef DEBUG_ENABLED + bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code; +#endif - pattern->dictionary.insert(static_cast<ConstantNode *>(key), value); - } else { - pattern->dictionary.insert(static_cast<ConstantNode *>(key), nullptr); + switch (current.type) { + case GDScriptTokenizer::Token::PASS: + advance(); + result = alloc_node<PassNode>(); + end_statement(R"("pass")"); + break; + case GDScriptTokenizer::Token::VAR: + advance(); + result = parse_variable(); + break; + case GDScriptTokenizer::Token::CONST: + advance(); + result = parse_constant(); + break; + case GDScriptTokenizer::Token::IF: + advance(); + result = parse_if(); + break; + case GDScriptTokenizer::Token::FOR: + advance(); + result = parse_for(); + break; + case GDScriptTokenizer::Token::WHILE: + advance(); + result = parse_while(); + break; + case GDScriptTokenizer::Token::MATCH: + advance(); + result = parse_match(); + break; + case GDScriptTokenizer::Token::BREAK: + advance(); + result = parse_break(); + break; + case GDScriptTokenizer::Token::CONTINUE: + advance(); + result = parse_continue(); + break; + case GDScriptTokenizer::Token::RETURN: { + advance(); + ReturnNode *n_return = alloc_node<ReturnNode>(); + if (!is_statement_end()) { + if (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) { + push_error(R"(Constructor cannot return a value.)"); } + n_return->return_value = parse_expression(false); + } + result = n_return; - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - tokenizer->advance(); - continue; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { - tokenizer->advance(); - break; - } else { - _set_error("Not a valid pattern"); - return nullptr; - } + current_suite->has_return = true; + + end_statement("return statement"); + break; + } + case GDScriptTokenizer::Token::BREAKPOINT: + advance(); + result = alloc_node<BreakpointNode>(); + end_statement(R"("breakpoint")"); + break; + case GDScriptTokenizer::Token::ASSERT: + advance(); + result = parse_assert(); + break; + case GDScriptTokenizer::Token::ANNOTATION: { + advance(); + AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT); + if (annotation != nullptr) { + annotation_stack.push_back(annotation); } - } break; - case GDScriptTokenizer::TK_WILDCARD: { - tokenizer->advance(); - pattern->pt_type = PatternNode::PT_WILDCARD; - } break; - // all the constants like strings and numbers + break; + } default: { - Node *value = _parse_and_reduce_expression(pattern, p_static); - if (!value) { - _set_error("Expect constant expression or variables in a pattern"); - return nullptr; + // Expression statement. + ExpressionNode *expression = parse_expression(true); // Allow assignment here. + if (expression == nullptr) { + push_error(vformat(R"(Expected statement, found "%s" instead.)", previous.get_name())); } + end_statement("expression"); + result = expression; - if (value->type == Node::TYPE_OPERATOR) { - // Maybe it's SomeEnum.VALUE - Node *current_value = value; - - while (current_value->type == Node::TYPE_OPERATOR) { - OperatorNode *op_node = static_cast<OperatorNode *>(current_value); - - if (op_node->op != OperatorNode::OP_INDEX_NAMED) { - _set_error("Invalid operator in pattern. Only index (`A.B`) is allowed"); - return nullptr; - } - current_value = op_node->arguments[0]; - } - - if (current_value->type != Node::TYPE_IDENTIFIER) { - _set_error("Only constant expression or variables allowed in a pattern"); - return nullptr; +#ifdef DEBUG_ENABLED + if (expression != nullptr) { + switch (expression->type) { + case Node::CALL: + case Node::ASSIGNMENT: + case Node::AWAIT: + // Fine. + break; + default: + push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION); } - - } else if (value->type != Node::TYPE_IDENTIFIER && value->type != Node::TYPE_CONSTANT) { - _set_error("Only constant expressions or variables allowed in a pattern"); - return nullptr; } - - pattern->pt_type = PatternNode::PT_CONSTANT; - pattern->constant = value; - } break; - } - - return pattern; -} - -void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static) { - IndentLevel current_level = indent_level.back()->get(); - - p_block->has_return = true; - - bool catch_all_appeared = false; - - while (true) { - - while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) - ; - - // GDScriptTokenizer::Token token = tokenizer->get_token(); - if (error_set) - return; - - if (current_level.indent > indent_level.back()->get().indent) { - break; // go back a level - } - - pending_newline = -1; - - PatternBranchNode *branch = alloc_node<PatternBranchNode>(); - branch->body = alloc_node<BlockNode>(); - branch->body->parent_block = p_block; - p_block->sub_blocks.push_back(branch->body); - current_block = branch->body; - - branch->patterns.push_back(_parse_pattern(p_static)); - if (!branch->patterns[0]) { +#endif break; } - - bool has_binding = branch->patterns[0]->pt_type == PatternNode::PT_BIND; - bool catch_all = has_binding || branch->patterns[0]->pt_type == PatternNode::PT_WILDCARD; + } #ifdef DEBUG_ENABLED - // Branches after a wildcard or binding are unreachable - if (catch_all_appeared && !current_function->has_unreachable_code) { - _add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String()); - current_function->has_unreachable_code = true; - } + if (unreachable) { + current_suite->has_unreachable_code = true; + push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name); + } #endif - while (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - tokenizer->advance(); - branch->patterns.push_back(_parse_pattern(p_static)); - if (!branch->patterns[branch->patterns.size() - 1]) { - return; - } - - PatternNode::PatternType pt = branch->patterns[branch->patterns.size() - 1]->pt_type; + if (panic_mode) { + synchronize(); + } - if (pt == PatternNode::PT_BIND) { - _set_error("Cannot use bindings with multipattern."); - return; - } + return result; +} - catch_all = catch_all || pt == PatternNode::PT_WILDCARD; - } +GDScriptParser::AssertNode *GDScriptParser::parse_assert() { + // TODO: Add assert message. + AssertNode *assert = alloc_node<AssertNode>(); - catch_all_appeared = catch_all_appeared || catch_all; + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "assert".)"); + assert->condition = parse_expression(false); + if (assert->condition == nullptr) { + push_error("Expected expression to assert."); + return nullptr; + } - if (!_enter_indent_block()) { - _set_error("Expected block in pattern branch"); - return; + if (match(GDScriptTokenizer::Token::COMMA)) { + // Error message. + if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected error message for assert after ",".)")) { + assert->message = parse_literal(); + if (assert->message->value.get_type() != Variant::STRING) { + push_error(R"(Expected string for assert error message.)"); + } + } else { + return nullptr; } + } - _parse_block(branch->body, p_static); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after assert expression.)*"); - current_block = p_block; + end_statement(R"("assert")"); - if (!branch->body->has_return) { - p_block->has_return = false; - } + return assert; +} - p_branches.push_back(branch); +GDScriptParser::BreakNode *GDScriptParser::parse_break() { + if (!can_break) { + push_error(R"(Cannot use "break" outside of a loop.)"); } + end_statement(R"("break")"); + return alloc_node<BreakNode>(); +} - // Even if all branches return, there is possibility of default fallthrough - if (!catch_all_appeared) { - p_block->has_return = false; +GDScriptParser::ContinueNode *GDScriptParser::parse_continue() { + if (!can_continue) { + push_error(R"(Cannot use "continue" outside of a loop or pattern matching block.)"); } + current_suite->has_continue = true; + end_statement(R"("continue")"); + return alloc_node<ContinueNode>(); } -void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) { - - const DataType &to_match_type = p_node_to_match->get_datatype(); - - switch (p_pattern->pt_type) { - case PatternNode::PT_CONSTANT: { - - DataType pattern_type = _reduce_node_type(p_pattern->constant); - if (error_set) { - return; - } - - OperatorNode *type_comp = nullptr; - - // static type check if possible - if (pattern_type.has_type && to_match_type.has_type) { - if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) { - _set_error("The pattern type (" + pattern_type.to_string() + ") isn't compatible with the type of the value to match (" + to_match_type.to_string() + ").", - p_pattern->line); - return; - } - } else { - // runtime typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; - - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); - - OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>(); - typeof_pattern_value->op = OperatorNode::OP_CALL; - typeof_pattern_value->arguments.push_back(typeof_node); - typeof_pattern_value->arguments.push_back(p_pattern->constant); - - type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_pattern_value); - } - - // compare the actual values - OperatorNode *value_comp = alloc_node<OperatorNode>(); - value_comp->op = OperatorNode::OP_EQUAL; - value_comp->arguments.push_back(p_pattern->constant); - value_comp->arguments.push_back(p_node_to_match); - - if (type_comp) { - OperatorNode *full_comparison = alloc_node<OperatorNode>(); - full_comparison->op = OperatorNode::OP_AND; - full_comparison->arguments.push_back(type_comp); - full_comparison->arguments.push_back(value_comp); - - p_resulting_node = full_comparison; - } else { - p_resulting_node = value_comp; - } - - } break; - case PatternNode::PT_BIND: { - p_bindings[p_pattern->bind] = p_node_to_match; - - // a bind always matches - ConstantNode *true_value = alloc_node<ConstantNode>(); - true_value->value = Variant(true); - p_resulting_node = true_value; - } break; - case PatternNode::PT_ARRAY: { - - bool open_ended = false; - - if (p_pattern->array.size() > 0) { - if (p_pattern->array[p_pattern->array.size() - 1]->pt_type == PatternNode::PT_IGNORE_REST) { - open_ended = true; - } - } - - // typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() >= length - // typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() == length - - { - OperatorNode *type_comp = nullptr; - // static type check if possible - if (to_match_type.has_type) { - // must be an array - if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::ARRAY) { - _set_error("Cannot match an array pattern with a non-array expression.", p_pattern->line); - return; - } - } else { - // runtime typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; - - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); - - IdentifierNode *typeof_array = alloc_node<IdentifierNode>(); - typeof_array->name = "TYPE_ARRAY"; - - type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_array); - } - - // size - ConstantNode *length = alloc_node<ConstantNode>(); - length->value = Variant(open_ended ? p_pattern->array.size() - 1 : p_pattern->array.size()); - - OperatorNode *call = alloc_node<OperatorNode>(); - call->op = OperatorNode::OP_CALL; - call->arguments.push_back(p_node_to_match); - - IdentifierNode *size = alloc_node<IdentifierNode>(); - size->name = "size"; - call->arguments.push_back(size); - - OperatorNode *length_comparison = alloc_node<OperatorNode>(); - length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL; - length_comparison->arguments.push_back(call); - length_comparison->arguments.push_back(length); - - if (type_comp) { - OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); - type_and_length_comparison->op = OperatorNode::OP_AND; - type_and_length_comparison->arguments.push_back(type_comp); - type_and_length_comparison->arguments.push_back(length_comparison); - - p_resulting_node = type_and_length_comparison; - } else { - p_resulting_node = length_comparison; - } - } - - for (int i = 0; i < p_pattern->array.size(); i++) { - PatternNode *pattern = p_pattern->array[i]; - - Node *condition = nullptr; - - ConstantNode *index = alloc_node<ConstantNode>(); - index->value = Variant(i); - - OperatorNode *indexed_value = alloc_node<OperatorNode>(); - indexed_value->op = OperatorNode::OP_INDEX; - indexed_value->arguments.push_back(p_node_to_match); - indexed_value->arguments.push_back(index); - - _generate_pattern(pattern, indexed_value, condition, p_bindings); - - // concatenate all the patterns with && - OperatorNode *and_node = alloc_node<OperatorNode>(); - and_node->op = OperatorNode::OP_AND; - and_node->arguments.push_back(p_resulting_node); - and_node->arguments.push_back(condition); - - p_resulting_node = and_node; - } - - } break; - case PatternNode::PT_DICTIONARY: { - - bool open_ended = false; - - if (p_pattern->array.size() > 0) { - open_ended = true; - } - - // typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() >= length - // typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() == length - - { - OperatorNode *type_comp = nullptr; - // static type check if possible - if (to_match_type.has_type) { - // must be an dictionary - if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::DICTIONARY) { - _set_error("Cannot match an dictionary pattern with a non-dictionary expression.", p_pattern->line); - return; - } - } else { - // runtime typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; - - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); - - IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>(); - typeof_dictionary->name = "TYPE_DICTIONARY"; - - type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_dictionary); - } - - // size - ConstantNode *length = alloc_node<ConstantNode>(); - length->value = Variant(open_ended ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size()); +GDScriptParser::ForNode *GDScriptParser::parse_for() { + ForNode *n_for = alloc_node<ForNode>(); - OperatorNode *call = alloc_node<OperatorNode>(); - call->op = OperatorNode::OP_CALL; - call->arguments.push_back(p_node_to_match); - - IdentifierNode *size = alloc_node<IdentifierNode>(); - size->name = "size"; - call->arguments.push_back(size); - - OperatorNode *length_comparison = alloc_node<OperatorNode>(); - length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL; - length_comparison->arguments.push_back(call); - length_comparison->arguments.push_back(length); + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected loop variable name after "for".)")) { + n_for->variable = parse_identifier(); + } - if (type_comp) { - OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); - type_and_length_comparison->op = OperatorNode::OP_AND; - type_and_length_comparison->arguments.push_back(type_comp); - type_and_length_comparison->arguments.push_back(length_comparison); + consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable name.)"); - p_resulting_node = type_and_length_comparison; - } else { - p_resulting_node = length_comparison; - } - } + n_for->list = parse_expression(false); - for (Map<ConstantNode *, PatternNode *>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) { + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)"); - Node *condition = nullptr; + // Save break/continue state. + bool could_break = can_break; + bool could_continue = can_continue; - // check for has, then for pattern + // Allow break/continue. + can_break = true; + can_continue = true; - IdentifierNode *has = alloc_node<IdentifierNode>(); - has->name = "has"; - - OperatorNode *has_call = alloc_node<OperatorNode>(); - has_call->op = OperatorNode::OP_CALL; - has_call->arguments.push_back(p_node_to_match); - has_call->arguments.push_back(has); - has_call->arguments.push_back(e->key()); + SuiteNode *suite = alloc_node<SuiteNode>(); + if (n_for->variable) { + suite->add_local(SuiteNode::Local(n_for->variable)); + } + suite->parent_for = n_for; - if (e->value()) { + n_for->loop = parse_suite(R"("for" block)", suite); - OperatorNode *indexed_value = alloc_node<OperatorNode>(); - indexed_value->op = OperatorNode::OP_INDEX; - indexed_value->arguments.push_back(p_node_to_match); - indexed_value->arguments.push_back(e->key()); + // Reset break/continue state. + can_break = could_break; + can_continue = could_continue; - _generate_pattern(e->value(), indexed_value, condition, p_bindings); + return n_for; +} - OperatorNode *has_and_pattern = alloc_node<OperatorNode>(); - has_and_pattern->op = OperatorNode::OP_AND; - has_and_pattern->arguments.push_back(has_call); - has_and_pattern->arguments.push_back(condition); +GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) { + IfNode *n_if = alloc_node<IfNode>(); - condition = has_and_pattern; + n_if->condition = parse_expression(false); + if (n_if->condition == nullptr) { + push_error(vformat(R"(Expected conditional expression after "%s".)", p_token)); + } - } else { - condition = has_call; - } + consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after "%s" condition.)", p_token)); - // concatenate all the patterns with && - OperatorNode *and_node = alloc_node<OperatorNode>(); - and_node->op = OperatorNode::OP_AND; - and_node->arguments.push_back(p_resulting_node); - and_node->arguments.push_back(condition); + n_if->true_block = parse_suite(vformat(R"("%s" block)", p_token)); + n_if->true_block->parent_if = n_if; - p_resulting_node = and_node; - } + if (n_if->true_block->has_continue) { + current_suite->has_continue = true; + } - } break; - case PatternNode::PT_IGNORE_REST: - case PatternNode::PT_WILDCARD: { - // simply generate a `true` - ConstantNode *true_value = alloc_node<ConstantNode>(); - true_value->value = Variant(true); - p_resulting_node = true_value; - } break; - default: { + if (match(GDScriptTokenizer::Token::ELIF)) { + IfNode *elif = parse_if("elif"); - } break; + SuiteNode *else_block = alloc_node<SuiteNode>(); + else_block->statements.push_back(elif); + n_if->false_block = else_block; + } else if (match(GDScriptTokenizer::Token::ELSE)) { + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "else".)"); + n_if->false_block = parse_suite(R"("else" block)"); } -} -void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) { - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = "#match_value"; - id->line = p_match_statement->line; - id->datatype = _reduce_node_type(p_match_statement->val_to_match); - if (id->datatype.has_type) { - _mark_line_as_safe(id->line); - } else { - _mark_line_as_unsafe(id->line); + if (n_if->false_block != nullptr && n_if->false_block->has_return && n_if->true_block->has_return) { + current_suite->has_return = true; } - - if (error_set) { - return; + if (n_if->false_block != nullptr && n_if->false_block->has_continue) { + current_suite->has_continue = true; } - for (int i = 0; i < p_match_statement->branches.size(); i++) { - - PatternBranchNode *branch = p_match_statement->branches[i]; + return n_if; +} - MatchNode::CompiledPatternBranch compiled_branch; - compiled_branch.compiled_pattern = nullptr; +GDScriptParser::MatchNode *GDScriptParser::parse_match() { + MatchNode *match = alloc_node<MatchNode>(); - Map<StringName, Node *> binding; + match->test = parse_expression(false); + if (match->test == nullptr) { + push_error(R"(Expected expression to test after "match".)"); + } - for (int j = 0; j < branch->patterns.size(); j++) { - PatternNode *pattern = branch->patterns[j]; - _mark_line_as_safe(pattern->line); + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" expression.)"); + consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected a newline after "match" statement.)"); - Map<StringName, Node *> bindings; - Node *resulting_node = nullptr; - _generate_pattern(pattern, id, resulting_node, bindings); + if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected an indented block after "match" statement.)")) { + return match; + } - if (!resulting_node) { - return; - } +#ifdef DEBUG_ENABLED + bool all_have_return = true; + bool have_wildcard = false; + bool wildcard_has_return = false; + bool have_wildcard_without_continue = false; +#endif - if (!binding.empty() && !bindings.empty()) { - _set_error("Multipatterns can't contain bindings"); - return; - } else { - binding = bindings; - } + while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { + MatchBranchNode *branch = parse_match_branch(); + if (branch == nullptr) { + continue; + } - // Result is always a boolean - DataType resulting_node_type; - resulting_node_type.has_type = true; - resulting_node_type.is_constant = true; - resulting_node_type.kind = DataType::BUILTIN; - resulting_node_type.builtin_type = Variant::BOOL; - resulting_node->set_datatype(resulting_node_type); - - if (compiled_branch.compiled_pattern) { - OperatorNode *or_node = alloc_node<OperatorNode>(); - or_node->op = OperatorNode::OP_OR; - or_node->arguments.push_back(compiled_branch.compiled_pattern); - or_node->arguments.push_back(resulting_node); - - compiled_branch.compiled_pattern = or_node; - } else { - // single pattern | first one - compiled_branch.compiled_pattern = resulting_node; - } +#ifdef DEBUG_ENABLED + if (have_wildcard_without_continue) { + push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN); } - // prepare the body ...hehe - for (Map<StringName, Node *>::Element *e = binding.front(); e; e = e->next()) { - if (!branch->body->variables.has(e->key())) { - _set_error("Parser bug: missing pattern bind variable.", branch->line); - ERR_FAIL(); + if (branch->has_wildcard) { + have_wildcard = true; + if (branch->block->has_return) { + wildcard_has_return = true; + } + if (!branch->block->has_continue) { + have_wildcard_without_continue = true; } - - LocalVarNode *local_var = branch->body->variables[e->key()]; - local_var->assign = e->value(); - local_var->set_datatype(local_var->assign->get_datatype()); - - IdentifierNode *id2 = alloc_node<IdentifierNode>(); - id2->name = local_var->name; - id2->declared_block = branch->body; - id2->set_datatype(local_var->assign->get_datatype()); - - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_ASSIGN; - op->arguments.push_back(id2); - op->arguments.push_back(local_var->assign); - local_var->assign_op = op; - - branch->body->statements.push_front(op); - branch->body->statements.push_front(local_var); } - - compiled_branch.body = branch->body; - - p_match_statement->compiled_pattern_branches.push_back(compiled_branch); + if (!branch->block->has_return) { + all_have_return = false; + } +#endif + match->branches.push_back(branch); } -} -void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { - - IndentLevel current_level = indent_level.back()->get(); + consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)"); #ifdef DEBUG_ENABLED - - NewLineNode *nl = alloc_node<NewLineNode>(); - - nl->line = tokenizer->get_token_line(); - p_block->statements.push_back(nl); + if (wildcard_has_return || (all_have_return && have_wildcard)) { + current_suite->has_return = true; + } #endif - bool is_first_line = true; + return match; +} - while (true) { - if (!is_first_line && indent_level.back()->prev() && indent_level.back()->prev()->get().indent == current_level.indent) { - if (indent_level.back()->prev()->get().is_mixed(current_level)) { - _set_error("Mixed tabs and spaces in indentation."); - return; - } - // pythonic single-line expression, don't parse future lines - indent_level.pop_back(); - p_block->end_line = tokenizer->get_token_line(); - return; - } - is_first_line = false; +GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { + MatchBranchNode *branch = alloc_node<MatchBranchNode>(); - GDScriptTokenizer::Token token = tokenizer->get_token(); - if (error_set) - return; + bool has_bind = false; - if (current_level.indent > indent_level.back()->get().indent) { - p_block->end_line = tokenizer->get_token_line(); - return; //go back a level + do { + PatternNode *pattern = parse_match_pattern(); + if (pattern == nullptr) { + continue; } - - if (pending_newline != -1) { - - NewLineNode *nl2 = alloc_node<NewLineNode>(); - nl2->line = pending_newline; - p_block->statements.push_back(nl2); - pending_newline = -1; + if (pattern->pattern_type == PatternNode::PT_BIND) { + has_bind = true; } - -#ifdef DEBUG_ENABLED - switch (token) { - case GDScriptTokenizer::TK_EOF: - case GDScriptTokenizer::TK_ERROR: - case GDScriptTokenizer::TK_NEWLINE: - case GDScriptTokenizer::TK_CF_PASS: { - // will check later - } break; - default: { - if (p_block->has_return && !current_function->has_unreachable_code) { - _add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String()); - current_function->has_unreachable_code = true; - } - } break; + if (branch->patterns.size() > 0 && has_bind) { + push_error(R"(Cannot use a variable bind with multiple patterns.)"); } -#endif // DEBUG_ENABLED - switch (token) { - case GDScriptTokenizer::TK_EOF: { - p_block->end_line = tokenizer->get_token_line(); - return; // End of file! - } break; - case GDScriptTokenizer::TK_ERROR: { - return; - } break; - case GDScriptTokenizer::TK_NEWLINE: { + if (pattern->pattern_type == PatternNode::PT_REST) { + push_error(R"(Rest pattern can only be used inside array and dictionary patterns.)"); + } else if (pattern->pattern_type == PatternNode::PT_BIND || pattern->pattern_type == PatternNode::PT_WILDCARD) { + branch->has_wildcard = true; + } + branch->patterns.push_back(pattern); + } while (match(GDScriptTokenizer::Token::COMMA)); - int line = tokenizer->get_token_line(); + if (branch->patterns.empty()) { + push_error(R"(No pattern found for "match" branch.)"); + } - if (!_parse_newline()) { - if (!error_set) { - p_block->end_line = tokenizer->get_token_line(); - pending_newline = p_block->end_line; - } - return; - } + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)"); - NewLineNode *nl2 = alloc_node<NewLineNode>(); - nl2->line = line; - p_block->statements.push_back(nl2); + // Save continue state. + bool could_continue = can_continue; + // Allow continue for match. + can_continue = true; - } break; - case GDScriptTokenizer::TK_CF_PASS: { - if (tokenizer->get_token(1) != GDScriptTokenizer::TK_SEMICOLON && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE && tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF) { + SuiteNode *suite = alloc_node<SuiteNode>(); + if (branch->patterns.size() > 0) { + List<StringName> binds; + branch->patterns[0]->binds.get_key_list(&binds); - _set_error("Expected \";\" or a line break."); - return; - } - _mark_line_as_safe(tokenizer->get_token_line()); - tokenizer->advance(); - if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) { - // Ignore semicolon after 'pass'. - tokenizer->advance(); - } - } break; - case GDScriptTokenizer::TK_PR_VAR: { - // Variable declaration and (eventual) initialization. - - tokenizer->advance(); - int var_line = tokenizer->get_token_line(); - if (!tokenizer->is_token_literal(0, true)) { - - _set_error("Expected an identifier for the local variable name."); - return; - } - StringName n = tokenizer->get_token_literal(); - tokenizer->advance(); - if (current_function) { - for (int i = 0; i < current_function->arguments.size(); i++) { - if (n == current_function->arguments[i]) { - _set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(current_function->line) + ")."); - return; - } - } - } - BlockNode *check_block = p_block; - while (check_block) { - if (check_block->variables.has(n)) { - _set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(check_block->variables[n]->line) + ")."); - return; - } - check_block = check_block->parent_block; - } + for (List<StringName>::Element *E = binds.front(); E != nullptr; E = E->next()) { + SuiteNode::Local local(branch->patterns[0]->binds[E->get()]); + suite->add_local(local); + } + } - //must know when the local variable is declared - LocalVarNode *lv = alloc_node<LocalVarNode>(); - lv->name = n; - lv->line = var_line; - p_block->statements.push_back(lv); + branch->block = parse_suite("match pattern block", suite); - Node *assigned = nullptr; + // Restore continue state. + can_continue = could_continue; - if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { - if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { - lv->datatype = DataType(); -#ifdef DEBUG_ENABLED - lv->datatype.infer_type = true; -#endif - tokenizer->advance(); - } else if (!_parse_type(lv->datatype)) { - _set_error("Expected a type for the variable."); - return; - } - } + return branch; +} - if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { +GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_root_pattern) { + PatternNode *pattern = alloc_node<PatternNode>(); - tokenizer->advance(); - Node *subexpr = _parse_and_reduce_expression(p_block, p_static); - if (!subexpr) { - if (_recover_from_completion()) { - break; - } - return; - } + switch (current.type) { + case GDScriptTokenizer::Token::LITERAL: + advance(); + pattern->pattern_type = PatternNode::PT_LITERAL; + pattern->literal = parse_literal(); + if (pattern->literal == nullptr) { + // Error happened. + return nullptr; + } + break; + case GDScriptTokenizer::Token::VAR: { + // Bind. + advance(); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected bind name after "var".)")) { + return nullptr; + } + pattern->pattern_type = PatternNode::PT_BIND; + pattern->bind = parse_identifier(); - lv->assignments++; - assigned = subexpr; - } else { + PatternNode *root_pattern = p_root_pattern == nullptr ? pattern : p_root_pattern; - assigned = _get_default_value_for_type(lv->datatype, var_line); - } - //must be added later, to avoid self-referencing. - p_block->variables.insert(n, lv); - - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = n; - id->declared_block = p_block; - id->line = var_line; - - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_ASSIGN; - op->arguments.push_back(id); - op->arguments.push_back(assigned); - op->line = var_line; - p_block->statements.push_back(op); - lv->assign_op = op; - lv->assign = assigned; - - if (!_end_statement()) { - _set_error("Expected end of statement (\"var\")."); - return; + if (p_root_pattern != nullptr) { + if (p_root_pattern->has_bind(pattern->bind->name)) { + push_error(vformat(R"(Bind variable name "%s" was already used in this pattern.)", pattern->bind->name)); + return nullptr; } + } - } break; - case GDScriptTokenizer::TK_CF_IF: { + if (current_suite->has_local(pattern->bind->name)) { + push_error(vformat(R"(There's already a %s named "%s" in this scope.)", current_suite->get_local(pattern->bind->name).get_name(), pattern->bind->name)); + return nullptr; + } - tokenizer->advance(); + root_pattern->binds[pattern->bind->name] = pattern->bind; - Node *condition = _parse_and_reduce_expression(p_block, p_static); - if (!condition) { - if (_recover_from_completion()) { - break; + } break; + case GDScriptTokenizer::Token::UNDERSCORE: + // Wildcard. + advance(); + pattern->pattern_type = PatternNode::PT_WILDCARD; + break; + case GDScriptTokenizer::Token::PERIOD_PERIOD: + // Rest. + advance(); + pattern->pattern_type = PatternNode::PT_REST; + break; + case GDScriptTokenizer::Token::BRACKET_OPEN: { + // Array. + advance(); + pattern->pattern_type = PatternNode::PT_ARRAY; + + if (!check(GDScriptTokenizer::Token::BRACKET_CLOSE)) { + do { + PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); + if (sub_pattern == nullptr) { + continue; } - return; - } - - ControlFlowNode *cf_if = alloc_node<ControlFlowNode>(); - - cf_if->cf_type = ControlFlowNode::CF_IF; - cf_if->arguments.push_back(condition); - - cf_if->body = alloc_node<BlockNode>(); - cf_if->body->parent_block = p_block; - cf_if->body->if_condition = condition; //helps code completion - - p_block->sub_blocks.push_back(cf_if->body); - - if (!_enter_indent_block(cf_if->body)) { - _set_error("Expected an indented block after \"if\"."); - p_block->end_line = tokenizer->get_token_line(); - return; - } - - current_block = cf_if->body; - _parse_block(cf_if->body, p_static); - current_block = p_block; - - if (error_set) - return; - p_block->statements.push_back(cf_if); - - bool all_have_return = cf_if->body->has_return; - bool have_else = false; - - while (true) { - - while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) - ; - - if (indent_level.back()->get().indent < current_level.indent) { //not at current indent level - p_block->end_line = tokenizer->get_token_line(); - return; + if (pattern->rest_used) { + push_error(R"(The ".." pattern must be the last element in the pattern array.)"); + } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { + pattern->rest_used = true; } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELIF) { - - if (indent_level.back()->get().indent > current_level.indent) { - - _set_error("Invalid indentation."); - return; - } - - tokenizer->advance(); - - cf_if->body_else = alloc_node<BlockNode>(); - cf_if->body_else->parent_block = p_block; - p_block->sub_blocks.push_back(cf_if->body_else); - - ControlFlowNode *cf_else = alloc_node<ControlFlowNode>(); - cf_else->cf_type = ControlFlowNode::CF_IF; - - //condition - Node *condition2 = _parse_and_reduce_expression(p_block, p_static); - if (!condition2) { - if (_recover_from_completion()) { - break; - } - return; - } - cf_else->arguments.push_back(condition2); - cf_else->cf_type = ControlFlowNode::CF_IF; - - cf_if->body_else->statements.push_back(cf_else); - cf_if = cf_else; - cf_if->body = alloc_node<BlockNode>(); - cf_if->body->parent_block = p_block; - p_block->sub_blocks.push_back(cf_if->body); - - if (!_enter_indent_block(cf_if->body)) { - _set_error("Expected an indented block after \"elif\"."); - p_block->end_line = tokenizer->get_token_line(); - return; - } - - current_block = cf_else->body; - _parse_block(cf_else->body, p_static); - current_block = p_block; - if (error_set) - return; - - all_have_return = all_have_return && cf_else->body->has_return; - - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) { - - if (indent_level.back()->get().indent > current_level.indent) { - _set_error("Invalid indentation."); - return; - } - - tokenizer->advance(); - cf_if->body_else = alloc_node<BlockNode>(); - cf_if->body_else->parent_block = p_block; - p_block->sub_blocks.push_back(cf_if->body_else); - - if (!_enter_indent_block(cf_if->body_else)) { - _set_error("Expected an indented block after \"else\"."); - p_block->end_line = tokenizer->get_token_line(); - return; + pattern->array.push_back(sub_pattern); + } while (match(GDScriptTokenizer::Token::COMMA)); + } + consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" to close the array pattern.)"); + break; + } + case GDScriptTokenizer::Token::BRACE_OPEN: { + // Dictionary. + advance(); + pattern->pattern_type = PatternNode::PT_DICTIONARY; + + if (!check(GDScriptTokenizer::Token::BRACE_CLOSE) && !is_at_end()) { + do { + if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { + // Rest. + if (pattern->rest_used) { + push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else { + PatternNode *sub_pattern = alloc_node<PatternNode>(); + sub_pattern->pattern_type = PatternNode::PT_REST; + pattern->dictionary.push_back({ nullptr, sub_pattern }); + pattern->rest_used = true; } - current_block = cf_if->body_else; - _parse_block(cf_if->body_else, p_static); - current_block = p_block; - if (error_set) - return; - - all_have_return = all_have_return && cf_if->body_else->has_return; - have_else = true; - - break; //after else, exit - - } else - break; - } - - cf_if->body->has_return = all_have_return; - // If there's no else block, path out of the if might not have a return - p_block->has_return = all_have_return && have_else; - - } break; - case GDScriptTokenizer::TK_CF_WHILE: { - - tokenizer->advance(); - Node *condition2 = _parse_and_reduce_expression(p_block, p_static); - if (!condition2) { - if (_recover_from_completion()) { - break; - } - return; - } - - ControlFlowNode *cf_while = alloc_node<ControlFlowNode>(); - - cf_while->cf_type = ControlFlowNode::CF_WHILE; - cf_while->arguments.push_back(condition2); - - cf_while->body = alloc_node<BlockNode>(); - cf_while->body->parent_block = p_block; - p_block->sub_blocks.push_back(cf_while->body); - - if (!_enter_indent_block(cf_while->body)) { - _set_error("Expected an indented block after \"while\"."); - p_block->end_line = tokenizer->get_token_line(); - return; - } - - current_block = cf_while->body; - _parse_block(cf_while->body, p_static); - current_block = p_block; - if (error_set) - return; - p_block->has_return = cf_while->body->has_return; - p_block->statements.push_back(cf_while); - } break; - case GDScriptTokenizer::TK_CF_FOR: { - - tokenizer->advance(); - - if (!tokenizer->is_token_literal(0, true)) { - - _set_error("Identifier expected after \"for\"."); - } - - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = tokenizer->get_token_identifier(); - - tokenizer->advance(); - - if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_IN) { - _set_error("\"in\" expected after identifier."); - return; - } - - tokenizer->advance(); - - Node *container = _parse_and_reduce_expression(p_block, p_static); - if (!container) { - if (_recover_from_completion()) { - break; - } - return; - } - - DataType iter_type; - - if (container->type == Node::TYPE_OPERATOR) { - - OperatorNode *op = static_cast<OperatorNode *>(container); - if (op->op == OperatorNode::OP_CALL && op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && static_cast<BuiltInFunctionNode *>(op->arguments[0])->function == GDScriptFunctions::GEN_RANGE) { - //iterating a range, so see if range() can be optimized without allocating memory, by replacing it by vectors (which can work as iterable too!) - - Vector<Node *> args; - Vector<double> constants; - - bool constant = true; - - for (int i = 1; i < op->arguments.size(); i++) { - args.push_back(op->arguments[i]); - if (op->arguments[i]->type == Node::TYPE_CONSTANT) { - ConstantNode *c = static_cast<ConstantNode *>(op->arguments[i]); - if (c->value.get_type() == Variant::FLOAT || c->value.get_type() == Variant::INT) { - constants.push_back(c->value); - } - } else { - constant = false; + } else { + ExpressionNode *key = parse_expression(false); + if (key == nullptr) { + push_error(R"(Expected expression as key for dictionary pattern.)"); + } + if (match(GDScriptTokenizer::Token::COLON)) { + // Value pattern. + PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); + if (sub_pattern == nullptr) { + continue; } - } - - if (args.size() > 0 && args.size() < 4) { - - if (constant) { - - ConstantNode *cn = alloc_node<ConstantNode>(); - switch (args.size()) { - case 1: cn->value = (int)constants[0]; break; - case 2: cn->value = Vector2(constants[0], constants[1]); break; - case 3: cn->value = Vector3(constants[0], constants[1], constants[2]); break; - } - cn->datatype = _type_from_variant(cn->value); - container = cn; + if (pattern->rest_used) { + push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { + push_error(R"(The ".." pattern cannot be used as a value.)"); } else { - OperatorNode *on = alloc_node<OperatorNode>(); - on->op = OperatorNode::OP_CALL; - - TypeNode *tn = alloc_node<TypeNode>(); - on->arguments.push_back(tn); - - switch (args.size()) { - case 1: tn->vtype = Variant::INT; break; - case 2: tn->vtype = Variant::VECTOR2; break; - case 3: tn->vtype = Variant::VECTOR3; break; - } - - for (int i = 0; i < args.size(); i++) { - on->arguments.push_back(args[i]); - } - - container = on; + pattern->dictionary.push_back({ key, sub_pattern }); } + } else { + // Key match only. + pattern->dictionary.push_back({ key, nullptr }); } - - iter_type.has_type = true; - iter_type.kind = DataType::BUILTIN; - iter_type.builtin_type = Variant::INT; - } - } - - ControlFlowNode *cf_for = alloc_node<ControlFlowNode>(); - - cf_for->cf_type = ControlFlowNode::CF_FOR; - cf_for->arguments.push_back(id); - cf_for->arguments.push_back(container); - - cf_for->body = alloc_node<BlockNode>(); - cf_for->body->parent_block = p_block; - p_block->sub_blocks.push_back(cf_for->body); - - if (!_enter_indent_block(cf_for->body)) { - _set_error("Expected indented block after \"for\"."); - p_block->end_line = tokenizer->get_token_line(); - return; - } - - current_block = cf_for->body; - - // this is for checking variable for redefining - // inside this _parse_block - LocalVarNode *lv = alloc_node<LocalVarNode>(); - lv->name = id->name; - lv->line = id->line; - lv->assignments++; - id->declared_block = cf_for->body; - lv->set_datatype(iter_type); - id->set_datatype(iter_type); - cf_for->body->variables.insert(id->name, lv); - _parse_block(cf_for->body, p_static); - current_block = p_block; - - if (error_set) - return; - p_block->has_return = cf_for->body->has_return; - p_block->statements.push_back(cf_for); - } break; - case GDScriptTokenizer::TK_CF_CONTINUE: { - - _mark_line_as_safe(tokenizer->get_token_line()); - tokenizer->advance(); - ControlFlowNode *cf_continue = alloc_node<ControlFlowNode>(); - cf_continue->cf_type = ControlFlowNode::CF_CONTINUE; - p_block->statements.push_back(cf_continue); - if (!_end_statement()) { - _set_error("Expected end of statement (\"continue\")."); - return; - } - } break; - case GDScriptTokenizer::TK_CF_BREAK: { - - _mark_line_as_safe(tokenizer->get_token_line()); - tokenizer->advance(); - ControlFlowNode *cf_break = alloc_node<ControlFlowNode>(); - cf_break->cf_type = ControlFlowNode::CF_BREAK; - p_block->statements.push_back(cf_break); - if (!_end_statement()) { - _set_error("Expected end of statement (\"break\")."); - return; - } - } break; - case GDScriptTokenizer::TK_CF_RETURN: { - - tokenizer->advance(); - ControlFlowNode *cf_return = alloc_node<ControlFlowNode>(); - cf_return->cf_type = ControlFlowNode::CF_RETURN; - cf_return->line = tokenizer->get_token_line(-1); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON || tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { - //expect end of statement - p_block->statements.push_back(cf_return); - if (!_end_statement()) { - return; - } - } else { - //expect expression - Node *retexpr = _parse_and_reduce_expression(p_block, p_static); - if (!retexpr) { - if (_recover_from_completion()) { - break; - } - return; - } - cf_return->arguments.push_back(retexpr); - p_block->statements.push_back(cf_return); - if (!_end_statement()) { - _set_error("Expected end of statement after return expression."); - return; } - } - p_block->has_return = true; - - } break; - case GDScriptTokenizer::TK_CF_MATCH: { - - tokenizer->advance(); - - MatchNode *match_node = alloc_node<MatchNode>(); - - Node *val_to_match = _parse_and_reduce_expression(p_block, p_static); - - if (!val_to_match) { - if (_recover_from_completion()) { - break; - } - return; - } - - match_node->val_to_match = val_to_match; - - if (!_enter_indent_block()) { - _set_error("Expected indented pattern matching block after \"match\"."); - return; - } - - BlockNode *compiled_branches = alloc_node<BlockNode>(); - compiled_branches->parent_block = p_block; - compiled_branches->parent_class = p_block->parent_class; - - p_block->sub_blocks.push_back(compiled_branches); - - _parse_pattern_block(compiled_branches, match_node->branches, p_static); - - if (error_set) return; - - ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>(); - match_cf_node->cf_type = ControlFlowNode::CF_MATCH; - match_cf_node->match = match_node; - match_cf_node->body = compiled_branches; - - p_block->has_return = p_block->has_return || compiled_branches->has_return; - p_block->statements.push_back(match_cf_node); - - _end_statement(); - } break; - case GDScriptTokenizer::TK_PR_ASSERT: { - - tokenizer->advance(); - - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - _set_error("Expected '(' after assert"); - return; - } - - tokenizer->advance(); - - Vector<Node *> args; - const bool result = _parse_arguments(p_block, args, p_static); - if (!result) { - return; - } - - if (args.empty() || args.size() > 2) { - _set_error("Wrong number of arguments, expected 1 or 2"); - return; - } - - AssertNode *an = alloc_node<AssertNode>(); - an->condition = _reduce_expression(args[0], p_static); - - if (args.size() == 2) { - an->message = _reduce_expression(args[1], p_static); - } else { - ConstantNode *message_node = alloc_node<ConstantNode>(); - message_node->value = String(); - an->message = message_node; - } - - p_block->statements.push_back(an); - - if (!_end_statement()) { - _set_error("Expected end of statement after \"assert\"."); - return; - } - } break; - case GDScriptTokenizer::TK_PR_BREAKPOINT: { - - tokenizer->advance(); - BreakpointNode *bn = alloc_node<BreakpointNode>(); - p_block->statements.push_back(bn); - - if (!_end_statement()) { - _set_error("Expected end of statement after \"breakpoint\"."); - return; - } - } break; - default: { - - Node *expression = _parse_and_reduce_expression(p_block, p_static, false, true); - if (!expression) { - if (_recover_from_completion()) { - break; - } - return; - } - p_block->statements.push_back(expression); - if (!_end_statement()) { - // Attempt to guess a better error message if the user "retypes" a variable - if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { - _set_error("Unexpected ':=', use '=' instead. Expected end of statement after expression."); - } else { - _set_error(String() + "Expected end of statement after expression, got " + tokenizer->get_token_name(tokenizer->get_token()) + " instead"); - } - return; - } - - } break; - } - } -} - -bool GDScriptParser::_parse_newline() { - - if (tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) { - - IndentLevel current_level = indent_level.back()->get(); - int indent = tokenizer->get_token_line_indent(); - int tabs = tokenizer->get_token_line_tab_indent(); - IndentLevel new_level(indent, tabs); - - if (new_level.is_mixed(current_level)) { - _set_error("Mixed tabs and spaces in indentation."); - return false; - } - - if (indent > current_level.indent) { - _set_error("Unexpected indentation."); - return false; + } while (match(GDScriptTokenizer::Token::COMMA)); + } + consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)"); + break; } - - if (indent < current_level.indent) { - - while (indent < current_level.indent) { - - //exit block - if (indent_level.size() == 1) { - _set_error("Invalid indentation. Bug?"); - return false; - } - - indent_level.pop_back(); - - if (indent_level.back()->get().indent < indent) { - - _set_error("Unindent does not match any outer indentation level."); - return false; - } - - if (indent_level.back()->get().is_mixed(current_level)) { - _set_error("Mixed tabs and spaces in indentation."); - return false; - } - - current_level = indent_level.back()->get(); + default: { + // Expression. + ExpressionNode *expression = parse_expression(false); + if (expression == nullptr) { + push_error(R"(Expected expression for match pattern.)"); + } else { + pattern->pattern_type = PatternNode::PT_EXPRESSION; + pattern->expression = expression; } - - tokenizer->advance(); - return false; + break; } } - tokenizer->advance(); - return true; + return pattern; } -void GDScriptParser::_parse_extends(ClassNode *p_class) { - - if (p_class->extends_used) { +bool GDScriptParser::PatternNode::has_bind(const StringName &p_name) { + return binds.has(p_name); +} - _set_error("\"extends\" can only be present once per script."); - return; - } +GDScriptParser::IdentifierNode *GDScriptParser::PatternNode::get_bind(const StringName &p_name) { + return binds[p_name]; +} - if (!p_class->constant_expressions.empty() || !p_class->subclasses.empty() || !p_class->functions.empty() || !p_class->variables.empty()) { +GDScriptParser::WhileNode *GDScriptParser::parse_while() { + WhileNode *n_while = alloc_node<WhileNode>(); - _set_error("\"extends\" must be used before anything else."); - return; + n_while->condition = parse_expression(false); + if (n_while->condition == nullptr) { + push_error(R"(Expected conditional expression after "while".)"); } - p_class->extends_used = true; - - tokenizer->advance(); + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "while" condition.)"); - if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token_type() == Variant::OBJECT) { - p_class->extends_class.push_back(Variant::get_type_name(Variant::OBJECT)); - tokenizer->advance(); - return; - } + // Save break/continue state. + bool could_break = can_break; + bool could_continue = can_continue; - // see if inheritance happens from a file - if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) { + // Allow break/continue. + can_break = true; + can_continue = true; - Variant constant = tokenizer->get_token_constant(); - if (constant.get_type() != Variant::STRING) { + n_while->loop = parse_suite(R"("while" block)"); - _set_error("\"extends\" constant must be a string."); - return; - } + // Reset break/continue state. + can_break = could_break; + can_continue = could_continue; - p_class->extends_file = constant; - tokenizer->advance(); - - // Add parent script as a dependency - String parent = constant; - if (parent.is_rel_path()) { - parent = base_path.plus_file(parent).simplify_path(); - } - dependencies.push_back(parent); + return n_while; +} - if (tokenizer->get_token() != GDScriptTokenizer::TK_PERIOD) { - return; - } else - tokenizer->advance(); +GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_precedence, bool p_can_assign, bool p_stop_on_assign) { + // Switch multiline mode on for grouping tokens. + // Do this early to avoid the tokenizer generating whitespace tokens. + switch (current.type) { + case GDScriptTokenizer::Token::PARENTHESIS_OPEN: + case GDScriptTokenizer::Token::BRACE_OPEN: + case GDScriptTokenizer::Token::BRACKET_OPEN: + push_multiline(true); + break; + default: + break; // Nothing to do. } - while (true) { + // Completion can appear whenever an expression is expected. + make_completion_context(COMPLETION_IDENTIFIER, nullptr); - switch (tokenizer->get_token()) { + GDScriptTokenizer::Token token = advance(); + ParseFunction prefix_rule = get_rule(token.type)->prefix; - case GDScriptTokenizer::TK_IDENTIFIER: { - - StringName identifier = tokenizer->get_token_identifier(); - p_class->extends_class.push_back(identifier); - } break; - - case GDScriptTokenizer::TK_PERIOD: - break; + if (prefix_rule == nullptr) { + // Expected expression. Let the caller give the proper error message. + return nullptr; + } - default: { + ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); - _set_error("Invalid \"extends\" syntax, expected string constant (path) and/or identifier (parent class)."); - return; - } + while (p_precedence <= get_rule(current.type)->precedence) { + if (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) { + return previous_operand; } - - tokenizer->advance(1); - - switch (tokenizer->get_token()) { - - case GDScriptTokenizer::TK_IDENTIFIER: - case GDScriptTokenizer::TK_PERIOD: - continue; - + // Also switch multiline mode on here for infix operators. + switch (current.type) { + // case GDScriptTokenizer::Token::BRACE_OPEN: // Not an infix operator. + case GDScriptTokenizer::Token::PARENTHESIS_OPEN: + case GDScriptTokenizer::Token::BRACKET_OPEN: + push_multiline(true); + break; default: - return; + break; // Nothing to do. } + token = advance(); + ParseFunction infix_rule = get_rule(token.type)->infix; + previous_operand = (this->*infix_rule)(previous_operand, p_can_assign); } -} -void GDScriptParser::_parse_class(ClassNode *p_class) { + return previous_operand; +} - IndentLevel current_level = indent_level.back()->get(); +GDScriptParser::ExpressionNode *GDScriptParser::parse_expression(bool p_can_assign, bool p_stop_on_assign) { + return parse_precedence(PREC_ASSIGNMENT, p_can_assign, p_stop_on_assign); +} - while (true) { +GDScriptParser::IdentifierNode *GDScriptParser::parse_identifier() { + return static_cast<IdentifierNode *>(parse_identifier(nullptr, false)); +} - GDScriptTokenizer::Token token = tokenizer->get_token(); - if (error_set) - return; +GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign) { + if (!previous.is_identifier()) { + ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token."); + } + IdentifierNode *identifier = alloc_node<IdentifierNode>(); + identifier->name = previous.get_identifier(); - if (current_level.indent > indent_level.back()->get().indent) { - p_class->end_line = tokenizer->get_token_line(); - return; //go back a level + if (current_suite != nullptr && current_suite->has_local(identifier->name)) { + const SuiteNode::Local &declaration = current_suite->get_local(identifier->name); + switch (declaration.type) { + case SuiteNode::Local::CONSTANT: + identifier->source = IdentifierNode::LOCAL_CONSTANT; + identifier->constant_source = declaration.constant; + declaration.constant->usages++; + break; + case SuiteNode::Local::VARIABLE: + identifier->source = IdentifierNode::LOCAL_VARIABLE; + identifier->variable_source = declaration.variable; + declaration.variable->usages++; + break; + case SuiteNode::Local::PARAMETER: + identifier->source = IdentifierNode::FUNCTION_PARAMETER; + identifier->parameter_source = declaration.parameter; + declaration.parameter->usages++; + break; + case SuiteNode::Local::FOR_VARIABLE: + identifier->source = IdentifierNode::LOCAL_ITERATOR; + identifier->bind_source = declaration.bind; + declaration.bind->usages++; + break; + case SuiteNode::Local::PATTERN_BIND: + identifier->source = IdentifierNode::LOCAL_BIND; + identifier->bind_source = declaration.bind; + declaration.bind->usages++; + break; + case SuiteNode::Local::UNDEFINED: + ERR_FAIL_V_MSG(nullptr, "Undefined local found."); } + } - switch (token) { - - case GDScriptTokenizer::TK_CURSOR: { - tokenizer->advance(); - } break; - case GDScriptTokenizer::TK_EOF: { - p_class->end_line = tokenizer->get_token_line(); - return; // End of file! - } break; - case GDScriptTokenizer::TK_ERROR: { - return; // Go back. - } break; - case GDScriptTokenizer::TK_NEWLINE: { - if (!_parse_newline()) { - if (!error_set) { - p_class->end_line = tokenizer->get_token_line(); - } - return; - } - } break; - case GDScriptTokenizer::TK_PR_EXTENDS: { - - _mark_line_as_safe(tokenizer->get_token_line()); - _parse_extends(p_class); - if (error_set) - return; - if (!_end_statement()) { - _set_error("Expected end of statement after \"extends\"."); - return; - } - - } break; - case GDScriptTokenizer::TK_PR_CLASS_NAME: { - - _mark_line_as_safe(tokenizer->get_token_line()); - if (p_class->owner) { - _set_error("\"class_name\" is only valid for the main class namespace."); - return; - } - if (self_path.begins_with("res://") && self_path.find("::") != -1) { - _set_error("\"class_name\" isn't allowed in built-in scripts."); - return; - } - if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) { - - _set_error("\"class_name\" syntax: \"class_name <UniqueName>\""); - return; - } - if (p_class->classname_used) { - _set_error("\"class_name\" can only be present once per script."); - return; - } - - p_class->classname_used = true; - - p_class->name = tokenizer->get_token_identifier(1); - - if (self_path != String() && ScriptServer::is_global_class(p_class->name) && ScriptServer::get_global_class_path(p_class->name) != self_path) { - _set_error("Unique global class \"" + p_class->name + "\" already exists at path: " + ScriptServer::get_global_class_path(p_class->name)); - return; - } - - if (ClassDB::class_exists(p_class->name)) { - _set_error("The class \"" + p_class->name + "\" shadows a native class."); - return; - } - - if (p_class->classname_used && ProjectSettings::get_singleton()->has_setting("autoload/" + p_class->name)) { - const String autoload_path = ProjectSettings::get_singleton()->get_setting("autoload/" + p_class->name); - if (autoload_path.begins_with("*")) { - // It's a singleton, and not just a regular AutoLoad script. - _set_error("The class \"" + p_class->name + "\" conflicts with the AutoLoad singleton of the same name, and is therefore redundant. Remove the class_name declaration to fix this error."); - } - return; - } - - tokenizer->advance(2); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - tokenizer->advance(); - - if ((tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING)) { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - Variant constant = tokenizer->get_token_constant(); - String icon_path = constant.operator String(); - - String abs_icon_path = icon_path.is_rel_path() ? self_path.get_base_dir().plus_file(icon_path).simplify_path() : icon_path; - if (!FileAccess::exists(abs_icon_path)) { - _set_error("No class icon found at: " + abs_icon_path); - return; - } - - p_class->icon_path = icon_path; - } -#endif - - tokenizer->advance(); - } else { - _set_error("The optional parameter after \"class_name\" must be a string constant file path to an icon."); - return; - } - - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) { - _set_error("The class icon must be separated by a comma."); - return; - } - - } break; - case GDScriptTokenizer::TK_PR_TOOL: { - - if (p_class->tool) { - - _set_error("The \"tool\" keyword can only be present once per script."); - return; - } - - p_class->tool = true; - tokenizer->advance(); - - } break; - case GDScriptTokenizer::TK_PR_CLASS: { - //class inside class :D - - StringName name; - - if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) { - - _set_error("\"class\" syntax: \"class <Name>:\" or \"class <Name> extends <BaseClass>:\""); - return; - } - name = tokenizer->get_token_identifier(1); - tokenizer->advance(2); - - // Check if name is shadowing something else - if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) { - _set_error("The class \"" + String(name) + "\" shadows a native class."); - return; - } - if (ScriptServer::is_global_class(name)) { - _set_error("Can't override name of the unique global class \"" + name + "\". It already exists at: " + ScriptServer::get_global_class_path(p_class->name)); - return; - } - ClassNode *outer_class = p_class; - while (outer_class) { - for (int i = 0; i < outer_class->subclasses.size(); i++) { - if (outer_class->subclasses[i]->name == name) { - _set_error("Another class named \"" + String(name) + "\" already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ")."); - return; - } - } - if (outer_class->constant_expressions.has(name)) { - _set_error("A constant named \"" + String(name) + "\" already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ")."); - return; - } - - outer_class = outer_class->owner; - } - - ClassNode *newclass = alloc_node<ClassNode>(); - newclass->initializer = alloc_node<BlockNode>(); - newclass->initializer->parent_class = newclass; - newclass->ready = alloc_node<BlockNode>(); - newclass->ready->parent_class = newclass; - newclass->name = name; - newclass->owner = p_class; - - p_class->subclasses.push_back(newclass); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_EXTENDS) { - - _parse_extends(newclass); - if (error_set) - return; - } - - if (!_enter_indent_block()) { - - _set_error("Indented block expected."); - return; - } - current_class = newclass; - _parse_class(newclass); - current_class = p_class; - - } break; - /* this is for functions.... - case GDScriptTokenizer::TK_CF_PASS: { - - tokenizer->advance(1); - } break; - */ - case GDScriptTokenizer::TK_PR_STATIC: { - tokenizer->advance(); - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - - _set_error("Expected \"func\"."); - return; - } - - [[fallthrough]]; - } - case GDScriptTokenizer::TK_PR_FUNCTION: { - - bool _static = false; - pending_newline = -1; - - if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_STATIC) { - - _static = true; - } - - tokenizer->advance(); - StringName name; - - if (_get_completable_identifier(COMPLETION_VIRTUAL_FUNC, name)) { - } - - if (name == StringName()) { - - _set_error("Expected an identifier after \"func\" (syntax: \"func <identifier>([arguments]):\")."); - return; - } - - for (int i = 0; i < p_class->functions.size(); i++) { - if (p_class->functions[i]->name == name) { - _set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->functions[i]->line) + ")."); - } - } - for (int i = 0; i < p_class->static_functions.size(); i++) { - if (p_class->static_functions[i]->name == name) { - _set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->static_functions[i]->line) + ")."); - } - } - -#ifdef DEBUG_ENABLED - if (p_class->constant_expressions.has(name)) { - _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name); - } - for (int i = 0; i < p_class->variables.size(); i++) { - if (p_class->variables[i].identifier == name) { - _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_VARIABLE, -1, name); - } - } - for (int i = 0; i < p_class->subclasses.size(); i++) { - if (p_class->subclasses[i]->name == name) { - _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name); - } - } -#endif // DEBUG_ENABLED - - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - - _set_error("Expected \"(\" after the identifier (syntax: \"func <identifier>([arguments]):\" )."); - return; - } - - tokenizer->advance(); - - Vector<StringName> arguments; - Vector<DataType> argument_types; - Vector<Node *> default_values; -#ifdef DEBUG_ENABLED - Vector<int> arguments_usage; -#endif // DEBUG_ENABLED - - int fnline = tokenizer->get_token_line(); - - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - //has arguments - bool defaulting = false; - while (true) { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { - tokenizer->advance(); - continue; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_VAR) { - - tokenizer->advance(); //var before the identifier is allowed - } - - if (!tokenizer->is_token_literal(0, true)) { - - _set_error("Expected an identifier for an argument."); - return; - } - - StringName argname = tokenizer->get_token_identifier(); - for (int i = 0; i < arguments.size(); i++) { - if (arguments[i] == argname) { - _set_error("The argument name \"" + String(argname) + "\" is defined multiple times."); - return; - } - } - arguments.push_back(argname); -#ifdef DEBUG_ENABLED - arguments_usage.push_back(0); -#endif // DEBUG_ENABLED - - tokenizer->advance(); - - DataType argtype; - if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { - if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { - argtype.infer_type = true; - tokenizer->advance(); - } else if (!_parse_type(argtype)) { - _set_error("Expected a type for an argument."); - return; - } - } - argument_types.push_back(argtype); - - if (defaulting && tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) { - - _set_error("Default parameter expected."); - return; - } - - //tokenizer->advance(); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { - defaulting = true; - tokenizer->advance(1); - Node *defval = _parse_and_reduce_expression(p_class, _static); - if (!defval || error_set) - return; - - OperatorNode *on = alloc_node<OperatorNode>(); - on->op = OperatorNode::OP_ASSIGN; - on->line = fnline; - - IdentifierNode *in = alloc_node<IdentifierNode>(); - in->name = argname; - in->line = fnline; - - on->arguments.push_back(in); - on->arguments.push_back(defval); - /* no .. - if (defval->type!=Node::TYPE_CONSTANT) { - - _set_error("default argument must be constant"); - } - */ - default_values.push_back(on); - } - - while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { - tokenizer->advance(); - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - tokenizer->advance(); - continue; - } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - - _set_error("Expected \",\" or \")\"."); - return; - } - - break; - } - } - - tokenizer->advance(); - - BlockNode *block = alloc_node<BlockNode>(); - block->parent_class = p_class; - - FunctionNode *function = alloc_node<FunctionNode>(); - function->name = name; - function->arguments = arguments; - function->argument_types = argument_types; - function->default_values = default_values; - function->_static = _static; - function->line = fnline; -#ifdef DEBUG_ENABLED - function->arguments_usage = arguments_usage; -#endif // DEBUG_ENABLED - function->rpc_mode = rpc_mode; - rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; - - if (name == "_init") { - - if (_static) { - _set_error("The constructor cannot be static."); - return; - } - - if (p_class->extends_used) { - - OperatorNode *cparent = alloc_node<OperatorNode>(); - cparent->op = OperatorNode::OP_PARENT_CALL; - block->statements.push_back(cparent); - - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = "_init"; - cparent->arguments.push_back(id); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) { - tokenizer->advance(); - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - _set_error("Expected \"(\" for parent constructor arguments."); - return; - } - tokenizer->advance(); - - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - //has arguments - parenthesis++; - while (true) { - - current_function = function; - Node *arg = _parse_and_reduce_expression(p_class, _static); - current_function = nullptr; - cparent->arguments.push_back(arg); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - tokenizer->advance(); - continue; - } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - - _set_error("Expected \",\" or \")\"."); - return; - } - - break; - } - parenthesis--; - } - - tokenizer->advance(); - } - } else { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) { - - _set_error("Parent constructor call found for a class without inheritance."); - return; - } - } - } - - DataType return_type; - if (tokenizer->get_token() == GDScriptTokenizer::TK_FORWARD_ARROW) { - - if (!_parse_type(return_type, true)) { - _set_error("Expected a return type for the function."); - return; - } - } - - if (!_enter_indent_block(block)) { - - _set_error("Indented block expected."); - return; - } - - function->return_type = return_type; - - if (_static) - p_class->static_functions.push_back(function); - else - p_class->functions.push_back(function); - - current_function = function; - function->body = block; - current_block = block; - _parse_block(block, _static); - current_block = nullptr; - - //arguments - } break; - case GDScriptTokenizer::TK_PR_SIGNAL: { - tokenizer->advance(); - - if (!tokenizer->is_token_literal()) { - _set_error("Expected an identifier after \"signal\"."); - return; - } - - ClassNode::Signal sig; - sig.name = tokenizer->get_token_identifier(); - sig.emissions = 0; - sig.line = tokenizer->get_token_line(); - tokenizer->advance(); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - tokenizer->advance(); - while (true) { - if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { - tokenizer->advance(); - continue; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - tokenizer->advance(); - break; - } - - if (!tokenizer->is_token_literal(0, true)) { - _set_error("Expected an identifier in a \"signal\" argument."); - return; - } - - sig.arguments.push_back(tokenizer->get_token_identifier()); - tokenizer->advance(); - - while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { - tokenizer->advance(); - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - tokenizer->advance(); - } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected \",\" or \")\" after a \"signal\" parameter identifier."); - return; - } - } - } - - p_class->_signals.push_back(sig); - - if (!_end_statement()) { - _set_error("Expected end of statement (\"signal\")."); - return; - } - } break; - case GDScriptTokenizer::TK_PR_EXPORT: { - - tokenizer->advance(); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - -#define _ADVANCE_AND_CONSUME_NEWLINES \ - do { \ - tokenizer->advance(); \ - } while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) - - _ADVANCE_AND_CONSUME_NEWLINES; - parenthesis++; - - String hint_prefix = ""; - bool is_arrayed = false; - - while (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && - tokenizer->get_token_type() == Variant::ARRAY && - tokenizer->get_token(1) == GDScriptTokenizer::TK_COMMA) { - tokenizer->advance(); // Array - tokenizer->advance(); // Comma - if (is_arrayed) { - hint_prefix += itos(Variant::ARRAY) + ":"; - } else { - is_arrayed = true; - } - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) { - - Variant::Type type = tokenizer->get_token_type(); - if (type == Variant::NIL) { - _set_error("Can't export null type."); - return; - } - if (type == Variant::OBJECT) { - _set_error("Can't export raw object type."); - return; - } - current_export.type = type; - current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - // hint expected next! - _ADVANCE_AND_CONSUME_NEWLINES; - - switch (type) { - - case Variant::INT: { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") { - - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - _set_error("Expected \",\" in the bit flags hint."); - return; - } - - current_export.hint = PROPERTY_HINT_FLAGS; - _ADVANCE_AND_CONSUME_NEWLINES; - - bool first = true; - while (true) { - - if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { - current_export = PropertyInfo(); - _set_error("Expected a string constant in the named bit flags hint."); - return; - } - - String c = tokenizer->get_token_constant(); - if (!first) - current_export.hint_string += ","; - else - first = false; - - current_export.hint_string += c.xml_escape(); - - _ADVANCE_AND_CONSUME_NEWLINES; - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) - break; - - if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - current_export = PropertyInfo(); - _set_error("Expected \")\" or \",\" in the named bit flags hint."); - return; - } - _ADVANCE_AND_CONSUME_NEWLINES; - } - - break; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_RENDER") { - - _ADVANCE_AND_CONSUME_NEWLINES; - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected \")\" in the layers 2D render hint."); - return; - } - current_export.hint = PROPERTY_HINT_LAYERS_2D_RENDER; - break; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_PHYSICS") { - - _ADVANCE_AND_CONSUME_NEWLINES; - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected \")\" in the layers 2D physics hint."); - return; - } - current_export.hint = PROPERTY_HINT_LAYERS_2D_PHYSICS; - break; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_RENDER") { - - _ADVANCE_AND_CONSUME_NEWLINES; - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected \")\" in the layers 3D render hint."); - return; - } - current_export.hint = PROPERTY_HINT_LAYERS_3D_RENDER; - break; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_PHYSICS") { - - _ADVANCE_AND_CONSUME_NEWLINES; - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected \")\" in the layers 3D physics hint."); - return; - } - current_export.hint = PROPERTY_HINT_LAYERS_3D_PHYSICS; - break; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) { - //enumeration - current_export.hint = PROPERTY_HINT_ENUM; - bool first = true; - while (true) { - - if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { - - current_export = PropertyInfo(); - _set_error("Expected a string constant in the enumeration hint."); - return; - } - - String c = tokenizer->get_token_constant(); - if (!first) - current_export.hint_string += ","; - else - first = false; - - current_export.hint_string += c.xml_escape(); - - _ADVANCE_AND_CONSUME_NEWLINES; - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) - break; - - if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - current_export = PropertyInfo(); - _set_error("Expected \")\" or \",\" in the enumeration hint."); - return; - } - - _ADVANCE_AND_CONSUME_NEWLINES; - } - - break; - } - - [[fallthrough]]; - } - case Variant::FLOAT: { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EASE") { - current_export.hint = PROPERTY_HINT_EXP_EASING; - _ADVANCE_AND_CONSUME_NEWLINES; - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected \")\" in the hint."); - return; - } - break; - } - - // range - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EXP") { - - current_export.hint = PROPERTY_HINT_EXP_RANGE; - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) - break; - else if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - _set_error("Expected \")\" or \",\" in the exponential range hint."); - return; - } - _ADVANCE_AND_CONSUME_NEWLINES; - } else - current_export.hint = PROPERTY_HINT_RANGE; - - float sign = 1.0; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) { - sign = -1; - _ADVANCE_AND_CONSUME_NEWLINES; - } - if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) { - - current_export = PropertyInfo(); - _set_error("Expected a range in the numeric hint."); - return; - } - - current_export.hint_string = rtos(sign * double(tokenizer->get_token_constant())); - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - current_export.hint_string = "0," + current_export.hint_string; - break; - } - - if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - - current_export = PropertyInfo(); - _set_error("Expected \",\" or \")\" in the numeric range hint."); - return; - } - - _ADVANCE_AND_CONSUME_NEWLINES; - - sign = 1.0; - if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) { - sign = -1; - _ADVANCE_AND_CONSUME_NEWLINES; - } - - if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) { - - current_export = PropertyInfo(); - _set_error("Expected a number as upper bound in the numeric range hint."); - return; - } - - current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant())); - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) - break; - - if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - - current_export = PropertyInfo(); - _set_error("Expected \",\" or \")\" in the numeric range hint."); - return; - } - - _ADVANCE_AND_CONSUME_NEWLINES; - sign = 1.0; - if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) { - sign = -1; - _ADVANCE_AND_CONSUME_NEWLINES; - } - - if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) { - - current_export = PropertyInfo(); - _set_error("Expected a number as step in the numeric range hint."); - return; - } - - current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant())); - _ADVANCE_AND_CONSUME_NEWLINES; - - } break; - case Variant::STRING: { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) { - //enumeration - current_export.hint = PROPERTY_HINT_ENUM; - bool first = true; - while (true) { - - if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { - - current_export = PropertyInfo(); - _set_error("Expected a string constant in the enumeration hint."); - return; - } - - String c = tokenizer->get_token_constant(); - if (!first) - current_export.hint_string += ","; - else - first = false; - - current_export.hint_string += c.xml_escape(); - _ADVANCE_AND_CONSUME_NEWLINES; - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) - break; - - if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - current_export = PropertyInfo(); - _set_error("Expected \")\" or \",\" in the enumeration hint."); - return; - } - _ADVANCE_AND_CONSUME_NEWLINES; - } - - break; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "DIR") { - - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) - current_export.hint = PROPERTY_HINT_DIR; - else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER || !(tokenizer->get_token_identifier() == "GLOBAL")) { - _set_error("Expected \"GLOBAL\" after comma in the directory hint."); - return; - } - if (!p_class->tool) { - _set_error("Global filesystem hints may only be used in tool scripts."); - return; - } - current_export.hint = PROPERTY_HINT_GLOBAL_DIR; - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected \")\" in the hint."); - return; - } - } else { - _set_error("Expected \")\" or \",\" in the hint."); - return; - } - break; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FILE") { - - current_export.hint = PROPERTY_HINT_FILE; - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "GLOBAL") { - - if (!p_class->tool) { - _set_error("Global filesystem hints may only be used in tool scripts."); - return; - } - current_export.hint = PROPERTY_HINT_GLOBAL_FILE; - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) - break; - else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) - _ADVANCE_AND_CONSUME_NEWLINES; - else { - _set_error("Expected \")\" or \",\" in the hint."); - return; - } - } - - if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { - - if (current_export.hint == PROPERTY_HINT_GLOBAL_FILE) - _set_error("Expected string constant with filter."); - else - _set_error("Expected \"GLOBAL\" or string constant with filter."); - return; - } - current_export.hint_string = tokenizer->get_token_constant(); - _ADVANCE_AND_CONSUME_NEWLINES; - } - - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected \")\" in the hint."); - return; - } - break; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "MULTILINE") { - - current_export.hint = PROPERTY_HINT_MULTILINE_TEXT; - _ADVANCE_AND_CONSUME_NEWLINES; - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected \")\" in the hint."); - return; - } - break; - } - } break; - case Variant::COLOR: { - - if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER) { - - current_export = PropertyInfo(); - _set_error("Color type hint expects RGB or RGBA as hints."); - return; - } - - String identifier = tokenizer->get_token_identifier(); - if (identifier == "RGB") { - current_export.hint = PROPERTY_HINT_COLOR_NO_ALPHA; - } else if (identifier == "RGBA") { - //none - } else { - current_export = PropertyInfo(); - _set_error("Color type hint expects RGB or RGBA as hints."); - return; - } - _ADVANCE_AND_CONSUME_NEWLINES; - - } break; - default: { - - current_export = PropertyInfo(); - _set_error("Type \"" + Variant::get_type_name(type) + "\" can't take hints."); - return; - } break; - } - } - - } else { - - parenthesis++; - Node *subexpr = _parse_and_reduce_expression(p_class, true, true); - if (!subexpr) { - if (_recover_from_completion()) { - break; - } - return; - } - parenthesis--; - - if (subexpr->type != Node::TYPE_CONSTANT) { - current_export = PropertyInfo(); - _set_error("Expected a constant expression."); - } - - Variant constant = static_cast<ConstantNode *>(subexpr)->value; - - if (constant.get_type() == Variant::OBJECT) { - GDScriptNativeClass *native_class = Object::cast_to<GDScriptNativeClass>(constant); - - if (native_class && ClassDB::is_parent_class(native_class->get_name(), "Resource")) { - current_export.type = Variant::OBJECT; - current_export.hint = PROPERTY_HINT_RESOURCE_TYPE; - current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; - - current_export.hint_string = native_class->get_name(); - current_export.class_name = native_class->get_name(); - - } else { - current_export = PropertyInfo(); - _set_error("The export hint isn't a resource type."); - } - } else if (constant.get_type() == Variant::DICTIONARY) { - // Enumeration - bool is_flags = false; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - _ADVANCE_AND_CONSUME_NEWLINES; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") { - is_flags = true; - _ADVANCE_AND_CONSUME_NEWLINES; - } else { - current_export = PropertyInfo(); - _set_error("Expected \"FLAGS\" after comma."); - } - } - - current_export.type = Variant::INT; - current_export.hint = is_flags ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM; - current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; - Dictionary enum_values = constant; - - List<Variant> keys; - enum_values.get_key_list(&keys); - - bool first = true; - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - if (enum_values[E->get()].get_type() == Variant::INT) { - if (!first) - current_export.hint_string += ","; - else - first = false; - - current_export.hint_string += E->get().operator String().camelcase_to_underscore(true).capitalize().xml_escape(); - if (!is_flags) { - current_export.hint_string += ":"; - current_export.hint_string += enum_values[E->get()].operator String().xml_escape(); - } - } - } - } else { - current_export = PropertyInfo(); - _set_error("Expected type for export."); - return; - } - } - - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - - current_export = PropertyInfo(); - _set_error("Expected \")\" or \",\" after the export hint."); - return; - } - - tokenizer->advance(); - parenthesis--; - - if (is_arrayed) { - hint_prefix += itos(current_export.type); - if (current_export.hint) { - hint_prefix += "/" + itos(current_export.hint); - } - current_export.hint_string = hint_prefix + ":" + current_export.hint_string; - current_export.hint = PROPERTY_HINT_TYPE_STRING; - current_export.type = Variant::ARRAY; - } -#undef _ADVANCE_AND_CONSUME_NEWLINES - } - - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_ONREADY && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTER && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPET && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTESYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTERSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPETSYNC) { + return identifier; +} - current_export = PropertyInfo(); - _set_error("Expected \"var\", \"onready\", \"remote\", \"master\", \"puppet\", \"remotesync\", \"mastersync\", \"puppetsync\"."); - return; - } +GDScriptParser::LiteralNode *GDScriptParser::parse_literal() { + return static_cast<LiteralNode *>(parse_literal(nullptr, false)); +} - continue; - } break; - case GDScriptTokenizer::TK_PR_ONREADY: { +GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_previous_operand, bool p_can_assign) { + if (previous.type != GDScriptTokenizer::Token::LITERAL) { + push_error("Parser bug: parsing literal node without literal token."); + ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token."); + } - //may be fallthrough from export, ignore if so - tokenizer->advance(); - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { - _set_error("Expected \"var\"."); - return; - } + LiteralNode *literal = alloc_node<LiteralNode>(); + literal->value = previous.literal; + return literal; +} - continue; - } break; - case GDScriptTokenizer::TK_PR_REMOTE: { +GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_previous_operand, bool p_can_assign) { + if (!current_function || current_function->is_static) { + push_error(R"(Cannot use "self" outside a non-static function.)"); + } + SelfNode *self = alloc_node<SelfNode>(); + self->current_class = current_class; + return self; +} - //may be fallthrough from export, ignore if so - tokenizer->advance(); - if (current_export.type) { - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { - _set_error("Expected \"var\"."); - return; - } +GDScriptParser::ExpressionNode *GDScriptParser::parse_builtin_constant(ExpressionNode *p_previous_operand, bool p_can_assign) { + GDScriptTokenizer::Token::Type op_type = previous.type; + LiteralNode *constant = alloc_node<LiteralNode>(); - } else { - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - _set_error("Expected \"var\" or \"func\"."); - return; - } - } - rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE; - - continue; - } break; - case GDScriptTokenizer::TK_PR_MASTER: { - - //may be fallthrough from export, ignore if so - tokenizer->advance(); - if (current_export.type) { - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { - _set_error("Expected \"var\"."); - return; - } + switch (op_type) { + case GDScriptTokenizer::Token::CONST_PI: + constant->value = Math_PI; + break; + case GDScriptTokenizer::Token::CONST_TAU: + constant->value = Math_TAU; + break; + case GDScriptTokenizer::Token::CONST_INF: + constant->value = Math_INF; + break; + case GDScriptTokenizer::Token::CONST_NAN: + constant->value = Math_NAN; + break; + default: + return nullptr; // Unreachable. + } - } else { - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - _set_error("Expected \"var\" or \"func\"."); - return; - } - } + return constant; +} - rpc_mode = MultiplayerAPI::RPC_MODE_MASTER; - continue; - } break; - case GDScriptTokenizer::TK_PR_PUPPET: { - - //may be fallthrough from export, ignore if so - tokenizer->advance(); - if (current_export.type) { - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { - _set_error("Expected \"var\"."); - return; - } +GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { + GDScriptTokenizer::Token::Type op_type = previous.type; + UnaryOpNode *operation = alloc_node<UnaryOpNode>(); - } else { - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - _set_error("Expected \"var\" or \"func\"."); - return; - } - } + switch (op_type) { + case GDScriptTokenizer::Token::MINUS: + operation->operation = UnaryOpNode::OP_NEGATIVE; + operation->variant_op = Variant::OP_NEGATE; + operation->operand = parse_precedence(PREC_SIGN, false); + break; + case GDScriptTokenizer::Token::PLUS: + operation->operation = UnaryOpNode::OP_POSITIVE; + operation->variant_op = Variant::OP_POSITIVE; + operation->operand = parse_precedence(PREC_SIGN, false); + break; + case GDScriptTokenizer::Token::TILDE: + operation->operation = UnaryOpNode::OP_COMPLEMENT; + operation->variant_op = Variant::OP_BIT_NEGATE; + operation->operand = parse_precedence(PREC_BIT_NOT, false); + break; + case GDScriptTokenizer::Token::NOT: + case GDScriptTokenizer::Token::BANG: + operation->operation = UnaryOpNode::OP_LOGIC_NOT; + operation->variant_op = Variant::OP_NOT; + operation->operand = parse_precedence(PREC_LOGIC_NOT, false); + break; + default: + return nullptr; // Unreachable. + } - rpc_mode = MultiplayerAPI::RPC_MODE_PUPPET; - continue; - } break; - case GDScriptTokenizer::TK_PR_REMOTESYNC: { - - //may be fallthrough from export, ignore if so - tokenizer->advance(); - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - if (current_export.type) - _set_error("Expected \"var\"."); - else - _set_error("Expected \"var\" or \"func\"."); - return; - } + return operation; +} - rpc_mode = MultiplayerAPI::RPC_MODE_REMOTESYNC; - continue; - } break; - case GDScriptTokenizer::TK_PR_MASTERSYNC: { - - //may be fallthrough from export, ignore if so - tokenizer->advance(); - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - if (current_export.type) - _set_error("Expected \"var\"."); - else - _set_error("Expected \"var\" or \"func\"."); - return; - } +GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { + GDScriptTokenizer::Token op = previous; + BinaryOpNode *operation = alloc_node<BinaryOpNode>(); - rpc_mode = MultiplayerAPI::RPC_MODE_MASTERSYNC; - continue; - } break; - case GDScriptTokenizer::TK_PR_PUPPETSYNC: { - - //may be fallthrough from export, ignore if so - tokenizer->advance(); - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - if (current_export.type) - _set_error("Expected \"var\"."); - else - _set_error("Expected \"var\" or \"func\"."); - return; - } + Precedence precedence = (Precedence)(get_rule(op.type)->precedence + 1); + operation->left_operand = p_previous_operand; + operation->right_operand = parse_precedence(precedence, false); - rpc_mode = MultiplayerAPI::RPC_MODE_PUPPETSYNC; - continue; - } break; - case GDScriptTokenizer::TK_PR_VAR: { - // variable declaration and (eventual) initialization + if (operation->right_operand == nullptr) { + push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name())); + } - ClassNode::Member member; + // TODO: Also for unary, ternary, and assignment. + switch (op.type) { + case GDScriptTokenizer::Token::PLUS: + operation->operation = BinaryOpNode::OP_ADDITION; + operation->variant_op = Variant::OP_ADD; + break; + case GDScriptTokenizer::Token::MINUS: + operation->operation = BinaryOpNode::OP_SUBTRACTION; + operation->variant_op = Variant::OP_SUBTRACT; + break; + case GDScriptTokenizer::Token::STAR: + operation->operation = BinaryOpNode::OP_MULTIPLICATION; + operation->variant_op = Variant::OP_MULTIPLY; + break; + case GDScriptTokenizer::Token::SLASH: + operation->operation = BinaryOpNode::OP_DIVISION; + operation->variant_op = Variant::OP_DIVIDE; + break; + case GDScriptTokenizer::Token::PERCENT: + operation->operation = BinaryOpNode::OP_MODULO; + operation->variant_op = Variant::OP_MODULE; + break; + case GDScriptTokenizer::Token::LESS_LESS: + operation->operation = BinaryOpNode::OP_BIT_LEFT_SHIFT; + operation->variant_op = Variant::OP_SHIFT_LEFT; + break; + case GDScriptTokenizer::Token::GREATER_GREATER: + operation->operation = BinaryOpNode::OP_BIT_RIGHT_SHIFT; + operation->variant_op = Variant::OP_SHIFT_RIGHT; + break; + case GDScriptTokenizer::Token::AMPERSAND: + operation->operation = BinaryOpNode::OP_BIT_AND; + operation->variant_op = Variant::OP_BIT_AND; + break; + case GDScriptTokenizer::Token::PIPE: + operation->operation = BinaryOpNode::OP_BIT_OR; + operation->variant_op = Variant::OP_BIT_OR; + break; + case GDScriptTokenizer::Token::CARET: + operation->operation = BinaryOpNode::OP_BIT_XOR; + operation->variant_op = Variant::OP_BIT_XOR; + break; + case GDScriptTokenizer::Token::AND: + case GDScriptTokenizer::Token::AMPERSAND_AMPERSAND: + operation->operation = BinaryOpNode::OP_LOGIC_AND; + operation->variant_op = Variant::OP_AND; + break; + case GDScriptTokenizer::Token::OR: + case GDScriptTokenizer::Token::PIPE_PIPE: + operation->operation = BinaryOpNode::OP_LOGIC_OR; + operation->variant_op = Variant::OP_OR; + break; + case GDScriptTokenizer::Token::IS: + operation->operation = BinaryOpNode::OP_TYPE_TEST; + break; + case GDScriptTokenizer::Token::IN: + operation->operation = BinaryOpNode::OP_CONTENT_TEST; + operation->variant_op = Variant::OP_IN; + break; + case GDScriptTokenizer::Token::EQUAL_EQUAL: + operation->operation = BinaryOpNode::OP_COMP_EQUAL; + operation->variant_op = Variant::OP_EQUAL; + break; + case GDScriptTokenizer::Token::BANG_EQUAL: + operation->operation = BinaryOpNode::OP_COMP_NOT_EQUAL; + operation->variant_op = Variant::OP_NOT_EQUAL; + break; + case GDScriptTokenizer::Token::LESS: + operation->operation = BinaryOpNode::OP_COMP_LESS; + operation->variant_op = Variant::OP_LESS; + break; + case GDScriptTokenizer::Token::LESS_EQUAL: + operation->operation = BinaryOpNode::OP_COMP_LESS_EQUAL; + operation->variant_op = Variant::OP_LESS_EQUAL; + break; + case GDScriptTokenizer::Token::GREATER: + operation->operation = BinaryOpNode::OP_COMP_GREATER; + operation->variant_op = Variant::OP_GREATER; + break; + case GDScriptTokenizer::Token::GREATER_EQUAL: + operation->operation = BinaryOpNode::OP_COMP_GREATER_EQUAL; + operation->variant_op = Variant::OP_GREATER_EQUAL; + break; + default: + return nullptr; // Unreachable. + } - bool autoexport = tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_EXPORT; - if (current_export.type != Variant::NIL) { - member._export = current_export; - current_export = PropertyInfo(); - } + return operation; +} - bool onready = tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_ONREADY; +GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { + // Only one ternary operation exists, so no abstraction here. + TernaryOpNode *operation = alloc_node<TernaryOpNode>(); + operation->true_expr = p_previous_operand; - tokenizer->advance(); - if (!tokenizer->is_token_literal(0, true)) { + operation->condition = parse_precedence(PREC_TERNARY, false); - _set_error("Expected an identifier for the member variable name."); - return; - } + if (operation->condition == nullptr) { + push_error(R"(Expected expression as ternary condition after "if".)"); + } - member.identifier = tokenizer->get_token_literal(); - member.expression = nullptr; - member._export.name = member.identifier; - member.line = tokenizer->get_token_line(); - member.usages = 0; - member.rpc_mode = rpc_mode; - - if (current_class->constant_expressions.has(member.identifier)) { - _set_error("A constant named \"" + String(member.identifier) + "\" already exists in this class (at line: " + - itos(current_class->constant_expressions[member.identifier].expression->line) + ")."); - return; - } + consume(GDScriptTokenizer::Token::ELSE, R"(Expected "else" after ternary operator condition.)"); - for (int i = 0; i < current_class->variables.size(); i++) { - if (current_class->variables[i].identifier == member.identifier) { - _set_error("Variable \"" + String(member.identifier) + "\" already exists in this class (at line: " + - itos(current_class->variables[i].line) + ")."); - return; - } - } + operation->false_expr = parse_precedence(PREC_TERNARY, false); - for (int i = 0; i < current_class->subclasses.size(); i++) { - if (current_class->subclasses[i]->name == member.identifier) { - _set_error("A class named \"" + String(member.identifier) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); - return; - } - } -#ifdef DEBUG_ENABLED - for (int i = 0; i < current_class->functions.size(); i++) { - if (current_class->functions[i]->name == member.identifier) { - _add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier); - break; - } - } - for (int i = 0; i < current_class->static_functions.size(); i++) { - if (current_class->static_functions[i]->name == member.identifier) { - _add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier); - break; - } - } -#endif // DEBUG_ENABLED - tokenizer->advance(); + return operation; +} - rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; +GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode *p_previous_operand, bool p_can_assign) { + if (!p_can_assign) { + push_error("Assignment is not allowed inside an expression."); + return parse_expression(false); // Return the following expression. + } - if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { - if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { - member.data_type = DataType(); #ifdef DEBUG_ENABLED - member.data_type.infer_type = true; -#endif - tokenizer->advance(); - } else if (!_parse_type(member.data_type)) { - _set_error("Expected a type for the class variable."); - return; - } - } - - if (autoexport && member.data_type.has_type) { - if (member.data_type.kind == DataType::BUILTIN) { - member._export.type = member.data_type.builtin_type; - } else if (member.data_type.kind == DataType::NATIVE) { - if (ClassDB::is_parent_class(member.data_type.native_type, "Resource")) { - member._export.type = Variant::OBJECT; - member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; - member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; - member._export.hint_string = member.data_type.native_type; - member._export.class_name = member.data_type.native_type; - } else { - _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); - return; - } - - } else { - _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); - return; - } - } - -#ifdef TOOLS_ENABLED - Callable::CallError ce; - member.default_value = Variant::construct(member._export.type, nullptr, 0, ce); + VariableNode *source_variable = nullptr; #endif - if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { - + switch (p_previous_operand->type) { + case Node::IDENTIFIER: { #ifdef DEBUG_ENABLED - int line = tokenizer->get_token_line(); + // Get source to store assignment count. + // Also remove one usage since assignment isn't usage. + IdentifierNode *id = static_cast<IdentifierNode *>(p_previous_operand); + switch (id->source) { + case IdentifierNode::LOCAL_VARIABLE: + + source_variable = id->variable_source; + id->variable_source->usages--; + break; + case IdentifierNode::LOCAL_CONSTANT: + id->constant_source->usages--; + break; + case IdentifierNode::FUNCTION_PARAMETER: + id->parameter_source->usages--; + break; + case IdentifierNode::LOCAL_ITERATOR: + case IdentifierNode::LOCAL_BIND: + id->bind_source->usages--; + break; + default: + break; + } #endif - tokenizer->advance(); - - Node *subexpr = _parse_and_reduce_expression(p_class, false, autoexport || member._export.type != Variant::NIL); - if (!subexpr) { - if (_recover_from_completion()) { - break; - } - return; - } - - //discourage common error - if (!onready && subexpr->type == Node::TYPE_OPERATOR) { - - OperatorNode *op = static_cast<OperatorNode *>(subexpr); - if (op->op == OperatorNode::OP_CALL && op->arguments[0]->type == Node::TYPE_SELF && op->arguments[1]->type == Node::TYPE_IDENTIFIER) { - IdentifierNode *id = static_cast<IdentifierNode *>(op->arguments[1]); - if (id->name == "get_node") { - - _set_error("Use \"onready var " + String(member.identifier) + " = get_node(...)\" instead."); - return; - } - } - } - - member.expression = subexpr; - - if (autoexport && !member.data_type.has_type) { - - if (subexpr->type != Node::TYPE_CONSTANT) { - - _set_error("Type-less export needs a constant expression assigned to infer type."); - return; - } - - ConstantNode *cn = static_cast<ConstantNode *>(subexpr); - if (cn->value.get_type() == Variant::NIL) { + } break; + case Node::SUBSCRIPT: + // Okay. + break; + default: + push_error(R"(Only identifier, attribute access, and subscription access can be used as assignment target.)"); + return parse_expression(false); // Return the following expression. + } - _set_error("Can't accept a null constant expression for inferring export type."); - return; - } - member._export.type = cn->value.get_type(); - member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; - if (cn->value.get_type() == Variant::OBJECT) { - Object *obj = cn->value; - Resource *res = Object::cast_to<Resource>(obj); - if (res == nullptr) { - _set_error("The exported constant isn't a type or resource."); - return; - } - member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; - member._export.hint_string = res->get_class(); - } - } -#ifdef TOOLS_ENABLED - if (subexpr->type == Node::TYPE_CONSTANT && (member._export.type != Variant::NIL || member.data_type.has_type)) { - - ConstantNode *cn = static_cast<ConstantNode *>(subexpr); - if (cn->value.get_type() != Variant::NIL) { - if (member._export.type != Variant::NIL && cn->value.get_type() != member._export.type) { - if (Variant::can_convert(cn->value.get_type(), member._export.type)) { - Callable::CallError err; - const Variant *args = &cn->value; - cn->value = Variant::construct(member._export.type, &args, 1, err); - } else { - _set_error("Can't convert the provided value to the export type."); - return; - } - } - member.default_value = cn->value; - } - } + AssignmentNode *assignment = alloc_node<AssignmentNode>(); + make_completion_context(COMPLETION_ASSIGN, assignment); +#ifdef DEBUG_ENABLED + bool has_operator = true; #endif - - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = member.identifier; - - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_INIT_ASSIGN; - op->arguments.push_back(id); - op->arguments.push_back(subexpr); - + switch (previous.type) { + case GDScriptTokenizer::Token::EQUAL: + assignment->operation = AssignmentNode::OP_NONE; + assignment->variant_op = Variant::OP_MAX; #ifdef DEBUG_ENABLED - NewLineNode *nl2 = alloc_node<NewLineNode>(); - nl2->line = line; - if (onready) - p_class->ready->statements.push_back(nl2); - else - p_class->initializer->statements.push_back(nl2); + has_operator = false; #endif - if (onready) - p_class->ready->statements.push_back(op); - else - p_class->initializer->statements.push_back(op); - - member.initial_assignment = op; - - } else { - - if (autoexport && !member.data_type.has_type) { - _set_error("Type-less export needs a constant expression assigned to infer type."); - return; - } - - Node *expr; - - if (member.data_type.has_type) { - expr = _get_default_value_for_type(member.data_type); - } else { - DataType exported_type; - exported_type.has_type = true; - exported_type.kind = DataType::BUILTIN; - exported_type.builtin_type = member._export.type; - expr = _get_default_value_for_type(exported_type); - } - - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = member.identifier; - - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_INIT_ASSIGN; - op->arguments.push_back(id); - op->arguments.push_back(expr); - - p_class->initializer->statements.push_back(op); - - member.initial_assignment = op; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_SETGET) { - - tokenizer->advance(); - - if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { - //just comma means using only getter - if (!tokenizer->is_token_literal()) { - _set_error("Expected an identifier for the setter function after \"setget\"."); - } - - member.setter = tokenizer->get_token_literal(); - - tokenizer->advance(); - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - //there is a getter - tokenizer->advance(); - - if (!tokenizer->is_token_literal()) { - _set_error("Expected an identifier for the getter function after \",\"."); - } - - member.getter = tokenizer->get_token_literal(); - tokenizer->advance(); - } - } - - p_class->variables.push_back(member); - - if (!_end_statement()) { - _set_error("Expected end of statement (\"continue\")."); - return; - } - } break; - case GDScriptTokenizer::TK_PR_CONST: { - // constant declaration and initialization - - ClassNode::Constant constant; - - tokenizer->advance(); - if (!tokenizer->is_token_literal(0, true)) { - - _set_error("Expected an identifier for the constant."); - return; - } - - StringName const_id = tokenizer->get_token_literal(); - int line = tokenizer->get_token_line(); - - if (current_class->constant_expressions.has(const_id)) { - _set_error("Constant \"" + String(const_id) + "\" already exists in this class (at line " + - itos(current_class->constant_expressions[const_id].expression->line) + ")."); - return; - } - - for (int i = 0; i < current_class->variables.size(); i++) { - if (current_class->variables[i].identifier == const_id) { - _set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " + - itos(current_class->variables[i].line) + ")."); - return; - } - } - - for (int i = 0; i < current_class->subclasses.size(); i++) { - if (current_class->subclasses[i]->name == const_id) { - _set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); - return; - } - } - - tokenizer->advance(); + 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. + } + assignment->assignee = p_previous_operand; + assignment->assigned_value = parse_expression(false); - if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { - if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { - constant.type = DataType(); #ifdef DEBUG_ENABLED - constant.type.infer_type = true; + if (has_operator && source_variable != nullptr && source_variable->assignments == 0) { + push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + } #endif - tokenizer->advance(); - } else if (!_parse_type(constant.type)) { - _set_error("Expected a type for the class constant."); - return; - } - } - - if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) { - _set_error("Constants must be assigned immediately."); - return; - } - - tokenizer->advance(); - - Node *subexpr = _parse_and_reduce_expression(p_class, true, true); - if (!subexpr) { - if (_recover_from_completion()) { - break; - } - return; - } - - if (subexpr->type != Node::TYPE_CONSTANT) { - _set_error("Expected a constant expression.", line); - return; - } - subexpr->line = line; - constant.expression = subexpr; - - p_class->constant_expressions.insert(const_id, constant); - - if (!_end_statement()) { - _set_error("Expected end of statement (constant).", line); - return; - } - - } break; - case GDScriptTokenizer::TK_PR_ENUM: { - //multiple constant declarations.. - int last_assign = -1; // Incremented by 1 right before the assignment. - String enum_name; - Dictionary enum_dict; - - tokenizer->advance(); - if (tokenizer->is_token_literal(0, true)) { - enum_name = tokenizer->get_token_literal(); - - if (current_class->constant_expressions.has(enum_name)) { - _set_error("A constant named \"" + String(enum_name) + "\" already exists in this class (at line " + - itos(current_class->constant_expressions[enum_name].expression->line) + ")."); - return; - } - - for (int i = 0; i < current_class->variables.size(); i++) { - if (current_class->variables[i].identifier == enum_name) { - _set_error("A variable named \"" + String(enum_name) + "\" already exists in this class (at line " + - itos(current_class->variables[i].line) + ")."); - return; - } - } - - for (int i = 0; i < current_class->subclasses.size(); i++) { - if (current_class->subclasses[i]->name == enum_name) { - _set_error("A class named \"" + String(enum_name) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); - return; - } - } - - tokenizer->advance(); - } - if (tokenizer->get_token() != GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) { - _set_error("Expected \"{\" in the enum declaration."); - return; - } - tokenizer->advance(); - - while (true) { - if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { - - tokenizer->advance(); // Ignore newlines - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { - - tokenizer->advance(); - break; // End of enum - } else if (!tokenizer->is_token_literal(0, true)) { - - if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { - _set_error("Unexpected end of file."); - } else { - _set_error(String("Unexpected ") + GDScriptTokenizer::get_token_name(tokenizer->get_token()) + ", expected an identifier."); - } - - return; - } else { // tokenizer->is_token_literal(0, true) - StringName const_id = tokenizer->get_token_literal(); - - tokenizer->advance(); - - ConstantNode *enum_value_expr; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { - tokenizer->advance(); - - Node *subexpr = _parse_and_reduce_expression(p_class, true, true); - if (!subexpr) { - if (_recover_from_completion()) { - break; - } - return; - } - - if (subexpr->type != Node::TYPE_CONSTANT) { - _set_error("Expected a constant expression."); - return; - } - - enum_value_expr = static_cast<ConstantNode *>(subexpr); - - if (enum_value_expr->value.get_type() != Variant::INT) { - _set_error("Expected an integer value for \"enum\"."); - return; - } - - last_assign = enum_value_expr->value; - - } else { - last_assign = last_assign + 1; - enum_value_expr = alloc_node<ConstantNode>(); - enum_value_expr->value = last_assign; - enum_value_expr->datatype = _type_from_variant(enum_value_expr->value); - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - tokenizer->advance(); - } else if (tokenizer->is_token_literal(0, true)) { - _set_error("Unexpected identifier."); - return; - } - - if (enum_name != "") { - enum_dict[const_id] = enum_value_expr->value; - } else { - if (current_class->constant_expressions.has(const_id)) { - _set_error("A constant named \"" + String(const_id) + "\" already exists in this class (at line " + - itos(current_class->constant_expressions[const_id].expression->line) + ")."); - return; - } - - for (int i = 0; i < current_class->variables.size(); i++) { - if (current_class->variables[i].identifier == const_id) { - _set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " + - itos(current_class->variables[i].line) + ")."); - return; - } - } - - for (int i = 0; i < current_class->subclasses.size(); i++) { - if (current_class->subclasses[i]->name == const_id) { - _set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); - return; - } - } - - ClassNode::Constant constant; - constant.type.has_type = true; - constant.type.kind = DataType::BUILTIN; - constant.type.builtin_type = Variant::INT; - constant.expression = enum_value_expr; - p_class->constant_expressions.insert(const_id, constant); - } - } - } - - if (enum_name != "") { - ClassNode::Constant enum_constant; - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = enum_dict; - cn->datatype = _type_from_variant(cn->value); - - enum_constant.expression = cn; - enum_constant.type = cn->datatype; - p_class->constant_expressions.insert(enum_name, enum_constant); - } - - if (!_end_statement()) { - _set_error("Expected end of statement (\"enum\")."); - return; - } - - } break; - - case GDScriptTokenizer::TK_CONSTANT: { - if (tokenizer->get_token_constant().get_type() == Variant::STRING) { - tokenizer->advance(); - // Ignore - } else { - _set_error(String() + "Unexpected constant of type: " + Variant::get_type_name(tokenizer->get_token_constant().get_type())); - return; - } - } break; - - case GDScriptTokenizer::TK_CF_PASS: { - tokenizer->advance(); - } break; - - default: { - - _set_error(String() + "Unexpected token: " + tokenizer->get_token_name(tokenizer->get_token()) + ":" + tokenizer->get_token_identifier()); - return; - - } break; - } - } + return assignment; } -void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive) { - - if (p_class->base_type.has_type) { - // Already determined - } else if (p_class->extends_used) { - //do inheritance - String path = p_class->extends_file; +GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) { + AwaitNode *await = alloc_node<AwaitNode>(); + await->to_await = parse_precedence(PREC_AWAIT, false); - Ref<GDScript> script; - StringName native; - ClassNode *base_class = nullptr; + current_function->is_coroutine = true; - if (path != "") { - //path (and optionally subclasses) - - if (path.is_rel_path()) { + return await; +} - String base = base_path; +GDScriptParser::ExpressionNode *GDScriptParser::parse_array(ExpressionNode *p_previous_operand, bool p_can_assign) { + ArrayNode *array = alloc_node<ArrayNode>(); - if (base == "" || base.is_rel_path()) { - _set_error("Couldn't resolve relative path for the parent class: " + path, p_class->line); - return; - } - path = base.plus_file(path).simplify_path(); - } - script = ResourceLoader::load(path); - if (script.is_null()) { - _set_error("Couldn't load the base class: " + path, p_class->line); - return; + if (!check(GDScriptTokenizer::Token::BRACKET_CLOSE)) { + do { + if (check(GDScriptTokenizer::Token::BRACKET_CLOSE)) { + // Allow for trailing comma. + break; } - if (!script->is_valid()) { - _set_error("Script isn't fully loaded (cyclic preload?): " + path, p_class->line); - return; + ExpressionNode *element = parse_expression(false); + if (element == nullptr) { + push_error(R"(Expected expression as array element.)"); + } else { + array->elements.push_back(element); } + } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); + } + pop_multiline(); + consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after array elements.)"); - if (p_class->extends_class.size()) { - - for (int i = 0; i < p_class->extends_class.size(); i++) { - - String sub = p_class->extends_class[i]; - if (script->get_subclasses().has(sub)) { + return array; +} - Ref<Script> subclass = script->get_subclasses()[sub]; //avoid reference from disappearing - script = subclass; - } else { +GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode *p_previous_operand, bool p_can_assign) { + DictionaryNode *dictionary = alloc_node<DictionaryNode>(); - _set_error("Couldn't find the subclass: " + sub, p_class->line); - return; - } - } + bool decided_style = false; + if (!check(GDScriptTokenizer::Token::BRACE_CLOSE)) { + do { + if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) { + // Allow for trailing comma. + break; } - } else { + // Key. + ExpressionNode *key = parse_expression(false, true); // Stop on "=" so we can check for Lua table style. - if (p_class->extends_class.size() == 0) { - _set_error("Parser bug: undecidable inheritance.", p_class->line); - ERR_FAIL(); - } - //look around for the subclasses - - int extend_iter = 1; - String base = p_class->extends_class[0]; - ClassNode *p = p_class->owner; - Ref<GDScript> base_script; - - if (ScriptServer::is_global_class(base)) { - base_script = ResourceLoader::load(ScriptServer::get_global_class_path(base)); - if (!base_script.is_valid()) { - _set_error("The class \"" + base + "\" couldn't be fully loaded (script error or cyclic dependency).", p_class->line); - return; - } - p = nullptr; - } else { - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) { - continue; - } - String name = s.get_slice("/", 1); - if (name == base) { - String singleton_path = ProjectSettings::get_singleton()->get(s); - if (singleton_path.begins_with("*")) { - singleton_path = singleton_path.right(1); - } - if (!singleton_path.begins_with("res://")) { - singleton_path = "res://" + singleton_path; - } - base_script = ResourceLoader::load(singleton_path); - if (!base_script.is_valid()) { - _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line); - return; - } - p = nullptr; - } - } + if (key == nullptr) { + push_error(R"(Expected expression as dictionary key.)"); } - while (p) { - - bool found = false; - - for (int i = 0; i < p->subclasses.size(); i++) { - if (p->subclasses[i]->name == base) { - ClassNode *test = p->subclasses[i]; - while (test) { - if (test == p_class) { - _set_error("Cyclic inheritance.", test->line); - return; - } - if (test->base_type.kind == DataType::CLASS) { - test = test->base_type.class_type; - } else { - break; - } - } - found = true; - if (extend_iter < p_class->extends_class.size()) { - // Keep looking at current classes if possible - base = p_class->extends_class[extend_iter++]; - p = p->subclasses[i]; - } else { - base_class = p->subclasses[i]; - } + if (!decided_style) { + switch (current.type) { + case GDScriptTokenizer::Token::COLON: + dictionary->style = DictionaryNode::PYTHON_DICT; + break; + case GDScriptTokenizer::Token::EQUAL: + dictionary->style = DictionaryNode::LUA_TABLE; + break; + default: + push_error(R"(Expected ":" or "=" after dictionary key.)"); break; - } } + decided_style = true; + } - if (base_class) break; - if (found) continue; - - if (p->constant_expressions.has(base)) { - if (p->constant_expressions[base].expression->type != Node::TYPE_CONSTANT) { - _set_error("Couldn't resolve the constant \"" + base + "\".", p_class->line); - return; + switch (dictionary->style) { + case DictionaryNode::LUA_TABLE: + if (key != nullptr && key->type != Node::IDENTIFIER) { + push_error("Expected identifier as dictionary key."); } - const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[base].expression); - base_script = cn->value; - if (base_script.is_null()) { - _set_error("Constant isn't a class: " + base, p_class->line); - return; + if (!match(GDScriptTokenizer::Token::EQUAL)) { + if (match(GDScriptTokenizer::Token::COLON)) { + push_error(R"(Expected "=" after dictionary key. Mixing dictionary styles is not allowed.)"); + advance(); // Consume wrong separator anyway. + } else { + push_error(R"(Expected "=" after dictionary key.)"); + } } break; - } - - p = p->owner; - } - - if (base_script.is_valid()) { - - String ident = base; - Ref<GDScript> find_subclass = base_script; - - for (int i = extend_iter; i < p_class->extends_class.size(); i++) { - - String subclass = p_class->extends_class[i]; - - ident += ("." + subclass); - - if (find_subclass->get_subclasses().has(subclass)) { - - find_subclass = find_subclass->get_subclasses()[subclass]; - } else if (find_subclass->get_constants().has(subclass)) { - - Ref<GDScript> new_base_class = find_subclass->get_constants()[subclass]; - if (new_base_class.is_null()) { - _set_error("Constant isn't a class: " + ident, p_class->line); - return; + case DictionaryNode::PYTHON_DICT: + if (!match(GDScriptTokenizer::Token::COLON)) { + if (match(GDScriptTokenizer::Token::EQUAL)) { + push_error(R"(Expected ":" after dictionary key. Mixing dictionary styles is not allowed.)"); + advance(); // Consume wrong separator anyway. + } else { + push_error(R"(Expected ":" after dictionary key.)"); } - find_subclass = new_base_class; - } else { - - _set_error("Couldn't find the subclass: " + ident, p_class->line); - return; } - } - - script = find_subclass; - - } else if (!base_class) { - - if (p_class->extends_class.size() > 1) { - - _set_error("Invalid inheritance (unknown class + subclasses).", p_class->line); - return; - } - //if not found, try engine classes - if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { - - _set_error("Unknown class: \"" + base + "\"", p_class->line); - return; - } - - native = base; + break; } - } - - if (base_class) { - p_class->base_type.has_type = true; - p_class->base_type.kind = DataType::CLASS; - p_class->base_type.class_type = base_class; - } else if (script.is_valid()) { - p_class->base_type.has_type = true; - p_class->base_type.kind = DataType::GDSCRIPT; - p_class->base_type.script_type = script; - p_class->base_type.native_type = script->get_instance_base_type(); - } else if (native != StringName()) { - p_class->base_type.has_type = true; - p_class->base_type.kind = DataType::NATIVE; - p_class->base_type.native_type = native; - } else { - _set_error("Couldn't determine inheritance.", p_class->line); - return; - } - - } else { - // without extends, implicitly extend Reference - p_class->base_type.has_type = true; - p_class->base_type.kind = DataType::NATIVE; - p_class->base_type.native_type = "Reference"; - } - if (p_recursive) { - // Recursively determine subclasses - for (int i = 0; i < p_class->subclasses.size(); i++) { - _determine_inheritance(p_class->subclasses[i], p_recursive); - } - } -} - -String GDScriptParser::DataType::to_string() const { - if (!has_type) return "var"; - switch (kind) { - case BUILTIN: { - if (builtin_type == Variant::NIL) return "null"; - return Variant::get_type_name(builtin_type); - } break; - case NATIVE: { - if (is_meta_type) { - return "GDScriptNativeClass"; + // Value. + ExpressionNode *value = parse_expression(false); + if (value == nullptr) { + push_error(R"(Expected expression as dictionary value.)"); } - return native_type.operator String(); - } break; - case GDSCRIPT: { - Ref<GDScript> gds = script_type; - const String &gds_class = gds->get_script_class_name(); - if (!gds_class.empty()) { - return gds_class; - } - [[fallthrough]]; - } - case SCRIPT: { - if (is_meta_type) { - return script_type->get_class_name().operator String(); + if (key != nullptr && value != nullptr) { + dictionary->elements.push_back({ key, value }); } - String name = script_type->get_name(); - if (name != String()) { - return name; - } - name = script_type->get_path().get_file(); - if (name != String()) { - return name; - } - return native_type.operator String(); - } break; - case CLASS: { - ERR_FAIL_COND_V(!class_type, String()); - if (is_meta_type) { - return "GDScript"; - } - if (class_type->name == StringName()) { - return "self"; - } - return class_type->name.operator String(); - } break; - case UNRESOLVED: { - } break; + } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); } + pop_multiline(); + consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" after dictionary elements.)"); - return "Unresolved"; + return dictionary; } -bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) { - tokenizer->advance(); - r_type.has_type = true; - - bool finished = false; - bool can_index = false; - String full_name; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { - completion_cursor = StringName(); - completion_type = COMPLETION_TYPE_HINT; - completion_class = current_class; - completion_function = current_function; - completion_line = tokenizer->get_token_line(); - completion_argument = 0; - completion_block = current_block; - completion_found = true; - completion_ident_is_call = p_can_be_void; - tokenizer->advance(); +GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p_previous_operand, bool p_can_assign) { + ExpressionNode *grouped = parse_expression(false); + pop_multiline(); + if (grouped == nullptr) { + push_error(R"(Expected grouping expression.)"); + } else { + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after grouping expression.)*"); } + return grouped; +} - switch (tokenizer->get_token()) { - case GDScriptTokenizer::TK_PR_VOID: { - if (!p_can_be_void) { - return false; - } - r_type.kind = DataType::BUILTIN; - r_type.builtin_type = Variant::NIL; - } break; - case GDScriptTokenizer::TK_BUILT_IN_TYPE: { - r_type.builtin_type = tokenizer->get_token_type(); - if (tokenizer->get_token_type() == Variant::OBJECT) { - r_type.kind = DataType::NATIVE; - r_type.native_type = "Object"; - } else { - r_type.kind = DataType::BUILTIN; - } - } break; - case GDScriptTokenizer::TK_IDENTIFIER: { - r_type.native_type = tokenizer->get_token_identifier(); - if (ClassDB::class_exists(r_type.native_type) || ClassDB::class_exists("_" + r_type.native_type.operator String())) { - r_type.kind = DataType::NATIVE; - } else { - r_type.kind = DataType::UNRESOLVED; - can_index = true; - full_name = r_type.native_type; +GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign) { + SubscriptNode *attribute = alloc_node<SubscriptNode>(); + + if (for_completion) { + bool is_builtin = false; + if (p_previous_operand->type == Node::IDENTIFIER) { + const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand); + Variant::Type builtin_type = get_builtin_type(id->name); + if (builtin_type < Variant::VARIANT_MAX) { + make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT, builtin_type, true); + is_builtin = true; } - } break; - default: { - return false; + } + if (!is_builtin) { + make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true); } } - tokenizer->advance(); + attribute->is_attribute = true; + attribute->base = p_previous_operand; - if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { - completion_cursor = r_type.native_type; - completion_type = COMPLETION_TYPE_HINT; - completion_class = current_class; - completion_function = current_function; - completion_line = tokenizer->get_token_line(); - completion_argument = 0; - completion_block = current_block; - completion_found = true; - completion_ident_is_call = p_can_be_void; - tokenizer->advance(); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) { + return attribute; } + attribute->attribute = parse_identifier(); - if (can_index) { - while (!finished) { - switch (tokenizer->get_token()) { - case GDScriptTokenizer::TK_PERIOD: { - if (!can_index) { - _set_error("Unexpected \".\"."); - return false; - } - can_index = false; - tokenizer->advance(); - } break; - case GDScriptTokenizer::TK_IDENTIFIER: { - if (can_index) { - _set_error("Unexpected identifier."); - return false; - } + return attribute; +} - StringName id; - bool has_completion = _get_completable_identifier(COMPLETION_TYPE_HINT_INDEX, id); - if (id == StringName()) { - id = "@temp"; - } +GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign) { + SubscriptNode *subscript = alloc_node<SubscriptNode>(); - full_name += "." + id.operator String(); - can_index = true; - if (has_completion) { - completion_cursor = full_name; - } - } break; - default: { - finished = true; - } break; - } - } + make_completion_context(COMPLETION_SUBSCRIPT, subscript); - if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PERIOD) { - _set_error("Expected a subclass identifier."); - return false; - } + subscript->base = p_previous_operand; + subscript->index = parse_expression(false); - r_type.native_type = full_name; - } + pop_multiline(); + consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)"); - return true; + return subscript; } -GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, int p_line) { - if (!p_source.has_type) return p_source; - if (p_source.kind != DataType::UNRESOLVED) return p_source; +GDScriptParser::ExpressionNode *GDScriptParser::parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign) { + CastNode *cast = alloc_node<CastNode>(); - Vector<String> full_name = p_source.native_type.operator String().split(".", false); - int name_part = 0; + cast->operand = p_previous_operand; + cast->cast_type = parse_type(); - DataType result; - result.has_type = true; + if (cast->cast_type == nullptr) { + push_error(R"(Expected type specifier after "as".)"); + return p_previous_operand; + } - while (name_part < full_name.size()) { + return cast; +} - bool found = false; - StringName id = full_name[name_part]; - DataType base_type = result; +GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_previous_operand, bool p_can_assign) { + CallNode *call = alloc_node<CallNode>(); - ClassNode *p = nullptr; - if (name_part == 0) { - if (ScriptServer::is_global_class(id)) { - String script_path = ScriptServer::get_global_class_path(id); - if (script_path == self_path) { - result.kind = DataType::CLASS; - result.class_type = static_cast<ClassNode *>(head); - } else { - Ref<Script> script = ResourceLoader::load(script_path); - Ref<GDScript> gds = script; - if (gds.is_valid()) { - if (!gds->is_valid()) { - _set_error("The class \"" + id + "\" couldn't be fully loaded (script error or cyclic dependency).", p_line); - return DataType(); - } - result.kind = DataType::GDSCRIPT; - result.script_type = gds; - } else if (script.is_valid()) { - result.kind = DataType::SCRIPT; - result.script_type = script; - } else { - _set_error("The class \"" + id + "\" was found in global scope, but its script couldn't be loaded.", p_line); - return DataType(); - } - } - name_part++; - continue; - } - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - String singleton_path; - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) { - continue; - } - String name = s.get_slice("/", 1); - if (name == id) { - singleton_path = ProjectSettings::get_singleton()->get(s); - if (singleton_path.begins_with("*")) { - singleton_path = singleton_path.right(1); - } - if (!singleton_path.begins_with("res://")) { - singleton_path = "res://" + singleton_path; - } - break; - } + if (previous.type == GDScriptTokenizer::Token::SUPER) { + // Super call. + call->is_super = true; + push_multiline(true); + if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + // Implicit call to the parent method of the same name. + if (current_function == nullptr) { + push_error(R"(Cannot use implicit "super" call outside of a function.)"); + pop_multiline(); + return nullptr; } - if (!singleton_path.empty()) { - Ref<Script> script = ResourceLoader::load(singleton_path); - Ref<GDScript> gds = script; - if (gds.is_valid()) { - if (!gds->is_valid()) { - _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line); - return DataType(); - } - result.kind = DataType::GDSCRIPT; - result.script_type = gds; - } else if (script.is_valid()) { - result.kind = DataType::SCRIPT; - result.script_type = script; - } else { - _set_error("Couldn't fully load singleton script '" + id + "' (possible cyclic reference or parse error).", p_line); - return DataType(); - } - name_part++; - continue; + call->function_name = current_function->identifier->name; + } else { + consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)"); + make_completion_context(COMPLETION_SUPER_METHOD, call, true); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) { + pop_multiline(); + return nullptr; } - - p = current_class; - } else if (base_type.kind == DataType::CLASS) { - p = base_type.class_type; + IdentifierNode *identifier = parse_identifier(); + call->callee = identifier; + call->function_name = identifier->name; + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after function name.)"); } - while (p) { - if (p->constant_expressions.has(id)) { - if (p->constant_expressions[id].expression->type != Node::TYPE_CONSTANT) { - _set_error("Parser bug: unresolved constant.", p_line); - ERR_FAIL_V(result); - } - const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[id].expression); - Ref<GDScript> gds = cn->value; - if (gds.is_valid()) { - result.kind = DataType::GDSCRIPT; - result.script_type = gds; - found = true; - } else { - Ref<Script> scr = cn->value; - if (scr.is_valid()) { - result.kind = DataType::SCRIPT; - result.script_type = scr; - found = true; - } - } - break; - } - - // Inner classes - ClassNode *outer_class = p; - while (outer_class) { - if (outer_class->name == id) { - found = true; - result.kind = DataType::CLASS; - result.class_type = outer_class; - break; - } - for (int i = 0; i < outer_class->subclasses.size(); i++) { - if (outer_class->subclasses[i] == p) { - continue; - } - if (outer_class->subclasses[i]->name == id) { - found = true; - result.kind = DataType::CLASS; - result.class_type = outer_class->subclasses[i]; - break; - } - } - if (found) { - break; - } - outer_class = outer_class->owner; - } - - if (!found && p->base_type.kind == DataType::CLASS) { - p = p->base_type.class_type; + } else { + call->callee = p_previous_operand; + + 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) { + SubscriptNode *attribute = static_cast<SubscriptNode *>(call->callee); + if (attribute->is_attribute) { + if (attribute->attribute) { + call->function_name = attribute->attribute->name; + } + make_completion_context(COMPLETION_ATTRIBUTE_METHOD, call->callee); } else { - base_type = p->base_type; - break; + // TODO: The analyzer can see if this is actually a Callable and give better error message. + push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*"); } + } else { + push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*"); } + } - // Still look for class constants in parent scripts - if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) { - Ref<Script> scr = base_type.script_type; - ERR_FAIL_COND_V(scr.is_null(), result); - while (scr.is_valid()) { - Map<StringName, Variant> constants; - scr->get_constants(&constants); - - if (constants.has(id)) { - Ref<GDScript> gds = constants[id]; - - if (gds.is_valid()) { - result.kind = DataType::GDSCRIPT; - result.script_type = gds; - found = true; - } else { - Ref<Script> scr2 = constants[id]; - if (scr2.is_valid()) { - result.kind = DataType::SCRIPT; - result.script_type = scr2; - found = true; - } - } - } - if (found) { - break; - } else { - scr = scr->get_base_script(); - } + 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; } - } - - if (!found && !for_completion) { - String base; - if (name_part == 0) { - base = "self"; + ExpressionNode *argument = parse_expression(false); + if (argument == nullptr) { + push_error(R"(Expected expression as the function argument.)"); } else { - base = result.to_string(); + call->arguments.push_back(argument); } - _set_error("The identifier \"" + String(id) + "\" isn't a valid type (not a script or class), or couldn't be found on base \"" + - base + "\".", - p_line); - return DataType(); - } - - name_part++; + } while (match(GDScriptTokenizer::Token::COMMA)); + pop_completion_call(); } - return result; -} + pop_multiline(); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after call arguments.)*"); -GDScriptParser::DataType GDScriptParser::_type_from_variant(const Variant &p_value) const { - DataType result; - result.has_type = true; - result.is_constant = true; - result.kind = DataType::BUILTIN; - result.builtin_type = p_value.get_type(); + return call; +} - if (result.builtin_type == Variant::OBJECT) { - Object *obj = p_value.operator Object *(); - if (!obj) { - return DataType(); - } - result.native_type = obj->get_class_name(); - Ref<Script> scr = p_value; - if (scr.is_valid()) { - result.is_meta_type = true; - } else { - result.is_meta_type = false; - scr = obj->get_script(); +GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) { + if (match(GDScriptTokenizer::Token::LITERAL)) { + if (previous.literal.get_type() != Variant::STRING) { + push_error(R"(Expect node path as string or identifer after "$".)"); + return nullptr; } - if (scr.is_valid()) { - result.script_type = scr; - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - result.kind = DataType::GDSCRIPT; - } else { - result.kind = DataType::SCRIPT; + GetNodeNode *get_node = alloc_node<GetNodeNode>(); + make_completion_context(COMPLETION_GET_NODE, get_node); + get_node->string = parse_literal(); + return get_node; + } else if (check(GDScriptTokenizer::Token::IDENTIFIER)) { + GetNodeNode *get_node = alloc_node<GetNodeNode>(); + int chain_position = 0; + do { + make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) { + return nullptr; } - result.native_type = scr->get_instance_base_type(); - } else { - result.kind = DataType::NATIVE; - } - } - - return result; -} - -GDScriptParser::DataType GDScriptParser::_type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant) const { - DataType ret; - if (p_property.type == Variant::NIL && (p_nil_is_variant || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { - // Variant - return ret; - } - ret.has_type = true; - ret.builtin_type = p_property.type; - if (p_property.type == Variant::OBJECT) { - ret.kind = DataType::NATIVE; - ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + IdentifierNode *identifier = parse_identifier(); + get_node->chain.push_back(identifier); + } while (match(GDScriptTokenizer::Token::SLASH)); + return get_node; } else { - ret.kind = DataType::BUILTIN; + push_error(R"(Expect node path as string or identifer after "$".)"); + return nullptr; } - return ret; } -GDScriptParser::DataType GDScriptParser::_type_from_gdtype(const GDScriptDataType &p_gdtype) const { - DataType result; - if (!p_gdtype.has_type) { - return result; - } - - result.has_type = true; - result.builtin_type = p_gdtype.builtin_type; - result.native_type = p_gdtype.native_type; - result.script_type = p_gdtype.script_type; +GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_previous_operand, bool p_can_assign) { + PreloadNode *preload = alloc_node<PreloadNode>(); + preload->resolved_path = "<missing path>"; - switch (p_gdtype.kind) { - case GDScriptDataType::UNINITIALIZED: { - ERR_PRINT("Uninitialized datatype. Please report a bug."); - } break; - case GDScriptDataType::BUILTIN: { - result.kind = DataType::BUILTIN; - } break; - case GDScriptDataType::NATIVE: { - result.kind = DataType::NATIVE; - } break; - case GDScriptDataType::GDSCRIPT: { - result.kind = DataType::GDSCRIPT; - } break; - case GDScriptDataType::SCRIPT: { - result.kind = DataType::SCRIPT; - } break; - } - return result; -} + push_multiline(true); + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "preload".)"); -GDScriptParser::DataType GDScriptParser::_get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const { - if (!p_a.has_type || !p_b.has_type) { - r_valid = true; - return DataType(); - } + make_completion_context(COMPLETION_RESOURCE_PATH, preload); + push_completion_call(preload); - Variant::Type a_type = p_a.kind == DataType::BUILTIN ? p_a.builtin_type : Variant::OBJECT; - Variant::Type b_type = p_b.kind == DataType::BUILTIN ? p_b.builtin_type : Variant::OBJECT; + preload->path = parse_expression(false); - Variant a; - REF a_ref; - if (a_type == Variant::OBJECT) { - a_ref.instance(); - a = a_ref; + 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 { - Callable::CallError err; - a = Variant::construct(a_type, nullptr, 0, err); - if (err.error != Callable::CallError::CALL_OK) { - r_valid = false; - return DataType(); - } - } - Variant b; - REF b_ref; - if (b_type == Variant::OBJECT) { - b_ref.instance(); - b = b_ref; - } else { - Callable::CallError err; - b = Variant::construct(b_type, nullptr, 0, err); - if (err.error != Callable::CallError::CALL_OK) { - r_valid = false; - return DataType(); + 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)); + } + } } } - // Avoid division by zero - if (a_type == Variant::INT || a_type == Variant::FLOAT) { - Variant::evaluate(Variant::OP_ADD, a, 1, a, r_valid); - } - if (b_type == Variant::INT || b_type == Variant::FLOAT) { - Variant::evaluate(Variant::OP_ADD, b, 1, b, r_valid); - } - if (a_type == Variant::STRING && b_type != Variant::ARRAY) { - a = "%s"; // Work around for formatting operator (%) - } - - Variant ret; - Variant::evaluate(p_op, a, b, ret, r_valid); + pop_completion_call(); - if (r_valid) { - return _type_from_variant(ret); - } + pop_multiline(); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after preload path.)*"); - return DataType(); -} - -Variant::Operator GDScriptParser::_get_variant_operation(const OperatorNode::Operator &p_op) const { - switch (p_op) { - case OperatorNode::OP_NEG: { - return Variant::OP_NEGATE; - } break; - case OperatorNode::OP_POS: { - return Variant::OP_POSITIVE; - } break; - case OperatorNode::OP_NOT: { - return Variant::OP_NOT; - } break; - case OperatorNode::OP_BIT_INVERT: { - return Variant::OP_BIT_NEGATE; - } break; - case OperatorNode::OP_IN: { - return Variant::OP_IN; - } break; - case OperatorNode::OP_EQUAL: { - return Variant::OP_EQUAL; - } break; - case OperatorNode::OP_NOT_EQUAL: { - return Variant::OP_NOT_EQUAL; - } break; - case OperatorNode::OP_LESS: { - return Variant::OP_LESS; - } break; - case OperatorNode::OP_LESS_EQUAL: { - return Variant::OP_LESS_EQUAL; - } break; - case OperatorNode::OP_GREATER: { - return Variant::OP_GREATER; - } break; - case OperatorNode::OP_GREATER_EQUAL: { - return Variant::OP_GREATER_EQUAL; - } break; - case OperatorNode::OP_AND: { - return Variant::OP_AND; - } break; - case OperatorNode::OP_OR: { - return Variant::OP_OR; - } break; - case OperatorNode::OP_ASSIGN_ADD: - case OperatorNode::OP_ADD: { - return Variant::OP_ADD; - } break; - case OperatorNode::OP_ASSIGN_SUB: - case OperatorNode::OP_SUB: { - return Variant::OP_SUBTRACT; - } break; - case OperatorNode::OP_ASSIGN_MUL: - case OperatorNode::OP_MUL: { - return Variant::OP_MULTIPLY; - } break; - case OperatorNode::OP_ASSIGN_DIV: - case OperatorNode::OP_DIV: { - return Variant::OP_DIVIDE; - } break; - case OperatorNode::OP_ASSIGN_MOD: - case OperatorNode::OP_MOD: { - return Variant::OP_MODULE; - } break; - case OperatorNode::OP_ASSIGN_BIT_AND: - case OperatorNode::OP_BIT_AND: { - return Variant::OP_BIT_AND; - } break; - case OperatorNode::OP_ASSIGN_BIT_OR: - case OperatorNode::OP_BIT_OR: { - return Variant::OP_BIT_OR; - } break; - case OperatorNode::OP_ASSIGN_BIT_XOR: - case OperatorNode::OP_BIT_XOR: { - return Variant::OP_BIT_XOR; - } break; - case OperatorNode::OP_ASSIGN_SHIFT_LEFT: - case OperatorNode::OP_SHIFT_LEFT: { - return Variant::OP_SHIFT_LEFT; - } - case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: - case OperatorNode::OP_SHIFT_RIGHT: { - return Variant::OP_SHIFT_RIGHT; - } - default: { - return Variant::OP_MAX; - } break; - } + return preload; } -bool GDScriptParser::_is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion) const { - // Ignore for completion - if (!check_types || for_completion) { - return true; - } - // Can't test if not all have type - if (!p_container.has_type || !p_expression.has_type) { - return true; - } - - // Should never get here unresolved - ERR_FAIL_COND_V(p_container.kind == DataType::UNRESOLVED, false); - ERR_FAIL_COND_V(p_expression.kind == DataType::UNRESOLVED, false); - - if (p_container.kind == DataType::BUILTIN && p_expression.kind == DataType::BUILTIN) { - bool valid = p_container.builtin_type == p_expression.builtin_type; - if (p_allow_implicit_conversion) { - valid = valid || Variant::can_convert_strict(p_expression.builtin_type, p_container.builtin_type); - } - return valid; - } - - if (p_container.kind == DataType::BUILTIN && p_container.builtin_type == Variant::OBJECT) { - // Object built-in is a special case, it's compatible with any object and with null - if (p_expression.kind == DataType::BUILTIN) { - return p_expression.builtin_type == Variant::NIL; - } - // If it's not a built-in, must be an object - return true; - } - - if (p_container.kind == DataType::BUILTIN || (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type != Variant::NIL)) { - // Can't mix built-ins with objects - return false; - } +GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) { + // Just for better error messages. + GDScriptTokenizer::Token::Type invalid = previous.type; - // From now on everything is objects, check polymorphism - // The container must be the same class or a superclass of the expression - - if (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type == Variant::NIL) { - // Null can be assigned to object types - return true; + switch (invalid) { + case GDScriptTokenizer::Token::QUESTION_MARK: + push_error(R"(Unexpected "?" in source. If you want a ternary operator, use "truthy_value if true_condition else falsy_value".)"); + break; + default: + return nullptr; // Unreachable. } - StringName expr_native; - Ref<Script> expr_script; - ClassNode *expr_class = nullptr; + // Return the previous expression. + return p_previous_operand; +} - switch (p_expression.kind) { - case DataType::NATIVE: { - if (p_container.kind != DataType::NATIVE) { - // Non-native type can't be a superclass of a native type - return false; - } - if (p_expression.is_meta_type) { - expr_native = GDScriptNativeClass::get_class_static(); - } else { - expr_native = p_expression.native_type; - } - } break; - case DataType::SCRIPT: - case DataType::GDSCRIPT: { - if (p_container.kind == DataType::CLASS) { - // This cannot be resolved without cyclic dependencies, so just bail out - return false; - } - if (p_expression.is_meta_type) { - expr_native = p_expression.script_type->get_class_name(); +GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { + TypeNode *type = alloc_node<TypeNode>(); + make_completion_context(p_allow_void ? COMPLETION_TYPE_NAME_OR_VOID : COMPLETION_TYPE_NAME, type); + if (!match(GDScriptTokenizer::Token::IDENTIFIER)) { + if (match(GDScriptTokenizer::Token::VOID)) { + if (p_allow_void) { + TypeNode *void_type = alloc_node<TypeNode>(); + return void_type; } else { - expr_script = p_expression.script_type; - expr_native = expr_script->get_instance_base_type(); + push_error(R"("void" is only allowed for a function return type.)"); } - } break; - case DataType::CLASS: { - if (p_expression.is_meta_type) { - expr_native = GDScript::get_class_static(); - } else { - expr_class = p_expression.class_type; - ClassNode *base = expr_class; - while (base->base_type.kind == DataType::CLASS) { - base = base->base_type.class_type; - } - expr_native = base->base_type.native_type; - expr_script = base->base_type.script_type; - } - } break; - case DataType::BUILTIN: // Already handled above - case DataType::UNRESOLVED: // Not allowed, see above - break; + } + // Leave error message to the caller who knows the context. + return nullptr; } - // Some classes are prefixed with `_` internally - if (!ClassDB::class_exists(expr_native)) { - expr_native = "_" + expr_native; + IdentifierNode *type_element = parse_identifier(); + + type->type_chain.push_back(type_element); + + int chain_index = 1; + while (match(GDScriptTokenizer::Token::PERIOD)) { + make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++); + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected inner type name after ".".)")) { + type_element = parse_identifier(); + type->type_chain.push_back(type_element); + } + } + + return type; +} + +GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Type p_token_type) { + // Function table for expression parsing. + // clang-format destroys the alignment here, so turn off for the table. + /* clang-format off */ + static ParseRule rules[] = { + // PREFIX INFIX PRECEDENCE (for infix) + { nullptr, nullptr, PREC_NONE }, // EMPTY, + // Basic + { nullptr, nullptr, PREC_NONE }, // ANNOTATION, + { &GDScriptParser::parse_identifier, nullptr, PREC_NONE }, // IDENTIFIER, + { &GDScriptParser::parse_literal, nullptr, PREC_NONE }, // LITERAL, + // Comparison + { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // LESS, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // LESS_EQUAL, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // GREATER, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // GREATER_EQUAL, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // EQUAL_EQUAL, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_COMPARISON }, // BANG_EQUAL, + // Logical + { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AND, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // OR, + { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // NOT, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AMPERSAND_AMPERSAND, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // PIPE_PIPE, + { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // BANG, + // Bitwise + { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_AND }, // AMPERSAND, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_OR }, // PIPE, + { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // TILDE, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_XOR }, // CARET, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_SHIFT }, // LESS_LESS, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_SHIFT }, // GREATER_GREATER, + // Math + { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_operator, PREC_ADDITION }, // PLUS, + { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_operator, PREC_SUBTRACTION }, // MINUS, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // STAR, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // SLASH, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // PERCENT, + // Assignment + { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // EQUAL, + { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PLUS_EQUAL, + { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // MINUS_EQUAL, + { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // STAR_EQUAL, + { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // SLASH_EQUAL, + { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PERCENT_EQUAL, + { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // LESS_LESS_EQUAL, + { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // GREATER_GREATER_EQUAL, + { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // AMPERSAND_EQUAL, + { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PIPE_EQUAL, + { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // CARET_EQUAL, + // Control flow + { nullptr, &GDScriptParser::parse_ternary_operator, PREC_TERNARY }, // IF, + { nullptr, nullptr, PREC_NONE }, // ELIF, + { nullptr, nullptr, PREC_NONE }, // ELSE, + { nullptr, nullptr, PREC_NONE }, // FOR, + { nullptr, nullptr, PREC_NONE }, // WHILE, + { nullptr, nullptr, PREC_NONE }, // BREAK, + { nullptr, nullptr, PREC_NONE }, // CONTINUE, + { nullptr, nullptr, PREC_NONE }, // PASS, + { nullptr, nullptr, PREC_NONE }, // RETURN, + { nullptr, nullptr, PREC_NONE }, // MATCH, + // Keywords + { nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS, + { nullptr, nullptr, PREC_NONE }, // ASSERT, + { &GDScriptParser::parse_await, nullptr, PREC_NONE }, // AWAIT, + { nullptr, nullptr, PREC_NONE }, // BREAKPOINT, + { nullptr, nullptr, PREC_NONE }, // CLASS, + { nullptr, nullptr, PREC_NONE }, // CLASS_NAME, + { nullptr, nullptr, PREC_NONE }, // CONST, + { nullptr, nullptr, PREC_NONE }, // ENUM, + { nullptr, nullptr, PREC_NONE }, // EXTENDS, + { nullptr, nullptr, PREC_NONE }, // FUNC, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN, + { nullptr, &GDScriptParser::parse_binary_operator, PREC_TYPE_TEST }, // IS, + { nullptr, nullptr, PREC_NONE }, // NAMESPACE, + { &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD, + { &GDScriptParser::parse_self, nullptr, PREC_NONE }, // SELF, + { nullptr, nullptr, PREC_NONE }, // SIGNAL, + { nullptr, nullptr, PREC_NONE }, // STATIC, + { &GDScriptParser::parse_call, nullptr, PREC_NONE }, // SUPER, + { nullptr, nullptr, PREC_NONE }, // TRAIT, + { nullptr, nullptr, PREC_NONE }, // VAR, + { nullptr, nullptr, PREC_NONE }, // VOID, + { nullptr, nullptr, PREC_NONE }, // YIELD, + // Punctuation + { &GDScriptParser::parse_array, &GDScriptParser::parse_subscript, PREC_SUBSCRIPT }, // BRACKET_OPEN, + { nullptr, nullptr, PREC_NONE }, // BRACKET_CLOSE, + { &GDScriptParser::parse_dictionary, nullptr, PREC_NONE }, // BRACE_OPEN, + { nullptr, nullptr, PREC_NONE }, // BRACE_CLOSE, + { &GDScriptParser::parse_grouping, &GDScriptParser::parse_call, PREC_CALL }, // PARENTHESIS_OPEN, + { nullptr, nullptr, PREC_NONE }, // PARENTHESIS_CLOSE, + { nullptr, nullptr, PREC_NONE }, // COMMA, + { nullptr, nullptr, PREC_NONE }, // SEMICOLON, + { nullptr, &GDScriptParser::parse_attribute, PREC_ATTRIBUTE }, // PERIOD, + { nullptr, nullptr, PREC_NONE }, // PERIOD_PERIOD, + { nullptr, nullptr, PREC_NONE }, // COLON, + { &GDScriptParser::parse_get_node, nullptr, PREC_NONE }, // DOLLAR, + { nullptr, nullptr, PREC_NONE }, // FORWARD_ARROW, + { nullptr, nullptr, PREC_NONE }, // UNDERSCORE, + // Whitespace + { nullptr, nullptr, PREC_NONE }, // NEWLINE, + { nullptr, nullptr, PREC_NONE }, // INDENT, + { nullptr, nullptr, PREC_NONE }, // DEDENT, + // Constants + { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_PI, + { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_TAU, + { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_INF, + { &GDScriptParser::parse_builtin_constant, nullptr, PREC_NONE }, // CONST_NAN, + // Error message improvement + { nullptr, nullptr, PREC_NONE }, // VCS_CONFLICT_MARKER, + { nullptr, nullptr, PREC_NONE }, // BACKTICK, + { nullptr, &GDScriptParser::parse_invalid_token, PREC_CAST }, // QUESTION_MARK, + // Special + { nullptr, nullptr, PREC_NONE }, // ERROR, + { nullptr, nullptr, PREC_NONE }, // TK_EOF, + }; + /* clang-format on */ + // Avoid desync. + static_assert(sizeof(rules) / sizeof(rules[0]) == GDScriptTokenizer::Token::TK_MAX, "Amount of parse rules don't match the amount of token types."); + + // Let's assume this this never invalid, since nothing generates a TK_MAX. + return &rules[p_token_type]; +} + +bool GDScriptParser::SuiteNode::has_local(const StringName &p_name) const { + if (locals_indices.has(p_name)) { + return true; } - - switch (p_container.kind) { - case DataType::NATIVE: { - if (p_container.is_meta_type) { - return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static()); - } else { - StringName container_native = ClassDB::class_exists(p_container.native_type) ? p_container.native_type : StringName("_" + p_container.native_type); - return ClassDB::is_parent_class(expr_native, container_native); - } - } break; - case DataType::SCRIPT: - case DataType::GDSCRIPT: { - if (p_container.is_meta_type) { - return ClassDB::is_parent_class(expr_native, GDScript::get_class_static()); - } - if (expr_class == head && p_container.script_type->get_path() == self_path) { - // Special case: container is self script and expression is self - return true; - } - while (expr_script.is_valid()) { - if (expr_script == p_container.script_type) { - return true; - } - expr_script = expr_script->get_base_script(); - } - return false; - } break; - case DataType::CLASS: { - if (p_container.is_meta_type) { - return ClassDB::is_parent_class(expr_native, GDScript::get_class_static()); - } - if (p_container.class_type == head && expr_script.is_valid() && expr_script->get_path() == self_path) { - // Special case: container is self and expression is self script - return true; - } - while (expr_class) { - if (expr_class == p_container.class_type) { - return true; - } - expr_class = expr_class->base_type.class_type; - } - return false; - } break; - case DataType::BUILTIN: // Already handled above - case DataType::UNRESOLVED: // Not allowed, see above - break; + if (parent_block != nullptr) { + return parent_block->has_local(p_name); } - return false; } -GDScriptParser::Node *GDScriptParser::_get_default_value_for_type(const DataType &p_type, int p_line) { - Node *result; - - if (p_type.has_type && p_type.kind == DataType::BUILTIN && p_type.builtin_type != Variant::NIL && p_type.builtin_type != Variant::OBJECT) { - if (p_type.builtin_type == Variant::ARRAY) { - result = alloc_node<ArrayNode>(); - } else if (p_type.builtin_type == Variant::DICTIONARY) { - result = alloc_node<DictionaryNode>(); - } else { - ConstantNode *c = alloc_node<ConstantNode>(); - Callable::CallError err; - c->value = Variant::construct(p_type.builtin_type, nullptr, 0, err); - result = c; - } - } else { - ConstantNode *c = alloc_node<ConstantNode>(); - c->value = Variant(); - result = c; +const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(const StringName &p_name) const { + if (locals_indices.has(p_name)) { + return locals[locals_indices[p_name]]; } + if (parent_block != nullptr) { + return parent_block->get_local(p_name); + } + return empty; +} - result->line = p_line; - - return result; +bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) const { + return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target); } -GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { -#ifdef DEBUG_ENABLED - if (p_node->get_datatype().has_type && p_node->type != Node::TYPE_ARRAY && p_node->type != Node::TYPE_DICTIONARY) { -#else - if (p_node->get_datatype().has_type) { -#endif - return p_node->get_datatype(); - } +bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const { + return (info->target_kind & p_target_kinds) > 0; +} - DataType node_type; +bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) { + ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name)); - switch (p_node->type) { - case Node::TYPE_CONSTANT: { - node_type = _type_from_variant(static_cast<ConstantNode *>(p_node)->value); - } break; - case Node::TYPE_TYPE: { - TypeNode *tn = static_cast<TypeNode *>(p_node); - node_type.has_type = true; - node_type.is_meta_type = true; - node_type.kind = DataType::BUILTIN; - node_type.builtin_type = tn->vtype; - } break; - case Node::TYPE_ARRAY: { - node_type.has_type = true; - node_type.kind = DataType::BUILTIN; - node_type.builtin_type = Variant::ARRAY; -#ifdef DEBUG_ENABLED - // Check stuff inside the array - ArrayNode *an = static_cast<ArrayNode *>(p_node); - for (int i = 0; i < an->elements.size(); i++) { - _reduce_node_type(an->elements[i]); - } -#endif // DEBUG_ENABLED - } break; - case Node::TYPE_DICTIONARY: { - node_type.has_type = true; - node_type.kind = DataType::BUILTIN; - node_type.builtin_type = Variant::DICTIONARY; -#ifdef DEBUG_ENABLED - // Check stuff inside the dictionarty - DictionaryNode *dn = static_cast<DictionaryNode *>(p_node); - for (int i = 0; i < dn->elements.size(); i++) { - _reduce_node_type(dn->elements[i].key); - _reduce_node_type(dn->elements[i].value); - } -#endif // DEBUG_ENABLED - } break; - case Node::TYPE_SELF: { - node_type.has_type = true; - node_type.kind = DataType::CLASS; - node_type.class_type = current_class; - node_type.is_constant = true; - } break; - case Node::TYPE_IDENTIFIER: { - IdentifierNode *id = static_cast<IdentifierNode *>(p_node); - if (id->declared_block) { - node_type = id->declared_block->variables[id->name]->get_datatype(); - id->declared_block->variables[id->name]->usages += 1; - } else if (id->name == "#match_value") { - // It's a special id just for the match statetement, ignore - break; - } else if (current_function && current_function->arguments.find(id->name) >= 0) { - int idx = current_function->arguments.find(id->name); - node_type = current_function->argument_types[idx]; - } else { - node_type = _reduce_identifier_type(nullptr, id->name, id->line, false); - } - } break; - case Node::TYPE_CAST: { - CastNode *cn = static_cast<CastNode *>(p_node); + const MethodInfo &info = valid_annotations[p_annotation->name].info; - DataType source_type = _reduce_node_type(cn->source_node); - cn->cast_type = _resolve_type(cn->cast_type, cn->line); - if (source_type.has_type) { + if (((info.flags & METHOD_FLAG_VARARG) == 0) && p_annotation->arguments.size() > info.arguments.size()) { + push_error(vformat(R"(Annotation "%s" requires at most %d arguments, but %d were given.)", p_annotation->name, info.arguments.size(), p_annotation->arguments.size())); + return false; + } - bool valid = false; - if (check_types) { - if (cn->cast_type.kind == DataType::BUILTIN && source_type.kind == DataType::BUILTIN) { - valid = Variant::can_convert(source_type.builtin_type, cn->cast_type.builtin_type); - } - if (cn->cast_type.kind != DataType::BUILTIN && source_type.kind != DataType::BUILTIN) { - valid = _is_type_compatible(cn->cast_type, source_type) || _is_type_compatible(source_type, cn->cast_type); - } + if (p_annotation->arguments.size() < info.arguments.size() - info.default_arguments.size()) { + push_error(vformat(R"(Annotation "%s" requires at least %d arguments, but %d were given.)", p_annotation->name, info.arguments.size() - info.default_arguments.size(), p_annotation->arguments.size())); + return false; + } - if (!valid) { - _set_error("Invalid cast. Cannot convert from \"" + source_type.to_string() + - "\" to \"" + cn->cast_type.to_string() + "\".", - cn->line); - return DataType(); + const List<PropertyInfo>::Element *E = info.arguments.front(); + for (int i = 0; i < p_annotation->arguments.size(); i++) { + ExpressionNode *argument = p_annotation->arguments[i]; + const PropertyInfo ¶meter = E->get(); + + if (E->next() != nullptr) { + E = E->next(); + } + + switch (parameter.type) { + case Variant::STRING: + case Variant::STRING_NAME: + case Variant::NODE_PATH: + // Allow "quote-less strings", as long as they are recognized as identifiers. + if (argument->type == Node::IDENTIFIER) { + IdentifierNode *string = static_cast<IdentifierNode *>(argument); + Callable::CallError error; + Vector<Variant> args = varray(string->name); + const Variant *name = args.ptr(); + p_annotation->resolved_arguments.push_back(Variant::construct(parameter.type, &(name), 1, error)); + if (error.error != Callable::CallError::CALL_OK) { + push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); + p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1); + return false; } + break; + } + [[fallthrough]]; + default: { + if (argument->type != Node::LITERAL) { + push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); + return false; } - } else { -#ifdef DEBUG_ENABLED - _add_warning(GDScriptWarning::UNSAFE_CAST, cn->line, cn->cast_type.to_string()); -#endif // DEBUG_ENABLED - _mark_line_as_unsafe(cn->line); - } - - node_type = cn->cast_type; - - } break; - case Node::TYPE_OPERATOR: { - OperatorNode *op = static_cast<OperatorNode *>(p_node); - - switch (op->op) { - case OperatorNode::OP_CALL: - case OperatorNode::OP_PARENT_CALL: { - node_type = _reduce_function_call_type(op); - } break; - case OperatorNode::OP_YIELD: { - if (op->arguments.size() == 2) { - DataType base_type = _reduce_node_type(op->arguments[0]); - DataType signal_type = _reduce_node_type(op->arguments[1]); - // TODO: Check if signal exists when it's a constant - if (base_type.has_type && base_type.kind == DataType::BUILTIN && base_type.builtin_type != Variant::NIL && base_type.builtin_type != Variant::OBJECT) { - _set_error("The first argument of \"yield()\" must be an object.", op->line); - return DataType(); - } - if (signal_type.has_type && (signal_type.kind != DataType::BUILTIN || signal_type.builtin_type != Variant::STRING)) { - _set_error("The second argument of \"yield()\" must be a string.", op->line); - return DataType(); - } - } - // yield can return anything - node_type.has_type = false; - } break; - case OperatorNode::OP_IS: - case OperatorNode::OP_IS_BUILTIN: { - - if (op->arguments.size() != 2) { - _set_error("Parser bug: binary operation without 2 arguments.", op->line); - ERR_FAIL_V(DataType()); - } - - DataType value_type = _reduce_node_type(op->arguments[0]); - DataType type_type = _reduce_node_type(op->arguments[1]); - - if (check_types && type_type.has_type) { - if (!type_type.is_meta_type && (type_type.kind != DataType::NATIVE || !ClassDB::is_parent_class(type_type.native_type, "Script"))) { - _set_error("Invalid \"is\" test: the right operand isn't a type (neither a native type nor a script).", op->line); - return DataType(); - } - type_type.is_meta_type = false; // Test the actual type - if (!_is_type_compatible(type_type, value_type) && !_is_type_compatible(value_type, type_type)) { - if (op->op == OperatorNode::OP_IS) { - _set_error("A value of type \"" + value_type.to_string() + "\" will never be an instance of \"" + type_type.to_string() + "\".", op->line); - } else { - _set_error("A value of type \"" + value_type.to_string() + "\" will never be of type \"" + type_type.to_string() + "\".", op->line); - } - return DataType(); - } - } - - node_type.has_type = true; - node_type.is_constant = true; - node_type.is_meta_type = false; - node_type.kind = DataType::BUILTIN; - node_type.builtin_type = Variant::BOOL; - } break; - // Unary operators - case OperatorNode::OP_NEG: - case OperatorNode::OP_POS: - case OperatorNode::OP_NOT: - case OperatorNode::OP_BIT_INVERT: { - - DataType argument_type = _reduce_node_type(op->arguments[0]); - if (!argument_type.has_type) { - break; - } - - Variant::Operator var_op = _get_variant_operation(op->op); - bool valid = false; - node_type = _get_operation_type(var_op, argument_type, argument_type, valid); - - if (check_types && !valid) { - _set_error("Invalid operand type (\"" + argument_type.to_string() + - "\") to unary operator \"" + Variant::get_operator_name(var_op) + "\".", - op->line, op->column); - return DataType(); - } - - } break; - // Binary operators - case OperatorNode::OP_IN: - case OperatorNode::OP_EQUAL: - case OperatorNode::OP_NOT_EQUAL: - case OperatorNode::OP_LESS: - case OperatorNode::OP_LESS_EQUAL: - case OperatorNode::OP_GREATER: - case OperatorNode::OP_GREATER_EQUAL: - case OperatorNode::OP_AND: - case OperatorNode::OP_OR: - case OperatorNode::OP_ADD: - case OperatorNode::OP_SUB: - case OperatorNode::OP_MUL: - case OperatorNode::OP_DIV: - case OperatorNode::OP_MOD: - case OperatorNode::OP_SHIFT_LEFT: - case OperatorNode::OP_SHIFT_RIGHT: - case OperatorNode::OP_BIT_AND: - case OperatorNode::OP_BIT_OR: - case OperatorNode::OP_BIT_XOR: { - - if (op->arguments.size() != 2) { - _set_error("Parser bug: binary operation without 2 arguments.", op->line); - ERR_FAIL_V(DataType()); - } - - DataType argument_a_type = _reduce_node_type(op->arguments[0]); - DataType argument_b_type = _reduce_node_type(op->arguments[1]); - if (!argument_a_type.has_type || !argument_b_type.has_type) { - _mark_line_as_unsafe(op->line); - break; - } - - Variant::Operator var_op = _get_variant_operation(op->op); - bool valid = false; - node_type = _get_operation_type(var_op, argument_a_type, argument_b_type, valid); - - if (check_types && !valid) { - _set_error("Invalid operand types (\"" + argument_a_type.to_string() + "\" and \"" + - argument_b_type.to_string() + "\") to operator \"" + Variant::get_operator_name(var_op) + "\".", - op->line, op->column); - return DataType(); - } -#ifdef DEBUG_ENABLED - if (var_op == Variant::OP_DIVIDE && argument_a_type.kind == DataType::BUILTIN && argument_a_type.builtin_type == Variant::INT && - argument_b_type.kind == DataType::BUILTIN && argument_b_type.builtin_type == Variant::INT) { - _add_warning(GDScriptWarning::INTEGER_DIVISION, op->line); - } -#endif // DEBUG_ENABLED - - } break; - // Ternary operators - case OperatorNode::OP_TERNARY_IF: { - if (op->arguments.size() != 3) { - _set_error("Parser bug: ternary operation without 3 arguments."); - ERR_FAIL_V(DataType()); - } - - DataType true_type = _reduce_node_type(op->arguments[1]); - DataType false_type = _reduce_node_type(op->arguments[2]); - // Check arguments[0] errors. - _reduce_node_type(op->arguments[0]); - - // If types are equal, then the expression is of the same type - // If they are compatible, return the broader type - if (true_type == false_type || _is_type_compatible(true_type, false_type)) { - node_type = true_type; - } else if (_is_type_compatible(false_type, true_type)) { - node_type = false_type; - } else { -#ifdef DEBUG_ENABLED - _add_warning(GDScriptWarning::INCOMPATIBLE_TERNARY, op->line); -#endif // DEBUG_ENABLED - } - } break; - // Assignment should never happen within an expression - case OperatorNode::OP_ASSIGN: - case OperatorNode::OP_ASSIGN_ADD: - case OperatorNode::OP_ASSIGN_SUB: - case OperatorNode::OP_ASSIGN_MUL: - case OperatorNode::OP_ASSIGN_DIV: - case OperatorNode::OP_ASSIGN_MOD: - case OperatorNode::OP_ASSIGN_SHIFT_LEFT: - case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: - case OperatorNode::OP_ASSIGN_BIT_AND: - case OperatorNode::OP_ASSIGN_BIT_OR: - case OperatorNode::OP_ASSIGN_BIT_XOR: - case OperatorNode::OP_INIT_ASSIGN: { - - _set_error("Assignment inside an expression isn't allowed (parser bug?).", op->line); - return DataType(); - - } break; - case OperatorNode::OP_INDEX_NAMED: { - if (op->arguments.size() != 2) { - _set_error("Parser bug: named index with invalid arguments.", op->line); - ERR_FAIL_V(DataType()); - } - if (op->arguments[1]->type != Node::TYPE_IDENTIFIER) { - _set_error("Parser bug: named index without identifier argument.", op->line); - ERR_FAIL_V(DataType()); - } - - DataType base_type = _reduce_node_type(op->arguments[0]); - IdentifierNode *member_id = static_cast<IdentifierNode *>(op->arguments[1]); - - if (base_type.has_type) { - if (check_types && base_type.kind == DataType::BUILTIN) { - // Variant type, just test if it's possible - DataType result; - switch (base_type.builtin_type) { - case Variant::NIL: - case Variant::DICTIONARY: { - result.has_type = false; - } break; - default: { - Callable::CallError err; - Variant temp = Variant::construct(base_type.builtin_type, nullptr, 0, err); - - bool valid = false; - Variant res = temp.get(member_id->name.operator String(), &valid); - - if (valid) { - result = _type_from_variant(res); - } else if (check_types) { - _set_error("Can't get index \"" + String(member_id->name.operator String()) + "\" on base \"" + - base_type.to_string() + "\".", - op->line); - return DataType(); - } - } break; - } - result.is_constant = false; - node_type = result; - } else { - node_type = _reduce_identifier_type(&base_type, member_id->name, op->line, true); -#ifdef DEBUG_ENABLED - if (!node_type.has_type) { - _add_warning(GDScriptWarning::UNSAFE_PROPERTY_ACCESS, op->line, member_id->name.operator String(), base_type.to_string()); - } -#endif // DEBUG_ENABLED - } - } else { - _mark_line_as_unsafe(op->line); - } - if (error_set) { - return DataType(); - } - } break; - case OperatorNode::OP_INDEX: { - - if (op->arguments[1]->type == Node::TYPE_CONSTANT) { - ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]); - if (cn->value.get_type() == Variant::STRING) { - // Treat this as named indexing - - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = cn->value.operator StringName(); - - op->op = OperatorNode::OP_INDEX_NAMED; - op->arguments.write[1] = id; - - return _reduce_node_type(op); - } - } - - DataType base_type = _reduce_node_type(op->arguments[0]); - DataType index_type = _reduce_node_type(op->arguments[1]); - - if (!base_type.has_type) { - _mark_line_as_unsafe(op->line); - break; - } - - if (check_types && index_type.has_type) { - if (base_type.kind == DataType::BUILTIN) { - // Check if indexing is valid - bool error = index_type.kind != DataType::BUILTIN && base_type.builtin_type != Variant::DICTIONARY; - if (!error) { - switch (base_type.builtin_type) { - // Expect int or real as index - case Variant::PACKED_BYTE_ARRAY: - case Variant::PACKED_COLOR_ARRAY: - case Variant::PACKED_INT32_ARRAY: - case Variant::PACKED_INT64_ARRAY: - case Variant::PACKED_FLOAT32_ARRAY: - case Variant::PACKED_FLOAT64_ARRAY: - case Variant::PACKED_STRING_ARRAY: - case Variant::PACKED_VECTOR2_ARRAY: - case Variant::PACKED_VECTOR3_ARRAY: - case Variant::ARRAY: - case Variant::STRING: { - error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT; - } break; - // Expect String only - case Variant::RECT2: - case Variant::PLANE: - case Variant::QUAT: - case Variant::AABB: - case Variant::OBJECT: { - error = index_type.builtin_type != Variant::STRING; - } break; - // Expect String or number - case Variant::VECTOR2: - case Variant::VECTOR3: - case Variant::TRANSFORM2D: - case Variant::BASIS: - case Variant::TRANSFORM: { - error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT && - index_type.builtin_type != Variant::STRING; - } break; - // Expect String or int - case Variant::COLOR: { - error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; - } break; - default: { - } - } - } - if (error) { - _set_error("Invalid index type (" + index_type.to_string() + ") for base \"" + base_type.to_string() + "\".", - op->line); - return DataType(); - } - if (op->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) { - ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]); - // Index is a constant, just try it if possible - switch (base_type.builtin_type) { - // Arrays/string have variable indexing, can't test directly - case Variant::STRING: - case Variant::ARRAY: - case Variant::DICTIONARY: - case Variant::PACKED_BYTE_ARRAY: - case Variant::PACKED_COLOR_ARRAY: - case Variant::PACKED_INT32_ARRAY: - case Variant::PACKED_INT64_ARRAY: - case Variant::PACKED_FLOAT32_ARRAY: - case Variant::PACKED_FLOAT64_ARRAY: - case Variant::PACKED_STRING_ARRAY: - case Variant::PACKED_VECTOR2_ARRAY: - case Variant::PACKED_VECTOR3_ARRAY: { - break; - } - default: { - Callable::CallError err; - Variant temp = Variant::construct(base_type.builtin_type, nullptr, 0, err); - - bool valid = false; - Variant res = temp.get(cn->value, &valid); - - if (valid) { - node_type = _type_from_variant(res); - node_type.is_constant = false; - } else if (check_types) { - _set_error("Can't get index \"" + String(cn->value) + "\" on base \"" + - base_type.to_string() + "\".", - op->line); - return DataType(); - } - } break; - } - } else { - _mark_line_as_unsafe(op->line); - } - } else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) { - _set_error("Only strings can be used as an index in the base type \"" + base_type.to_string() + "\".", op->line); - return DataType(); - } - } - if (check_types && !node_type.has_type && base_type.kind == DataType::BUILTIN) { - // Can infer indexing type for some variant types - DataType result; - result.has_type = true; - result.kind = DataType::BUILTIN; - switch (base_type.builtin_type) { - // Can't index at all - case Variant::NIL: - case Variant::BOOL: - case Variant::INT: - case Variant::FLOAT: - case Variant::NODE_PATH: - case Variant::_RID: { - _set_error("Can't index on a value of type \"" + base_type.to_string() + "\".", op->line); - return DataType(); - } break; - // Return int - case Variant::PACKED_BYTE_ARRAY: - case Variant::PACKED_INT32_ARRAY: - case Variant::PACKED_INT64_ARRAY: { - result.builtin_type = Variant::INT; - } break; - // Return real - case Variant::PACKED_FLOAT32_ARRAY: - case Variant::PACKED_FLOAT64_ARRAY: - case Variant::VECTOR2: - case Variant::VECTOR3: - case Variant::QUAT: { - result.builtin_type = Variant::FLOAT; - } break; - // Return color - case Variant::PACKED_COLOR_ARRAY: { - result.builtin_type = Variant::COLOR; - } break; - // Return string - case Variant::PACKED_STRING_ARRAY: - case Variant::STRING: { - result.builtin_type = Variant::STRING; - } break; - // Return Vector2 - case Variant::PACKED_VECTOR2_ARRAY: - case Variant::TRANSFORM2D: - case Variant::RECT2: { - result.builtin_type = Variant::VECTOR2; - } break; - // Return Vector3 - case Variant::PACKED_VECTOR3_ARRAY: - case Variant::AABB: - case Variant::BASIS: { - result.builtin_type = Variant::VECTOR3; - } break; - // Depends on the index - case Variant::TRANSFORM: - case Variant::PLANE: - case Variant::COLOR: - default: { - result.has_type = false; - } break; - } - node_type = result; - } - } break; - default: { - _set_error("Parser bug: unhandled operation.", op->line); - ERR_FAIL_V(DataType()); + Variant value = static_cast<LiteralNode *>(argument)->value; + if (!Variant::can_convert_strict(value.get_type(), parameter.type)) { + push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); + return false; + } + Callable::CallError error; + const Variant *args = &value; + p_annotation->resolved_arguments.push_back(Variant::construct(parameter.type, &(args), 1, error)); + if (error.error != Callable::CallError::CALL_OK) { + push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); + p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1); + return false; } + break; } - } break; - default: { } } - node_type = _resolve_type(node_type, p_node->line); - p_node->set_datatype(node_type); - return node_type; + return true; } -bool GDScriptParser::_get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const { - - r_static = false; - r_default_arg_count = 0; +bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) { + this->_is_tool = true; + return true; +} - DataType original_type = p_base_type; - ClassNode *base = nullptr; - FunctionNode *callee = nullptr; +bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) { + ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); + ClassNode *p_class = static_cast<ClassNode *>(p_node); + p_class->icon_path = p_annotation->resolved_arguments[0]; + return true; +} - if (p_base_type.kind == DataType::CLASS) { - base = p_base_type.class_type; - } +bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) { + ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); - // Look up the current file (parse tree) - while (!callee && base) { - for (int i = 0; i < base->static_functions.size(); i++) { - FunctionNode *func = base->static_functions[i]; - if (p_function == func->name) { - r_static = true; - callee = func; - break; - } - } - if (!callee && !p_base_type.is_meta_type) { - for (int i = 0; i < base->functions.size(); i++) { - FunctionNode *func = base->functions[i]; - if (p_function == func->name) { - callee = func; - break; - } - } - } - p_base_type = base->base_type; - if (p_base_type.kind == DataType::CLASS) { - base = p_base_type.class_type; - } else { - break; - } + VariableNode *variable = static_cast<VariableNode *>(p_node); + if (variable->onready) { + push_error(R"("@onready" annotation can only be used once per variable.)"); + return false; } + variable->onready = true; + current_class->onready_used = true; + return true; +} - if (callee) { - r_return_type = callee->get_datatype(); - for (int i = 0; i < callee->argument_types.size(); i++) { - r_arg_types.push_back(callee->argument_types[i]); - } - r_default_arg_count = callee->default_values.size(); - return true; - } +template <PropertyHint t_hint, Variant::Type t_type> +bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_node) { + ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); - // Nothing in current file, check parent script - Ref<GDScript> base_gdscript; - Ref<Script> base_script; - StringName native; - if (p_base_type.kind == DataType::GDSCRIPT) { - base_gdscript = p_base_type.script_type; - if (base_gdscript.is_null() || !base_gdscript->is_valid()) { - // GDScript wasn't properly compÃled, don't bother trying - return false; - } - } else if (p_base_type.kind == DataType::SCRIPT) { - base_script = p_base_type.script_type; - } else if (p_base_type.kind == DataType::NATIVE) { - native = p_base_type.native_type; + VariableNode *variable = static_cast<VariableNode *>(p_node); + if (variable->exported) { + push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); + return false; } - while (base_gdscript.is_valid()) { - native = base_gdscript->get_instance_base_type(); - - Map<StringName, GDScriptFunction *> funcs = base_gdscript->get_member_functions(); + variable->exported = true; + // TODO: Improving setting type, especially for range hints, which can be int or float. + variable->export_info.type = t_type; + variable->export_info.hint = t_hint; - if (funcs.has(p_function)) { - GDScriptFunction *f = funcs[p_function]; - r_static = f->is_static(); - r_default_arg_count = f->get_default_argument_count(); - r_return_type = _type_from_gdtype(f->get_return_type()); - for (int i = 0; i < f->get_argument_count(); i++) { - r_arg_types.push_back(_type_from_gdtype(f->get_argument_type(i))); + if (p_annotation->name == "@export") { + if (variable->datatype_specifier == nullptr) { + if (variable->initializer == nullptr) { + push_error(R"(Cannot use "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); + return false; } - return true; - } - - base_gdscript = base_gdscript->get_base_script(); - } - - while (base_script.is_valid()) { - native = base_script->get_instance_base_type(); - MethodInfo mi = base_script->get_method_info(p_function); - - if (!(mi == MethodInfo())) { - r_return_type = _type_from_property(mi.return_val, false); - r_default_arg_count = mi.default_arguments.size(); - for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) { - r_arg_types.push_back(_type_from_property(E->get())); + if (variable->initializer->type != Node::LITERAL) { + push_error(R"(To use "@export" annotation with type-less variable, the default value must be a literal.)", p_annotation); + return false; } - return true; - } - base_script = base_script->get_base_script(); + variable->export_info.type = static_cast<LiteralNode *>(variable->initializer)->value.get_type(); + } // else: Actual type will be set by the analyzer, which can infer the proper type. } - if (native == StringName()) { - // Empty native class, might happen in some Script implementations - // Just ignore it - return false; + String hint_string; + for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { + if (i > 0) { + hint_string += ","; + } + hint_string += String(p_annotation->resolved_arguments[i]); } -#ifdef DEBUG_METHODS_ENABLED + variable->export_info.hint_string = hint_string; - // Only native remains - if (!ClassDB::class_exists(native)) { - native = "_" + native.operator String(); - } - if (!ClassDB::class_exists(native)) { - if (!check_types) return false; - ERR_FAIL_V_MSG(false, "Parser bug: Class '" + String(native) + "' not found."); - } + return true; +} - MethodBind *method = ClassDB::get_method(native, p_function); +bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) { + ERR_FAIL_V_MSG(false, "Not implemented."); +} - if (!method) { - // Try virtual methods - List<MethodInfo> virtuals; - ClassDB::get_virtual_methods(native, &virtuals); +template <MultiplayerAPI::RPCMode t_mode> +bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) { + ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name)); - for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) { - const MethodInfo &mi = E->get(); - if (mi.name == p_function) { - r_default_arg_count = mi.default_arguments.size(); - for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) { - r_arg_types.push_back(_type_from_property(pi->get())); - } - r_return_type = _type_from_property(mi.return_val, false); - r_vararg = mi.flags & METHOD_FLAG_VARARG; - return true; + switch (p_node->type) { + case Node::VARIABLE: { + VariableNode *variable = static_cast<VariableNode *>(p_node); + if (variable->rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + push_error(R"(RPC annotations can only be used once per variable.)", p_annotation); } + variable->rpc_mode = t_mode; + break; } - - // If the base is a script, it might be trying to access members of the Script class itself - if (original_type.is_meta_type && !(p_function == "new") && (original_type.kind == DataType::SCRIPT || original_type.kind == DataType::GDSCRIPT)) { - method = ClassDB::get_method(original_type.script_type->get_class_name(), p_function); - - if (method) { - r_static = true; - } else { - // Try virtual methods of the script type - virtuals.clear(); - ClassDB::get_virtual_methods(original_type.script_type->get_class_name(), &virtuals); - for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) { - const MethodInfo &mi = E->get(); - if (mi.name == p_function) { - r_default_arg_count = mi.default_arguments.size(); - for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) { - r_arg_types.push_back(_type_from_property(pi->get())); - } - r_return_type = _type_from_property(mi.return_val, false); - r_static = true; - r_vararg = mi.flags & METHOD_FLAG_VARARG; - return true; - } - } - return false; + case Node::FUNCTION: { + FunctionNode *function = static_cast<FunctionNode *>(p_node); + if (function->rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + push_error(R"(RPC annotations can only be used once per function.)", p_annotation); } - } else { - return false; + function->rpc_mode = t_mode; + break; } + default: + return false; // Unreachable. } - r_default_arg_count = method->get_default_argument_count(); - r_return_type = _type_from_property(method->get_return_info(), false); - r_vararg = method->is_vararg(); - - for (int i = 0; i < method->get_argument_count(); i++) { - r_arg_types.push_back(_type_from_property(method->get_argument_info(i))); - } return true; -#else - return false; -#endif } -GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const OperatorNode *p_call) { - if (p_call->arguments.size() < 1) { - _set_error("Parser bug: function call without enough arguments.", p_call->line); - ERR_FAIL_V(DataType()); - } - - DataType return_type; - List<DataType> arg_types; - int default_args_count = 0; - int arg_count = p_call->arguments.size(); - String callee_name; - bool is_vararg = false; - - switch (p_call->arguments[0]->type) { - case GDScriptParser::Node::TYPE_TYPE: { - // Built-in constructor, special case - TypeNode *tn = static_cast<TypeNode *>(p_call->arguments[0]); - - Vector<DataType> par_types; - par_types.resize(p_call->arguments.size() - 1); - for (int i = 1; i < p_call->arguments.size(); i++) { - par_types.write[i - 1] = _reduce_node_type(p_call->arguments[i]); - } - - if (error_set) return DataType(); - - // Special case: check copy constructor. Those are defined implicitly in Variant. - if (par_types.size() == 1) { - if (!par_types[0].has_type || (par_types[0].kind == DataType::BUILTIN && par_types[0].builtin_type == tn->vtype)) { - DataType result; - result.has_type = true; - result.kind = DataType::BUILTIN; - result.builtin_type = tn->vtype; - return result; - } - } - - bool match = false; - List<MethodInfo> constructors; - Variant::get_constructor_list(tn->vtype, &constructors); - PropertyInfo return_type2; - - for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) { - MethodInfo &mi = E->get(); - - if (p_call->arguments.size() - 1 < mi.arguments.size() - mi.default_arguments.size()) { - continue; - } - if (p_call->arguments.size() - 1 > mi.arguments.size()) { - continue; - } - - bool types_match = true; - for (int i = 0; i < par_types.size(); i++) { - DataType arg_type; - if (mi.arguments[i].type != Variant::NIL) { - arg_type.has_type = true; - arg_type.kind = mi.arguments[i].type == Variant::OBJECT ? DataType::NATIVE : DataType::BUILTIN; - arg_type.builtin_type = mi.arguments[i].type; - arg_type.native_type = mi.arguments[i].class_name; - } - - if (!_is_type_compatible(arg_type, par_types[i], true)) { - types_match = false; - break; - } else { -#ifdef DEBUG_ENABLED - if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_types[i].kind == DataType::BUILTIN && par_types[i].builtin_type == Variant::FLOAT) { - _add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, Variant::get_type_name(tn->vtype)); - } - if (par_types[i].may_yield && p_call->arguments[i + 1]->type == Node::TYPE_OPERATOR) { - _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i + 1]))); - } -#endif // DEBUG_ENABLED - } - } - - if (types_match) { - match = true; - return_type2 = mi.return_val; - break; - } - } - - if (match) { - return _type_from_property(return_type2, false); - } else if (check_types) { - String err = "No constructor of '"; - err += Variant::get_type_name(tn->vtype); - err += "' matches the signature '"; - err += Variant::get_type_name(tn->vtype) + "("; - for (int i = 0; i < par_types.size(); i++) { - if (i > 0) err += ", "; - err += par_types[i].to_string(); - } - err += ")'."; - _set_error(err, p_call->line, p_call->column); - return DataType(); - } +GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const { + switch (type) { + case CONSTANT: + return constant->get_datatype(); + case VARIABLE: + return variable->get_datatype(); + case PARAMETER: + return parameter->get_datatype(); + case FOR_VARIABLE: + case PATTERN_BIND: + return bind->get_datatype(); + case UNDEFINED: return DataType(); - } break; - case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: { - BuiltInFunctionNode *func = static_cast<BuiltInFunctionNode *>(p_call->arguments[0]); - MethodInfo mi = GDScriptFunctions::get_info(func->function); - - return_type = _type_from_property(mi.return_val, false); - - // Check all arguments beforehand to solve warnings - for (int i = 1; i < p_call->arguments.size(); i++) { - _reduce_node_type(p_call->arguments[i]); - } - - // Check arguments - - is_vararg = mi.flags & METHOD_FLAG_VARARG; - - default_args_count = mi.default_arguments.size(); - callee_name = mi.name; - arg_count -= 1; - - // Check each argument type - for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) { - arg_types.push_back(_type_from_property(E->get())); - } - } break; - default: { - if (p_call->op == OperatorNode::OP_CALL && p_call->arguments.size() < 2) { - _set_error("Parser bug: self method call without enough arguments.", p_call->line); - ERR_FAIL_V(DataType()); - } - - int arg_id = p_call->op == OperatorNode::OP_CALL ? 1 : 0; - - if (p_call->arguments[arg_id]->type != Node::TYPE_IDENTIFIER) { - _set_error("Parser bug: invalid function call argument.", p_call->line); - ERR_FAIL_V(DataType()); - } - - // Check all arguments beforehand to solve warnings - for (int i = arg_id + 1; i < p_call->arguments.size(); i++) { - _reduce_node_type(p_call->arguments[i]); - } - - IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]); - callee_name = func_id->name; - arg_count -= 1 + arg_id; - - DataType base_type; - if (p_call->op == OperatorNode::OP_PARENT_CALL) { - base_type = current_class->base_type; - } else { - base_type = _reduce_node_type(p_call->arguments[0]); - } - - if (!base_type.has_type || (base_type.kind == DataType::BUILTIN && base_type.builtin_type == Variant::NIL)) { - _mark_line_as_unsafe(p_call->line); - return DataType(); - } - - if (base_type.kind == DataType::BUILTIN) { - Callable::CallError err; - Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err); - - if (check_types) { - if (!tmp.has_method(callee_name)) { - _set_error("The method \"" + callee_name + "\" isn't declared on base \"" + base_type.to_string() + "\".", p_call->line); - return DataType(); - } - - default_args_count = Variant::get_method_default_arguments(base_type.builtin_type, callee_name).size(); - const Vector<Variant::Type> &var_arg_types = Variant::get_method_argument_types(base_type.builtin_type, callee_name); - - for (int i = 0; i < var_arg_types.size(); i++) { - DataType argtype; - if (var_arg_types[i] != Variant::NIL) { - argtype.has_type = true; - argtype.kind = DataType::BUILTIN; - argtype.builtin_type = var_arg_types[i]; - } - arg_types.push_back(argtype); - } - } - - bool rets = false; - return_type.has_type = true; - return_type.kind = DataType::BUILTIN; - return_type.builtin_type = Variant::get_method_return_type(base_type.builtin_type, callee_name, &rets); - // If the method returns, but it might return any type, (Variant::NIL), pretend we don't know the type. - // At least make sure we know that it returns - if (rets && return_type.builtin_type == Variant::NIL) { - return_type.has_type = false; - } - break; - } - - DataType original_type = base_type; - bool is_initializer = callee_name == "new"; - bool is_static = false; - bool valid = false; - - if (is_initializer && original_type.is_meta_type) { - // Try to check it as initializer - base_type = original_type; - callee_name = "_init"; - base_type.is_meta_type = false; - - valid = _get_function_signature(base_type, callee_name, return_type, arg_types, - default_args_count, is_static, is_vararg); - - return_type = original_type; - return_type.is_meta_type = false; - - valid = true; // There's always an initializer, we can assume this is true - } + } + return DataType(); +} - if (!valid) { - base_type = original_type; - return_type = DataType(); - valid = _get_function_signature(base_type, callee_name, return_type, arg_types, - default_args_count, is_static, is_vararg); - } +String GDScriptParser::SuiteNode::Local::get_name() const { + String name; + switch (type) { + case SuiteNode::Local::PARAMETER: + name = "parameter"; + break; + case SuiteNode::Local::CONSTANT: + name = "constant"; + break; + case SuiteNode::Local::VARIABLE: + name = "variable"; + break; + case SuiteNode::Local::FOR_VARIABLE: + name = "for loop iterator"; + break; + case SuiteNode::Local::PATTERN_BIND: + name = "pattern_bind"; + break; + case SuiteNode::Local::UNDEFINED: + name = "<undefined>"; + break; + } + return name; +} - if (!valid) { -#ifdef DEBUG_ENABLED - if (p_call->arguments[0]->type == Node::TYPE_SELF) { - _set_error("The method \"" + callee_name + "\" isn't declared in the current class.", p_call->line); - return DataType(); - } - DataType tmp_type; - valid = _get_member_type(original_type, func_id->name, tmp_type); - if (valid) { - if (tmp_type.is_constant) { - _add_warning(GDScriptWarning::CONSTANT_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string()); - } else { - _add_warning(GDScriptWarning::PROPERTY_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string()); - } - } - _add_warning(GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->line, callee_name, original_type.to_string()); - _mark_line_as_unsafe(p_call->line); -#endif // DEBUG_ENABLED - return DataType(); +String GDScriptParser::DataType::to_string() const { + switch (kind) { + case VARIANT: + return "Variant"; + case BUILTIN: + if (builtin_type == Variant::NIL) { + return "null"; } - -#ifdef DEBUG_ENABLED - if (current_function && !for_completion && !is_static && p_call->arguments[0]->type == Node::TYPE_SELF && current_function->_static) { - _set_error("Can't call non-static function from a static function.", p_call->line); - return DataType(); + return Variant::get_type_name(builtin_type); + case NATIVE: + if (is_meta_type) { + return GDScriptNativeClass::get_class_static(); } - - if (check_types && !is_static && !is_initializer && base_type.is_meta_type) { - _set_error("Non-static function \"" + String(callee_name) + "\" can only be called from an instance.", p_call->line); - return DataType(); + return native_type.operator String(); + case CLASS: + if (is_meta_type) { + return GDScript::get_class_static(); } - - // Check signal emission for warnings - if (callee_name == "emit_signal" && p_call->op == OperatorNode::OP_CALL && p_call->arguments[0]->type == Node::TYPE_SELF && p_call->arguments.size() >= 3 && p_call->arguments[2]->type == Node::TYPE_CONSTANT) { - ConstantNode *sig = static_cast<ConstantNode *>(p_call->arguments[2]); - String emitted = sig->value.get_type() == Variant::STRING ? sig->value.operator String() : ""; - for (int i = 0; i < current_class->_signals.size(); i++) { - if (current_class->_signals[i].name == emitted) { - current_class->_signals.write[i].emissions += 1; - break; - } - } + if (class_type->identifier != nullptr) { + return class_type->identifier->name.operator String(); } -#endif // DEBUG_ENABLED - } break; - } - -#ifdef DEBUG_ENABLED - if (!check_types) { - return return_type; - } - - if (arg_count < arg_types.size() - default_args_count) { - _set_error("Too few arguments for \"" + callee_name + "()\" call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line); - return return_type; - } - if (!is_vararg && arg_count > arg_types.size()) { - _set_error("Too many arguments for \"" + callee_name + "()\" call. Expected at most " + itos(arg_types.size()) + ".", p_call->line); - return return_type; - } - - int arg_diff = p_call->arguments.size() - arg_count; - for (int i = arg_diff; i < p_call->arguments.size(); i++) { - DataType par_type = _reduce_node_type(p_call->arguments[i]); - - if ((i - arg_diff) >= arg_types.size()) { - continue; - } - - DataType arg_type = arg_types[i - arg_diff]; - - if (!par_type.has_type) { - _mark_line_as_unsafe(p_call->line); - if (par_type.may_yield && p_call->arguments[i]->type == Node::TYPE_OPERATOR) { - _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i]))); + return class_type->fqcn; + case SCRIPT: { + if (is_meta_type) { + return script_type->get_class_name().operator String(); } - } else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) { - // Supertypes are acceptable for dynamic compliance - if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) { - _set_error("At \"" + callee_name + "()\" call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" + - par_type.to_string() + ") doesn't match the function argument's type (" + - arg_types[i - arg_diff].to_string() + ").", - p_call->line); - return DataType(); - } else { - _mark_line_as_unsafe(p_call->line); + String name = script_type->get_name(); + if (!name.empty()) { + return name; } - } else { - if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_type.kind == DataType::BUILTIN && par_type.builtin_type == Variant::FLOAT) { - _add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, callee_name); + name = script_path; + if (!name.empty()) { + return name; } + return native_type.operator String(); } + case ENUM: + return enum_type.operator String() + " (enum)"; + case ENUM_VALUE: + return enum_type.operator String() + " (enum value)"; + case UNRESOLVED: + return "<unresolved type>"; } -#endif // DEBUG_ENABLED - - return return_type; + ERR_FAIL_V_MSG("<unresolved type", "Kind set outside the enum range."); } -bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const { - DataType base_type = p_base_type; - - // Check classes in current file - ClassNode *base = nullptr; - if (base_type.kind == DataType::CLASS) { - base = base_type.class_type; - } - - while (base) { - if (base->constant_expressions.has(p_member)) { - r_member_type = base->constant_expressions[p_member].expression->get_datatype(); - return true; - } +/*---------- PRETTY PRINT FOR DEBUG ----------*/ - if (!base_type.is_meta_type) { - for (int i = 0; i < base->variables.size(); i++) { - if (base->variables[i].identifier == p_member) { - r_member_type = base->variables[i].data_type; - base->variables.write[i].usages += 1; - return true; - } - } - } else { - for (int i = 0; i < base->subclasses.size(); i++) { - ClassNode *c = base->subclasses[i]; - if (c->name == p_member) { - DataType class_type; - class_type.has_type = true; - class_type.is_constant = true; - class_type.is_meta_type = true; - class_type.kind = DataType::CLASS; - class_type.class_type = c; - r_member_type = class_type; - return true; - } - } - } +#ifdef DEBUG_ENABLED - base_type = base->base_type; - if (base_type.kind == DataType::CLASS) { - base = base_type.class_type; +void GDScriptParser::TreePrinter::increase_indent() { + indent_level++; + indent = ""; + for (int i = 0; i < indent_level * 4; i++) { + if (i % 4 == 0) { + indent += "|"; } else { - break; + indent += " "; } } +} - Ref<GDScript> gds; - if (base_type.kind == DataType::GDSCRIPT) { - gds = base_type.script_type; - if (gds.is_null() || !gds->is_valid()) { - // GDScript wasn't properly compÃled, don't bother trying - return false; +void GDScriptParser::TreePrinter::decrease_indent() { + indent_level--; + indent = ""; + for (int i = 0; i < indent_level * 4; i++) { + if (i % 4 == 0) { + indent += "|"; + } else { + indent += " "; } } +} - Ref<Script> scr; - if (base_type.kind == DataType::SCRIPT) { - scr = base_type.script_type; +void GDScriptParser::TreePrinter::push_line(const String &p_line) { + if (!p_line.empty()) { + push_text(p_line); } + printed += "\n"; + pending_indent = true; +} - StringName native; - if (base_type.kind == DataType::NATIVE) { - native = base_type.native_type; +void GDScriptParser::TreePrinter::push_text(const String &p_text) { + if (pending_indent) { + printed += indent; + pending_indent = false; } + printed += p_text; +} - // Check GDScripts - while (gds.is_valid()) { - if (gds->get_constants().has(p_member)) { - Variant c = gds->get_constants()[p_member]; - r_member_type = _type_from_variant(c); - return true; - } - - if (!base_type.is_meta_type) { - if (gds->get_members().has(p_member)) { - r_member_type = _type_from_gdtype(gds->get_member_type(p_member)); - return true; - } - } - - native = gds->get_instance_base_type(); - if (gds->get_base_script().is_valid()) { - gds = gds->get_base_script(); - scr = gds->get_base_script(); - bool is_meta = base_type.is_meta_type; - base_type = _type_from_variant(scr.operator Variant()); - base_type.is_meta_type = is_meta; - } else { - break; +void GDScriptParser::TreePrinter::print_annotation(AnnotationNode *p_annotation) { + push_text(p_annotation->name); + push_text(" ("); + for (int i = 0; i < p_annotation->arguments.size(); i++) { + if (i > 0) { + push_text(" , "); } + print_expression(p_annotation->arguments[i]); } + push_line(")"); +} - // Check other script types - while (scr.is_valid()) { - Map<StringName, Variant> constants; - scr->get_constants(&constants); - if (constants.has(p_member)) { - r_member_type = _type_from_variant(constants[p_member]); - return true; +void GDScriptParser::TreePrinter::print_array(ArrayNode *p_array) { + push_text("[ "); + for (int i = 0; i < p_array->elements.size(); i++) { + if (i > 0) { + push_text(" , "); } + print_expression(p_array->elements[i]); + } + push_text(" ]"); +} - List<PropertyInfo> properties; - scr->get_script_property_list(&properties); - for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { - if (E->get().name == p_member) { - r_member_type = _type_from_property(E->get()); - return true; - } - } +void GDScriptParser::TreePrinter::print_assert(AssertNode *p_assert) { + push_text("Assert ( "); + print_expression(p_assert->condition); + push_line(" )"); +} - base_type = _type_from_variant(scr.operator Variant()); - native = scr->get_instance_base_type(); - scr = scr->get_base_script(); +void GDScriptParser::TreePrinter::print_assignment(AssignmentNode *p_assignment) { + switch (p_assignment->assignee->type) { + case Node::IDENTIFIER: + print_identifier(static_cast<IdentifierNode *>(p_assignment->assignee)); + break; + case Node::SUBSCRIPT: + print_subscript(static_cast<SubscriptNode *>(p_assignment->assignee)); + break; + default: + break; // Unreachable. } - if (native == StringName()) { - // Empty native class, might happen in some Script implementations - // Just ignore it - return false; + push_text(" "); + switch (p_assignment->operation) { + case AssignmentNode::OP_ADDITION: + push_text("+"); + break; + case AssignmentNode::OP_SUBTRACTION: + push_text("-"); + break; + case AssignmentNode::OP_MULTIPLICATION: + push_text("*"); + break; + case AssignmentNode::OP_DIVISION: + push_text("/"); + break; + case AssignmentNode::OP_MODULO: + push_text("%"); + break; + case AssignmentNode::OP_BIT_SHIFT_LEFT: + push_text("<<"); + break; + case AssignmentNode::OP_BIT_SHIFT_RIGHT: + push_text(">>"); + break; + case AssignmentNode::OP_BIT_AND: + push_text("&"); + break; + case AssignmentNode::OP_BIT_OR: + push_text("|"); + break; + case AssignmentNode::OP_BIT_XOR: + push_text("^"); + break; + case AssignmentNode::OP_NONE: + break; } + push_text("= "); + print_expression(p_assignment->assigned_value); + push_line(); +} - // Check ClassDB - if (!ClassDB::class_exists(native)) { - native = "_" + native.operator String(); - } - if (!ClassDB::class_exists(native)) { - if (!check_types) return false; - ERR_FAIL_V_MSG(false, "Parser bug: Class \"" + String(native) + "\" not found."); - } +void GDScriptParser::TreePrinter::print_await(AwaitNode *p_await) { + push_text("Await "); + print_expression(p_await->to_await); +} - bool valid = false; - ClassDB::get_integer_constant(native, p_member, &valid); - if (valid) { - DataType ct; - ct.has_type = true; - ct.is_constant = true; - ct.kind = DataType::BUILTIN; - ct.builtin_type = Variant::INT; - r_member_type = ct; - return true; +void GDScriptParser::TreePrinter::print_binary_op(BinaryOpNode *p_binary_op) { + // Surround in parenthesis for disambiguation. + push_text("("); + print_expression(p_binary_op->left_operand); + switch (p_binary_op->operation) { + case BinaryOpNode::OP_ADDITION: + push_text(" + "); + break; + case BinaryOpNode::OP_SUBTRACTION: + push_text(" - "); + break; + case BinaryOpNode::OP_MULTIPLICATION: + push_text(" * "); + break; + case BinaryOpNode::OP_DIVISION: + push_text(" / "); + break; + case BinaryOpNode::OP_MODULO: + push_text(" % "); + break; + case BinaryOpNode::OP_BIT_LEFT_SHIFT: + push_text(" << "); + break; + case BinaryOpNode::OP_BIT_RIGHT_SHIFT: + push_text(" >> "); + break; + case BinaryOpNode::OP_BIT_AND: + push_text(" & "); + break; + case BinaryOpNode::OP_BIT_OR: + push_text(" | "); + break; + case BinaryOpNode::OP_BIT_XOR: + push_text(" ^ "); + break; + case BinaryOpNode::OP_LOGIC_AND: + push_text(" AND "); + break; + case BinaryOpNode::OP_LOGIC_OR: + push_text(" OR "); + break; + case BinaryOpNode::OP_TYPE_TEST: + push_text(" IS "); + break; + case BinaryOpNode::OP_CONTENT_TEST: + push_text(" IN "); + break; + case BinaryOpNode::OP_COMP_EQUAL: + push_text(" == "); + break; + case BinaryOpNode::OP_COMP_NOT_EQUAL: + push_text(" != "); + break; + case BinaryOpNode::OP_COMP_LESS: + push_text(" < "); + break; + case BinaryOpNode::OP_COMP_LESS_EQUAL: + push_text(" <= "); + break; + case BinaryOpNode::OP_COMP_GREATER: + push_text(" > "); + break; + case BinaryOpNode::OP_COMP_GREATER_EQUAL: + push_text(" >= "); + break; } + print_expression(p_binary_op->right_operand); + // Surround in parenthesis for disambiguation. + push_text(")"); +} - if (!base_type.is_meta_type) { - List<PropertyInfo> properties; - ClassDB::get_property_list(native, &properties); - for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { - if (E->get().name == p_member) { - // Check if a getter exists - StringName getter_name = ClassDB::get_property_getter(native, p_member); - if (getter_name != StringName()) { - // Use the getter return type -#ifdef DEBUG_METHODS_ENABLED - MethodBind *getter_method = ClassDB::get_method(native, getter_name); - if (getter_method) { - r_member_type = _type_from_property(getter_method->get_return_info()); - } else { - r_member_type = DataType(); - } -#else - r_member_type = DataType(); -#endif - } else { - r_member_type = _type_from_property(E->get()); - } - return true; - } +void GDScriptParser::TreePrinter::print_call(CallNode *p_call) { + if (p_call->is_super) { + push_text("super"); + if (p_call->callee != nullptr) { + push_text("."); + print_expression(p_call->callee); } + } else { + print_expression(p_call->callee); } - - // If the base is a script, it might be trying to access members of the Script class itself - if (p_base_type.is_meta_type && (p_base_type.kind == DataType::SCRIPT || p_base_type.kind == DataType::GDSCRIPT)) { - native = p_base_type.script_type->get_class_name(); - ClassDB::get_integer_constant(native, p_member, &valid); - if (valid) { - DataType ct; - ct.has_type = true; - ct.is_constant = true; - ct.kind = DataType::BUILTIN; - ct.builtin_type = Variant::INT; - r_member_type = ct; - return true; - } - - List<PropertyInfo> properties; - ClassDB::get_property_list(native, &properties); - for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { - if (E->get().name == p_member) { - // Check if a getter exists - StringName getter_name = ClassDB::get_property_getter(native, p_member); - if (getter_name != StringName()) { - // Use the getter return type -#ifdef DEBUG_METHODS_ENABLED - MethodBind *getter_method = ClassDB::get_method(native, getter_name); - if (getter_method) { - r_member_type = _type_from_property(getter_method->get_return_info()); - } else { - r_member_type = DataType(); - } -#else - r_member_type = DataType(); -#endif - } else { - r_member_type = _type_from_property(E->get()); - } - return true; - } + push_text("( "); + for (int i = 0; i < p_call->arguments.size(); i++) { + if (i > 0) { + push_text(" , "); } + print_expression(p_call->arguments[i]); } - - return false; + push_text(" )"); } -GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line, bool p_is_indexing) { - - if (p_base_type && !p_base_type->has_type) { - return DataType(); - } - - DataType base_type; - DataType member_type; +void GDScriptParser::TreePrinter::print_cast(CastNode *p_cast) { + print_expression(p_cast->operand); + push_text(" AS "); + print_type(p_cast->cast_type); +} - if (!p_base_type) { - base_type.has_type = true; - base_type.is_constant = true; - base_type.kind = DataType::CLASS; - base_type.class_type = current_class; +void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) { + push_text("Class "); + if (p_class->identifier == nullptr) { + push_text("<unnamed>"); } else { - base_type = DataType(*p_base_type); - } - - if (_get_member_type(base_type, p_identifier, member_type)) { - return member_type; + print_identifier(p_class->identifier); } - if (p_is_indexing) { - // Don't look for globals since this is an indexed identifier - return DataType(); - } - - if (!p_base_type) { - // Possibly this is a global, check before failing - - if (ClassDB::class_exists(p_identifier) || ClassDB::class_exists("_" + p_identifier.operator String())) { - DataType result; - result.has_type = true; - result.is_constant = true; - result.is_meta_type = true; - if (Engine::get_singleton()->has_singleton(p_identifier) || Engine::get_singleton()->has_singleton("_" + p_identifier.operator String())) { - result.is_meta_type = false; - } - result.kind = DataType::NATIVE; - result.native_type = p_identifier; - return result; - } - - ClassNode *outer_class = current_class; - while (outer_class) { - if (outer_class->name == p_identifier) { - DataType result; - result.has_type = true; - result.is_constant = true; - result.is_meta_type = true; - result.kind = DataType::CLASS; - result.class_type = outer_class; - return result; - } - if (outer_class->constant_expressions.has(p_identifier)) { - return outer_class->constant_expressions[p_identifier].type; - } - for (int i = 0; i < outer_class->subclasses.size(); i++) { - if (outer_class->subclasses[i] == current_class) { - continue; - } - if (outer_class->subclasses[i]->name == p_identifier) { - DataType result; - result.has_type = true; - result.is_constant = true; - result.is_meta_type = true; - result.kind = DataType::CLASS; - result.class_type = outer_class->subclasses[i]; - return result; - } - } - outer_class = outer_class->owner; - } - - if (ScriptServer::is_global_class(p_identifier)) { - Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier)); - if (scr.is_valid()) { - DataType result; - result.has_type = true; - result.script_type = scr; - result.is_constant = true; - result.is_meta_type = true; - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - if (!gds->is_valid()) { - _set_error("The class \"" + p_identifier + "\" couldn't be fully loaded (script error or cyclic dependency)."); - return DataType(); - } - result.kind = DataType::GDSCRIPT; - } else { - result.kind = DataType::SCRIPT; - } - return result; + if (p_class->extends_used) { + bool first = true; + push_text(" Extends "); + if (!p_class->extends_path.empty()) { + push_text(vformat(R"("%s")", p_class->extends_path)); + first = false; + } + for (int i = 0; i < p_class->extends.size(); i++) { + if (!first) { + push_text("."); + } else { + first = false; } - _set_error("The class \"" + p_identifier + "\" was found in global scope, but its script couldn't be loaded."); - return DataType(); - } - - if (GDScriptLanguage::get_singleton()->get_global_map().has(p_identifier)) { - int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier]; - Variant g = GDScriptLanguage::get_singleton()->get_global_array()[idx]; - return _type_from_variant(g); - } - - if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) { - Variant g = GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]; - return _type_from_variant(g); + push_text(p_class->extends[i]); } + } - // Non-tool singletons aren't loaded, check project settings - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); + push_line(" :"); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) { - continue; - } - String name = s.get_slice("/", 1); - if (name == p_identifier) { - String script = ProjectSettings::get_singleton()->get(s); - if (script.begins_with("*")) { - script = script.right(1); - } - if (!script.begins_with("res://")) { - script = "res://" + script; - } - Ref<Script> singleton = ResourceLoader::load(script); - if (singleton.is_valid()) { - DataType result; - result.has_type = true; - result.is_constant = true; - result.script_type = singleton; - - Ref<GDScript> gds = singleton; - if (gds.is_valid()) { - if (!gds->is_valid()) { - _set_error("Couldn't fully load the singleton script \"" + p_identifier + "\" (possible cyclic reference or parse error).", p_line); - return DataType(); - } - result.kind = DataType::GDSCRIPT; - } else { - result.kind = DataType::SCRIPT; - } - } - } - } + increase_indent(); - // This means looking in the current class, which type is always known - _set_error("The identifier \"" + p_identifier.operator String() + "\" isn't declared in the current scope.", p_line); - } + for (int i = 0; i < p_class->members.size(); i++) { + const ClassNode::Member &m = p_class->members[i]; -#ifdef DEBUG_ENABLED - { - DataType tmp_type; - List<DataType> arg_types; - int argcount; - bool _static; - bool vararg; - if (_get_function_signature(base_type, p_identifier, tmp_type, arg_types, argcount, _static, vararg)) { - _add_warning(GDScriptWarning::FUNCTION_USED_AS_PROPERTY, p_line, p_identifier.operator String(), base_type.to_string()); + switch (m.type) { + case ClassNode::Member::CLASS: + print_class(m.m_class); + break; + case ClassNode::Member::VARIABLE: + print_variable(m.variable); + break; + case ClassNode::Member::CONSTANT: + print_constant(m.constant); + break; + case ClassNode::Member::SIGNAL: + print_signal(m.signal); + break; + case ClassNode::Member::FUNCTION: + print_function(m.function); + break; + case ClassNode::Member::ENUM: + print_enum(m.m_enum); + break; + case ClassNode::Member::ENUM_VALUE: + break; // Nothing. Will be printed by enum. + case ClassNode::Member::UNDEFINED: + push_line("<unknown member>"); + break; } } -#endif // DEBUG_ENABLED - _mark_line_as_unsafe(p_line); - return DataType(); + decrease_indent(); } -void GDScriptParser::_check_class_level_types(ClassNode *p_class) { - - // Names of internal object properties that we check to avoid overriding them. - // "__meta__" could also be in here, but since it doesn't really affect object metadata, - // it is okay to override it on script. - StringName script_name = CoreStringNames::get_singleton()->_script; - - _mark_line_as_safe(p_class->line); +void GDScriptParser::TreePrinter::print_constant(ConstantNode *p_constant) { + push_text("Constant "); + print_identifier(p_constant->identifier); - // Constants - for (Map<StringName, ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { - ClassNode::Constant &c = E->get(); - _mark_line_as_safe(c.expression->line); - DataType cont = _resolve_type(c.type, c.expression->line); - DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line); + increase_indent(); - if (check_types && !_is_type_compatible(cont, expr)) { - _set_error("The constant value type (" + expr.to_string() + ") isn't compatible with declared type (" + cont.to_string() + ").", - c.expression->line); - return; - } - - expr.is_constant = true; - c.type = expr; - c.expression->set_datatype(expr); + push_line(); + push_text("= "); + if (p_constant->initializer == nullptr) { + push_text("<missing value>"); + } else { + print_expression(p_constant->initializer); + } + decrease_indent(); + push_line(); +} - DataType tmp; - const StringName &constant_name = E->key(); - if (constant_name == script_name || _get_member_type(p_class->base_type, constant_name, tmp)) { - _set_error("The member \"" + String(constant_name) + "\" already exists in a parent class.", c.expression->line); - return; +void GDScriptParser::TreePrinter::print_dictionary(DictionaryNode *p_dictionary) { + push_line("{"); + increase_indent(); + for (int i = 0; i < p_dictionary->elements.size(); i++) { + print_expression(p_dictionary->elements[i].key); + if (p_dictionary->style == DictionaryNode::PYTHON_DICT) { + push_text(" : "); + } else { + push_text(" = "); } + print_expression(p_dictionary->elements[i].value); + push_line(" ,"); } + decrease_indent(); + push_text("}"); +} - // Function declarations - for (int i = 0; i < p_class->static_functions.size(); i++) { - _check_function_types(p_class->static_functions[i]); - if (error_set) return; +void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) { + switch (p_expression->type) { + case Node::ARRAY: + print_array(static_cast<ArrayNode *>(p_expression)); + break; + case Node::ASSIGNMENT: + print_assignment(static_cast<AssignmentNode *>(p_expression)); + break; + case Node::AWAIT: + print_await(static_cast<AwaitNode *>(p_expression)); + break; + case Node::BINARY_OPERATOR: + print_binary_op(static_cast<BinaryOpNode *>(p_expression)); + break; + case Node::CALL: + print_call(static_cast<CallNode *>(p_expression)); + break; + case Node::CAST: + print_cast(static_cast<CastNode *>(p_expression)); + break; + case Node::DICTIONARY: + print_dictionary(static_cast<DictionaryNode *>(p_expression)); + break; + case Node::GET_NODE: + print_get_node(static_cast<GetNodeNode *>(p_expression)); + break; + case Node::IDENTIFIER: + print_identifier(static_cast<IdentifierNode *>(p_expression)); + break; + case Node::LITERAL: + print_literal(static_cast<LiteralNode *>(p_expression)); + break; + case Node::PRELOAD: + print_preload(static_cast<PreloadNode *>(p_expression)); + break; + case Node::SELF: + print_self(static_cast<SelfNode *>(p_expression)); + break; + case Node::SUBSCRIPT: + print_subscript(static_cast<SubscriptNode *>(p_expression)); + break; + case Node::TERNARY_OPERATOR: + print_ternary_op(static_cast<TernaryOpNode *>(p_expression)); + break; + case Node::UNARY_OPERATOR: + print_unary_op(static_cast<UnaryOpNode *>(p_expression)); + break; + default: + push_text(vformat("<unknown expression %d>", p_expression->type)); + break; } +} - for (int i = 0; i < p_class->functions.size(); i++) { - _check_function_types(p_class->functions[i]); - if (error_set) return; +void GDScriptParser::TreePrinter::print_enum(EnumNode *p_enum) { + push_text("Enum "); + if (p_enum->identifier != nullptr) { + print_identifier(p_enum->identifier); + } else { + push_text("<unnamed>"); } - // Class variables - for (int i = 0; i < p_class->variables.size(); i++) { - ClassNode::Member &v = p_class->variables.write[i]; - - DataType tmp; - if (v.identifier == script_name || _get_member_type(p_class->base_type, v.identifier, tmp)) { - _set_error("The member \"" + String(v.identifier) + "\" already exists in a parent class.", v.line); - return; - } - - _mark_line_as_safe(v.line); - v.data_type = _resolve_type(v.data_type, v.line); - - if (v.expression) { - DataType expr_type = _reduce_node_type(v.expression); - - if (check_types && !_is_type_compatible(v.data_type, expr_type)) { - // Try supertype test - if (_is_type_compatible(expr_type, v.data_type)) { - _mark_line_as_unsafe(v.line); - } else { - // Try with implicit conversion - if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) { - _set_error("The assigned expression's type (" + expr_type.to_string() + ") doesn't match the variable's type (" + - v.data_type.to_string() + ").", - v.line); - return; - } - - // Replace assignment with implicit conversion - BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); - convert->line = v.line; - convert->function = GDScriptFunctions::TYPE_CONVERT; - - ConstantNode *tgt_type = alloc_node<ConstantNode>(); - tgt_type->line = v.line; - tgt_type->value = (int)v.data_type.builtin_type; - - OperatorNode *convert_call = alloc_node<OperatorNode>(); - convert_call->line = v.line; - convert_call->op = OperatorNode::OP_CALL; - convert_call->arguments.push_back(convert); - convert_call->arguments.push_back(v.expression); - convert_call->arguments.push_back(tgt_type); - - v.expression = convert_call; - v.initial_assignment->arguments.write[1] = convert_call; - } - } - - if (v.data_type.infer_type) { - if (!expr_type.has_type) { - _set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", v.line); - return; - } - v.data_type = expr_type; - v.data_type.is_constant = false; - } - } - - // Check export hint - if (v.data_type.has_type && v._export.type != Variant::NIL) { - DataType export_type = _type_from_property(v._export); - if (!_is_type_compatible(v.data_type, export_type, true)) { - _set_error("The export hint's type (" + export_type.to_string() + ") doesn't match the variable's type (" + - v.data_type.to_string() + ").", - v.line); - return; - } - } + push_line(" {"); + increase_indent(); + for (int i = 0; i < p_enum->values.size(); i++) { + const EnumNode::Value &item = p_enum->values[i]; + print_identifier(item.identifier); + push_text(" = "); + push_text(itos(item.value)); + push_line(" ,"); + } + decrease_indent(); + push_line("}"); +} - // Setter and getter - if (v.setter == StringName() && v.getter == StringName()) continue; - - bool found_getter = false; - bool found_setter = false; - for (int j = 0; j < p_class->functions.size(); j++) { - if (v.setter == p_class->functions[j]->name) { - found_setter = true; - FunctionNode *setter = p_class->functions[j]; - - if (setter->get_required_argument_count() != 1 && - !(setter->get_required_argument_count() == 0 && setter->default_values.size() > 0)) { - _set_error("The setter function needs to receive exactly 1 argument. See \"" + setter->name + - "()\" definition at line " + itos(setter->line) + ".", - v.line); - return; - } - if (!_is_type_compatible(v.data_type, setter->argument_types[0])) { - _set_error("The setter argument's type (" + setter->argument_types[0].to_string() + - ") doesn't match the variable's type (" + v.data_type.to_string() + "). See \"" + - setter->name + "()\" definition at line " + itos(setter->line) + ".", - v.line); - return; - } - continue; - } - if (v.getter == p_class->functions[j]->name) { - found_getter = true; - FunctionNode *getter = p_class->functions[j]; - - if (getter->get_required_argument_count() != 0) { - _set_error("The getter function can't receive arguments. See \"" + getter->name + - "()\" definition at line " + itos(getter->line) + ".", - v.line); - return; - } - if (!_is_type_compatible(v.data_type, getter->get_datatype())) { - _set_error("The getter return type (" + getter->get_datatype().to_string() + - ") doesn't match the variable's type (" + v.data_type.to_string() + - "). See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", - v.line); - return; - } - } - if (found_getter && found_setter) break; - } +void GDScriptParser::TreePrinter::print_for(ForNode *p_for) { + push_text("For "); + print_identifier(p_for->variable); + push_text(" IN "); + print_expression(p_for->list); + push_line(" :"); - if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) continue; + increase_indent(); - // Check for static functions - for (int j = 0; j < p_class->static_functions.size(); j++) { - if (v.setter == p_class->static_functions[j]->name) { - FunctionNode *setter = p_class->static_functions[j]; - _set_error("The setter can't be a static function. See \"" + setter->name + "()\" definition at line " + itos(setter->line) + ".", v.line); - return; - } - if (v.getter == p_class->static_functions[j]->name) { - FunctionNode *getter = p_class->static_functions[j]; - _set_error("The getter can't be a static function. See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", v.line); - return; - } - } + print_suite(p_for->loop); - if (!found_setter && v.setter != StringName()) { - _set_error("The setter function isn't defined.", v.line); - return; - } + decrease_indent(); +} - if (!found_getter && v.getter != StringName()) { - _set_error("The getter function isn't defined.", v.line); - return; - } +void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function) { + for (const List<AnnotationNode *>::Element *E = p_function->annotations.front(); E != nullptr; E = E->next()) { + print_annotation(E->get()); } - - // Inner classes - for (int i = 0; i < p_class->subclasses.size(); i++) { - current_class = p_class->subclasses[i]; - _check_class_level_types(current_class); - if (error_set) return; - current_class = p_class; + push_text("Function "); + print_identifier(p_function->identifier); + push_text("( "); + for (int i = 0; i < p_function->parameters.size(); i++) { + if (i > 0) { + push_text(" , "); + } + print_parameter(p_function->parameters[i]); } + push_line(" ) :"); + increase_indent(); + print_suite(p_function->body); + decrease_indent(); } -void GDScriptParser::_check_function_types(FunctionNode *p_function) { - - p_function->return_type = _resolve_type(p_function->return_type, p_function->line); - - // Arguments - int defaults_ofs = p_function->arguments.size() - p_function->default_values.size(); - for (int i = 0; i < p_function->arguments.size(); i++) { - if (i < defaults_ofs) { - p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line); - } else { - if (p_function->default_values[i - defaults_ofs]->type != Node::TYPE_OPERATOR) { - _set_error("Parser bug: invalid argument default value.", p_function->line, p_function->column); - return; - } - - OperatorNode *op = static_cast<OperatorNode *>(p_function->default_values[i - defaults_ofs]); - - if (op->op != OperatorNode::OP_ASSIGN || op->arguments.size() != 2) { - _set_error("Parser bug: invalid argument default value operation.", p_function->line); - return; - } - - DataType def_type = _reduce_node_type(op->arguments[1]); - - if (p_function->argument_types[i].infer_type) { - def_type.is_constant = false; - p_function->argument_types.write[i] = def_type; - } else { - p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line); - - if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) { - String arg_name = p_function->arguments[i]; - _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" + - arg_name + "' (" + p_function->argument_types[i].to_string() + ").", - p_function->line); - } - } - } -#ifdef DEBUG_ENABLED - if (p_function->arguments_usage[i] == 0 && !p_function->arguments[i].operator String().begins_with("_")) { - _add_warning(GDScriptWarning::UNUSED_ARGUMENT, p_function->line, p_function->name, p_function->arguments[i].operator String()); - } - for (int j = 0; j < current_class->variables.size(); j++) { - if (current_class->variables[j].identifier == p_function->arguments[i]) { - _add_warning(GDScriptWarning::SHADOWED_VARIABLE, p_function->line, p_function->arguments[i], itos(current_class->variables[j].line)); +void GDScriptParser::TreePrinter::print_get_node(GetNodeNode *p_get_node) { + push_text("$"); + if (p_get_node->string != nullptr) { + print_literal(p_get_node->string); + } else { + for (int i = 0; i < p_get_node->chain.size(); i++) { + if (i > 0) { + push_text("/"); } + print_identifier(p_get_node->chain[i]); } -#endif // DEBUG_ENABLED } +} - if (!(p_function->name == "_init")) { - // Signature for the initializer may vary -#ifdef DEBUG_ENABLED - DataType return_type; - List<DataType> arg_types; - int default_arg_count = 0; - bool _static = false; - bool vararg = false; - - DataType base_type = current_class->base_type; - if (_get_function_signature(base_type, p_function->name, return_type, arg_types, default_arg_count, _static, vararg)) { - bool valid = _static == p_function->_static; - valid = valid && return_type == p_function->return_type; - int argsize_diff = p_function->arguments.size() - arg_types.size(); - valid = valid && argsize_diff >= 0; - valid = valid && p_function->default_values.size() >= default_arg_count + argsize_diff; - int i = 0; - for (List<DataType>::Element *E = arg_types.front(); valid && E; E = E->next()) { - valid = valid && E->get() == p_function->argument_types[i++]; - } - - if (!valid) { - String parent_signature = return_type.has_type ? return_type.to_string() : "Variant"; - if (parent_signature == "null") { - parent_signature = "void"; - } - parent_signature += " " + p_function->name + "("; - if (arg_types.size()) { - int j = 0; - for (List<DataType>::Element *E = arg_types.front(); E; E = E->next()) { - if (E != arg_types.front()) { - parent_signature += ", "; - } - String arg = E->get().to_string(); - if (arg == "null" || arg == "var") { - arg = "Variant"; - } - parent_signature += arg; - if (j == arg_types.size() - default_arg_count) { - parent_signature += "=default"; - } +void GDScriptParser::TreePrinter::print_identifier(IdentifierNode *p_identifier) { + push_text(p_identifier->name); +} - j++; - } - } - parent_signature += ")"; - _set_error("The function signature doesn't match the parent. Parent signature is: \"" + parent_signature + "\".", p_function->line); - return; - } - } -#endif // DEBUG_ENABLED +void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) { + if (p_is_elif) { + push_text("Elif "); } else { - if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) { - _set_error("The constructor can't return a value.", p_function->line); - return; - } + push_text("If "); } + print_expression(p_if->condition); + push_line(" :"); - if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) { - if (!p_function->body->has_return) { - _set_error("A non-void function must return a value in all possible paths.", p_function->line); - return; - } - } + increase_indent(); + print_suite(p_if->true_block); + decrease_indent(); - if (p_function->has_yield) { - // yield() will make the function return a GDScriptFunctionState, so the type is ambiguous - p_function->return_type.has_type = false; - p_function->return_type.may_yield = true; + // FIXME: Properly detect "elif" blocks. + if (p_if->false_block != nullptr) { + push_line("Else :"); + increase_indent(); + print_suite(p_if->false_block); + decrease_indent(); } - -#ifdef DEBUG_ENABLED - for (Map<StringName, LocalVarNode *>::Element *E = p_function->body->variables.front(); E; E = E->next()) { - LocalVarNode *lv = E->get(); - for (int i = 0; i < current_class->variables.size(); i++) { - if (current_class->variables[i].identifier == lv->name) { - _add_warning(GDScriptWarning::SHADOWED_VARIABLE, lv->line, lv->name, itos(current_class->variables[i].line)); - } - } - } -#endif // DEBUG_ENABLED } -void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) { - - // Function blocks - for (int i = 0; i < p_class->static_functions.size(); i++) { - current_function = p_class->static_functions[i]; - current_block = current_function->body; - _mark_line_as_safe(current_function->line); - _check_block_types(current_block); - current_block = nullptr; - current_function = nullptr; - if (error_set) return; +void GDScriptParser::TreePrinter::print_literal(LiteralNode *p_literal) { + // Prefix for string types. + switch (p_literal->value.get_type()) { + case Variant::NODE_PATH: + push_text("^\""); + break; + case Variant::STRING: + push_text("\""); + break; + case Variant::STRING_NAME: + push_text("&\""); + break; + default: + break; } - - for (int i = 0; i < p_class->functions.size(); i++) { - current_function = p_class->functions[i]; - current_block = current_function->body; - _mark_line_as_safe(current_function->line); - _check_block_types(current_block); - current_block = nullptr; - current_function = nullptr; - if (error_set) return; + push_text(p_literal->value); + // Suffix for string types. + switch (p_literal->value.get_type()) { + case Variant::NODE_PATH: + case Variant::STRING: + case Variant::STRING_NAME: + push_text("\""); + break; + default: + break; } +} -#ifdef DEBUG_ENABLED - // Warnings - for (int i = 0; i < p_class->variables.size(); i++) { - if (p_class->variables[i].usages == 0) { - _add_warning(GDScriptWarning::UNUSED_CLASS_VARIABLE, p_class->variables[i].line, p_class->variables[i].identifier); - } - } - for (int i = 0; i < p_class->_signals.size(); i++) { - if (p_class->_signals[i].emissions == 0) { - _add_warning(GDScriptWarning::UNUSED_SIGNAL, p_class->_signals[i].line, p_class->_signals[i].name); - } - } -#endif // DEBUG_ENABLED +void GDScriptParser::TreePrinter::print_match(MatchNode *p_match) { + push_text("Match "); + print_expression(p_match->test); + push_line(" :"); - // Inner classes - for (int i = 0; i < p_class->subclasses.size(); i++) { - current_class = p_class->subclasses[i]; - _check_class_blocks_types(current_class); - if (error_set) return; - current_class = p_class; + increase_indent(); + for (int i = 0; i < p_match->branches.size(); i++) { + print_match_branch(p_match->branches[i]); } + decrease_indent(); } -#ifdef DEBUG_ENABLED -static String _find_function_name(const GDScriptParser::OperatorNode *p_call) { - switch (p_call->arguments[0]->type) { - case GDScriptParser::Node::TYPE_TYPE: { - return Variant::get_type_name(static_cast<GDScriptParser::TypeNode *>(p_call->arguments[0])->vtype); - } break; - case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: { - return GDScriptFunctions::get_func_name(static_cast<GDScriptParser::BuiltInFunctionNode *>(p_call->arguments[0])->function); - } break; - default: { - int id_index = p_call->op == GDScriptParser::OperatorNode::OP_PARENT_CALL ? 0 : 1; - if (p_call->arguments.size() > id_index && p_call->arguments[id_index]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - return static_cast<GDScriptParser::IdentifierNode *>(p_call->arguments[id_index])->name; - } - } break; +void GDScriptParser::TreePrinter::print_match_branch(MatchBranchNode *p_match_branch) { + for (int i = 0; i < p_match_branch->patterns.size(); i++) { + if (i > 0) { + push_text(" , "); + } + print_match_pattern(p_match_branch->patterns[i]); } - return String(); -} -#endif // DEBUG_ENABLED - -void GDScriptParser::_check_block_types(BlockNode *p_block) { - - Node *last_var_assign = nullptr; - - // Check each statement - for (List<Node *>::Element *E = p_block->statements.front(); E; E = E->next()) { - Node *statement = E->get(); - switch (statement->type) { - case Node::TYPE_NEWLINE: - case Node::TYPE_BREAKPOINT: - case Node::TYPE_ASSERT: { - // Nothing to do - } break; - case Node::TYPE_LOCAL_VAR: { - LocalVarNode *lv = static_cast<LocalVarNode *>(statement); - lv->datatype = _resolve_type(lv->datatype, lv->line); - _mark_line_as_safe(lv->line); - - last_var_assign = lv->assign; - if (lv->assign) { - lv->assign_op->arguments[0]->set_datatype(lv->datatype); - DataType assign_type = _reduce_node_type(lv->assign); -#ifdef DEBUG_ENABLED - if (assign_type.has_type && assign_type.kind == DataType::BUILTIN && assign_type.builtin_type == Variant::NIL) { - if (lv->assign->type == Node::TYPE_OPERATOR) { - OperatorNode *call = static_cast<OperatorNode *>(lv->assign); - if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) { - _add_warning(GDScriptWarning::VOID_ASSIGNMENT, lv->line, _find_function_name(call)); - } - } - } - if (lv->datatype.has_type && assign_type.may_yield && lv->assign->type == Node::TYPE_OPERATOR) { - _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, lv->line, _find_function_name(static_cast<OperatorNode *>(lv->assign))); - } -#endif // DEBUG_ENABLED - - if (!_is_type_compatible(lv->datatype, assign_type)) { - // Try supertype test - if (_is_type_compatible(assign_type, lv->datatype)) { - _mark_line_as_unsafe(lv->line); - } else { - // Try implicit conversion - if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) { - _set_error("The assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" + - lv->datatype.to_string() + ").", - lv->line); - return; - } - // Replace assignment with implicit conversion - BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); - convert->line = lv->line; - convert->function = GDScriptFunctions::TYPE_CONVERT; - - ConstantNode *tgt_type = alloc_node<ConstantNode>(); - tgt_type->line = lv->line; - tgt_type->value = (int)lv->datatype.builtin_type; - - OperatorNode *convert_call = alloc_node<OperatorNode>(); - convert_call->line = lv->line; - convert_call->op = OperatorNode::OP_CALL; - convert_call->arguments.push_back(convert); - convert_call->arguments.push_back(lv->assign); - convert_call->arguments.push_back(tgt_type); - - lv->assign = convert_call; - lv->assign_op->arguments.write[1] = convert_call; -#ifdef DEBUG_ENABLED - if (lv->datatype.builtin_type == Variant::INT && assign_type.builtin_type == Variant::FLOAT) { - _add_warning(GDScriptWarning::NARROWING_CONVERSION, lv->line); - } -#endif // DEBUG_ENABLED - } - } - if (lv->datatype.infer_type) { - if (!assign_type.has_type) { - _set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", lv->line); - return; - } - lv->datatype = assign_type; - lv->datatype.is_constant = false; - } - if (lv->datatype.has_type && !assign_type.has_type) { - _mark_line_as_unsafe(lv->line); - } - } - } break; - case Node::TYPE_OPERATOR: { - OperatorNode *op = static_cast<OperatorNode *>(statement); - - switch (op->op) { - case OperatorNode::OP_ASSIGN: - case OperatorNode::OP_ASSIGN_ADD: - case OperatorNode::OP_ASSIGN_SUB: - case OperatorNode::OP_ASSIGN_MUL: - case OperatorNode::OP_ASSIGN_DIV: - case OperatorNode::OP_ASSIGN_MOD: - case OperatorNode::OP_ASSIGN_SHIFT_LEFT: - case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: - case OperatorNode::OP_ASSIGN_BIT_AND: - case OperatorNode::OP_ASSIGN_BIT_OR: - case OperatorNode::OP_ASSIGN_BIT_XOR: { - if (op->arguments.size() < 2) { - _set_error("Parser bug: operation without enough arguments.", op->line, op->column); - return; - } - - if (op->arguments[1] == last_var_assign) { - // Assignment was already checked - break; - } - - _mark_line_as_safe(op->line); - - DataType lh_type = _reduce_node_type(op->arguments[0]); - - if (error_set) { - return; - } - - if (check_types) { - if (!lh_type.has_type) { - if (op->arguments[0]->type == Node::TYPE_OPERATOR) { - _mark_line_as_unsafe(op->line); - } - } - if (lh_type.is_constant) { - _set_error("Can't assign a new value to a constant.", op->line); - return; - } - } - - DataType rh_type; - if (op->op != OperatorNode::OP_ASSIGN) { - // Validate operation - DataType arg_type = _reduce_node_type(op->arguments[1]); - if (!arg_type.has_type) { - _mark_line_as_unsafe(op->line); - break; - } - - Variant::Operator oper = _get_variant_operation(op->op); - bool valid = false; - rh_type = _get_operation_type(oper, lh_type, arg_type, valid); - if (check_types && !valid) { - _set_error("Invalid operand types (\"" + lh_type.to_string() + "\" and \"" + arg_type.to_string() + - "\") to assignment operator \"" + Variant::get_operator_name(oper) + "\".", - op->line); - return; - } - } else { - rh_type = _reduce_node_type(op->arguments[1]); - } -#ifdef DEBUG_ENABLED - if (rh_type.has_type && rh_type.kind == DataType::BUILTIN && rh_type.builtin_type == Variant::NIL) { - if (op->arguments[1]->type == Node::TYPE_OPERATOR) { - OperatorNode *call = static_cast<OperatorNode *>(op->arguments[1]); - if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) { - _add_warning(GDScriptWarning::VOID_ASSIGNMENT, op->line, _find_function_name(call)); - } - } - } - if (lh_type.has_type && rh_type.may_yield && op->arguments[1]->type == Node::TYPE_OPERATOR) { - _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, op->line, _find_function_name(static_cast<OperatorNode *>(op->arguments[1]))); - } + push_line(" :"); -#endif // DEBUG_ENABLED - bool type_match = lh_type.has_type && rh_type.has_type; - if (check_types && !_is_type_compatible(lh_type, rh_type)) { - type_match = false; + increase_indent(); + print_suite(p_match_branch->block); + decrease_indent(); +} - // Try supertype test - if (_is_type_compatible(rh_type, lh_type)) { - _mark_line_as_unsafe(op->line); - } else { - // Try implicit conversion - if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) { - _set_error("The assigned value's type (" + rh_type.to_string() + ") doesn't match the variable's type (" + - lh_type.to_string() + ").", - op->line); - return; - } - if (op->op == OperatorNode::OP_ASSIGN) { - // Replace assignment with implicit conversion - BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); - convert->line = op->line; - convert->function = GDScriptFunctions::TYPE_CONVERT; - - ConstantNode *tgt_type = alloc_node<ConstantNode>(); - tgt_type->line = op->line; - tgt_type->value = (int)lh_type.builtin_type; - - OperatorNode *convert_call = alloc_node<OperatorNode>(); - convert_call->line = op->line; - convert_call->op = OperatorNode::OP_CALL; - convert_call->arguments.push_back(convert); - convert_call->arguments.push_back(op->arguments[1]); - convert_call->arguments.push_back(tgt_type); - - op->arguments.write[1] = convert_call; - - type_match = true; // Since we are converting, the type is matching - } -#ifdef DEBUG_ENABLED - if (lh_type.builtin_type == Variant::INT && rh_type.builtin_type == Variant::FLOAT) { - _add_warning(GDScriptWarning::NARROWING_CONVERSION, op->line); - } -#endif // DEBUG_ENABLED - } - } -#ifdef DEBUG_ENABLED - if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) { - _mark_line_as_unsafe(op->line); - } -#endif // DEBUG_ENABLED - op->datatype.has_type = type_match; - } break; - case OperatorNode::OP_CALL: - case OperatorNode::OP_PARENT_CALL: { - _mark_line_as_safe(op->line); - DataType func_type = _reduce_function_call_type(op); -#ifdef DEBUG_ENABLED - if (func_type.has_type && (func_type.kind != DataType::BUILTIN || func_type.builtin_type != Variant::NIL)) { - // Figure out function name for warning - String func_name = _find_function_name(op); - if (func_name.empty()) { - func_name = "<undetected name>"; - } - _add_warning(GDScriptWarning::RETURN_VALUE_DISCARDED, op->line, func_name); - } -#endif // DEBUG_ENABLED - if (error_set) return; - } break; - case OperatorNode::OP_YIELD: { - _mark_line_as_safe(op->line); - _reduce_node_type(op); - } break; - default: { - _mark_line_as_safe(op->line); - _reduce_node_type(op); // Test for safety anyway -#ifdef DEBUG_ENABLED - if (op->op == OperatorNode::OP_TERNARY_IF) { - _add_warning(GDScriptWarning::STANDALONE_TERNARY, statement->line); - } else { - _add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line); - } -#endif // DEBUG_ENABLED - } +void GDScriptParser::TreePrinter::print_match_pattern(PatternNode *p_match_pattern) { + switch (p_match_pattern->pattern_type) { + case PatternNode::PT_LITERAL: + print_literal(p_match_pattern->literal); + break; + case PatternNode::PT_WILDCARD: + push_text("_"); + break; + case PatternNode::PT_REST: + push_text(".."); + break; + case PatternNode::PT_BIND: + push_text("Var "); + print_identifier(p_match_pattern->bind); + break; + case PatternNode::PT_EXPRESSION: + print_expression(p_match_pattern->expression); + break; + case PatternNode::PT_ARRAY: + push_text("[ "); + for (int i = 0; i < p_match_pattern->array.size(); i++) { + if (i > 0) { + push_text(" , "); } - } break; - case Node::TYPE_CONTROL_FLOW: { - ControlFlowNode *cf = static_cast<ControlFlowNode *>(statement); - _mark_line_as_safe(cf->line); - - switch (cf->cf_type) { - case ControlFlowNode::CF_RETURN: { - DataType function_type = current_function->get_datatype(); - - DataType ret_type; - if (cf->arguments.size() > 0) { - ret_type = _reduce_node_type(cf->arguments[0]); - if (error_set) { - return; - } - } - - if (!function_type.has_type) break; - - if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) { - // Return void, should not have arguments - if (cf->arguments.size() > 0) { - _set_error("A void function cannot return a value.", cf->line, cf->column); - return; - } - } else { - // Return something, cannot be empty - if (cf->arguments.size() == 0) { - _set_error("A non-void function must return a value.", cf->line, cf->column); - return; - } - - if (!_is_type_compatible(function_type, ret_type)) { - _set_error("The returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" + - function_type.to_string() + ").", - cf->line, cf->column); - return; - } - } - } break; - case ControlFlowNode::CF_MATCH: { - MatchNode *match_node = cf->match; - _transform_match_statment(match_node); - } break; - default: { - if (cf->body_else) { - _mark_line_as_safe(cf->body_else->line); - } - for (int i = 0; i < cf->arguments.size(); i++) { - _reduce_node_type(cf->arguments[i]); - } - } break; + print_match_pattern(p_match_pattern->array[i]); + } + push_text(" ]"); + break; + case PatternNode::PT_DICTIONARY: + push_text("{ "); + for (int i = 0; i < p_match_pattern->dictionary.size(); i++) { + if (i > 0) { + push_text(" , "); } - } break; - case Node::TYPE_CONSTANT: { - ConstantNode *cn = static_cast<ConstantNode *>(statement); - // Strings are fine since they can be multiline comments - if (cn->value.get_type() == Variant::STRING) { - break; + if (p_match_pattern->dictionary[i].key != nullptr) { + // Key can be null for rest pattern. + print_expression(p_match_pattern->dictionary[i].key); + push_text(" : "); } - [[fallthrough]]; + print_match_pattern(p_match_pattern->dictionary[i].value_pattern); } - default: { - _mark_line_as_safe(statement->line); - _reduce_node_type(statement); // Test for safety anyway -#ifdef DEBUG_ENABLED - _add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line); -#endif // DEBUG_ENABLED - } - } + push_text(" }"); + break; } +} - // Parse sub blocks - for (int i = 0; i < p_block->sub_blocks.size(); i++) { - current_block = p_block->sub_blocks[i]; - _check_block_types(current_block); - current_block = p_block; - if (error_set) return; +void GDScriptParser::TreePrinter::print_parameter(ParameterNode *p_parameter) { + print_identifier(p_parameter->identifier); + if (p_parameter->datatype_specifier != nullptr) { + push_text(" : "); + print_type(p_parameter->datatype_specifier); } - -#ifdef DEBUG_ENABLED - // Warnings check - for (Map<StringName, LocalVarNode *>::Element *E = p_block->variables.front(); E; E = E->next()) { - LocalVarNode *lv = E->get(); - if (!lv->name.operator String().begins_with("_")) { - if (lv->usages == 0) { - _add_warning(GDScriptWarning::UNUSED_VARIABLE, lv->line, lv->name); - } else if (lv->assignments == 0) { - _add_warning(GDScriptWarning::UNASSIGNED_VARIABLE, lv->line, lv->name); - } - } + if (p_parameter->default_value != nullptr) { + push_text(" = "); + print_expression(p_parameter->default_value); } -#endif // DEBUG_ENABLED } -void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column) { - - if (error_set) - return; //allow no further errors - - error = p_error; - error_line = p_line < 0 ? tokenizer->get_token_line() : p_line; - error_column = p_column < 0 ? tokenizer->get_token_column() : p_column; - error_set = true; +void GDScriptParser::TreePrinter::print_preload(PreloadNode *p_preload) { + push_text(R"(Preload ( ")"); + push_text(p_preload->resolved_path); + push_text(R"(" )"); } -#ifdef DEBUG_ENABLED -void GDScriptParser::_add_warning(int p_code, int p_line, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) { - Vector<String> symbols; - if (!p_symbol1.empty()) { - symbols.push_back(p_symbol1); - } - if (!p_symbol2.empty()) { - symbols.push_back(p_symbol2); - } - if (!p_symbol3.empty()) { - symbols.push_back(p_symbol3); - } - if (!p_symbol4.empty()) { - symbols.push_back(p_symbol4); +void GDScriptParser::TreePrinter::print_return(ReturnNode *p_return) { + push_text("Return"); + if (p_return->return_value != nullptr) { + push_text(" "); + print_expression(p_return->return_value); } - _add_warning(p_code, p_line, symbols); + push_line(); } -void GDScriptParser::_add_warning(int p_code, int p_line, const Vector<String> &p_symbols) { - if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && base_path.begins_with("res://addons/")) { - return; - } - if (tokenizer->is_ignoring_warnings() || !GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) { - return; - } - String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower(); - if (tokenizer->get_warning_global_skips().has(warn_name)) { - return; - } - if (!GLOBAL_GET("debug/gdscript/warnings/" + warn_name)) { - return; - } - - GDScriptWarning warn; - warn.code = (GDScriptWarning::Code)p_code; - warn.symbols = p_symbols; - warn.line = p_line == -1 ? tokenizer->get_token_line() : p_line; - - List<GDScriptWarning>::Element *before = nullptr; - for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) { - if (E->get().line > warn.line) { - break; - } - before = E; - } - if (before) { - warnings.insert_after(before, warn); +void GDScriptParser::TreePrinter::print_self(SelfNode *p_self) { + push_text("Self("); + if (p_self->current_class->identifier != nullptr) { + print_identifier(p_self->current_class->identifier); } else { - warnings.push_front(warn); + push_text("<main class>"); } + push_text(")"); } -#endif // DEBUG_ENABLED - -String GDScriptParser::get_error() const { - - return error; -} - -int GDScriptParser::get_error_line() const { - return error_line; +void GDScriptParser::TreePrinter::print_signal(SignalNode *p_signal) { + push_text("Signal "); + print_identifier(p_signal->identifier); + push_text("( "); + for (int i = 0; i < p_signal->parameters.size(); i++) { + print_parameter(p_signal->parameters[i]); + } + push_line(" )"); } -int GDScriptParser::get_error_column() const { - return error_column; +void GDScriptParser::TreePrinter::print_subscript(SubscriptNode *p_subscript) { + print_expression(p_subscript->base); + if (p_subscript->is_attribute) { + push_text("."); + print_identifier(p_subscript->attribute); + } else { + push_text("[ "); + print_expression(p_subscript->index); + push_text(" ]"); + } } -bool GDScriptParser::has_error() const { - return error_set; +void GDScriptParser::TreePrinter::print_statement(Node *p_statement) { + switch (p_statement->type) { + case Node::ASSERT: + print_assert(static_cast<AssertNode *>(p_statement)); + break; + case Node::VARIABLE: + print_variable(static_cast<VariableNode *>(p_statement)); + break; + case Node::CONSTANT: + print_constant(static_cast<ConstantNode *>(p_statement)); + break; + case Node::IF: + print_if(static_cast<IfNode *>(p_statement)); + break; + case Node::FOR: + print_for(static_cast<ForNode *>(p_statement)); + break; + case Node::WHILE: + print_while(static_cast<WhileNode *>(p_statement)); + break; + case Node::MATCH: + print_match(static_cast<MatchNode *>(p_statement)); + break; + case Node::RETURN: + print_return(static_cast<ReturnNode *>(p_statement)); + break; + case Node::BREAK: + push_line("Break"); + break; + case Node::CONTINUE: + push_line("Continue"); + break; + case Node::PASS: + push_line("Pass"); + break; + case Node::BREAKPOINT: + push_line("Breakpoint"); + break; + case Node::ASSIGNMENT: + print_assignment(static_cast<AssignmentNode *>(p_statement)); + break; + default: + if (p_statement->is_expression()) { + print_expression(static_cast<ExpressionNode *>(p_statement)); + push_line(); + } else { + push_line(vformat("<unknown statement %d>", p_statement->type)); + } + break; + } } -Error GDScriptParser::_parse(const String &p_base_path) { - - base_path = p_base_path; - - //assume class - ClassNode *main_class = alloc_node<ClassNode>(); - main_class->initializer = alloc_node<BlockNode>(); - main_class->initializer->parent_class = main_class; - main_class->ready = alloc_node<BlockNode>(); - main_class->ready->parent_class = main_class; - current_class = main_class; - - _parse_class(main_class); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_ERROR) { - error_set = false; - _set_error("Parse error: " + tokenizer->get_token_error()); +void GDScriptParser::TreePrinter::print_suite(SuiteNode *p_suite) { + for (int i = 0; i < p_suite->statements.size(); i++) { + print_statement(p_suite->statements[i]); } +} - if (error_set && !for_completion) { - return ERR_PARSE_ERROR; - } +void GDScriptParser::TreePrinter::print_ternary_op(TernaryOpNode *p_ternary_op) { + // Surround in parenthesis for disambiguation. + push_text("("); + print_expression(p_ternary_op->true_expr); + push_text(") IF ("); + print_expression(p_ternary_op->condition); + push_text(") ELSE ("); + print_expression(p_ternary_op->false_expr); + push_text(")"); +} - if (dependencies_only) { - return OK; +void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) { + if (p_type->type_chain.empty()) { + push_text("Void"); + } else { + for (int i = 0; i < p_type->type_chain.size(); i++) { + if (i > 0) { + push_text("."); + } + print_identifier(p_type->type_chain[i]); + } } +} - _determine_inheritance(main_class); - - if (error_set) { - return ERR_PARSE_ERROR; +void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) { + // Surround in parenthesis for disambiguation. + push_text("("); + switch (p_unary_op->operation) { + case UnaryOpNode::OP_POSITIVE: + push_text("+"); + break; + case UnaryOpNode::OP_NEGATIVE: + push_text("-"); + break; + case UnaryOpNode::OP_LOGIC_NOT: + push_text("NOT"); + break; + case UnaryOpNode::OP_COMPLEMENT: + push_text("~"); + break; } + print_expression(p_unary_op->operand); + // Surround in parenthesis for disambiguation. + push_text(")"); +} - current_class = main_class; - current_function = nullptr; - current_block = nullptr; - - if (for_completion) check_types = false; - - // Resolve all class-level stuff before getting into function blocks - _check_class_level_types(main_class); - - if (error_set) { - return ERR_PARSE_ERROR; +void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) { + for (const List<AnnotationNode *>::Element *E = p_variable->annotations.front(); E != nullptr; E = E->next()) { + print_annotation(E->get()); } - // Resolve the function blocks - _check_class_blocks_types(main_class); + push_text("Variable "); + print_identifier(p_variable->identifier); - if (error_set) { - return ERR_PARSE_ERROR; + push_text(" : "); + if (p_variable->datatype_specifier != nullptr) { + print_type(p_variable->datatype_specifier); + } else if (p_variable->infer_datatype) { + push_text("<inferred type>"); + } else { + push_text("Variant"); } -#ifdef DEBUG_ENABLED + increase_indent(); - // Resolve warning ignores - Vector<Pair<int, String>> warning_skips = tokenizer->get_warning_skips(); - bool warning_is_error = GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors").booleanize(); - for (List<GDScriptWarning>::Element *E = warnings.front(); E;) { - GDScriptWarning &w = E->get(); - int skip_index = -1; - for (int i = 0; i < warning_skips.size(); i++) { - if (warning_skips[i].first >= w.line) { - break; - } - skip_index = i; - } - List<GDScriptWarning>::Element *next = E->next(); - bool erase = false; - if (skip_index != -1) { - if (warning_skips[skip_index].second == GDScriptWarning::get_name_from_code(w.code).to_lower()) { - erase = true; + push_line(); + push_text("= "); + if (p_variable->initializer == nullptr) { + push_text("<default value>"); + } else { + print_expression(p_variable->initializer); + } + push_line(); + + if (p_variable->property != VariableNode::PROP_NONE) { + if (p_variable->getter != nullptr) { + push_text("Get"); + if (p_variable->property == VariableNode::PROP_INLINE) { + push_line(":"); + increase_indent(); + print_suite(p_variable->getter); + decrease_indent(); + } else { + push_line(" ="); + increase_indent(); + print_identifier(p_variable->getter_pointer); + push_line(); + decrease_indent(); + } + } + if (p_variable->setter != nullptr) { + push_text("Set ("); + if (p_variable->property == VariableNode::PROP_INLINE) { + if (p_variable->setter_parameter != nullptr) { + print_identifier(p_variable->setter_parameter); + } else { + push_text("<missing>"); + } + push_line("):"); + increase_indent(); + print_suite(p_variable->setter); + decrease_indent(); + } else { + push_line(" ="); + increase_indent(); + print_identifier(p_variable->setter_pointer); + push_line(); + decrease_indent(); } - warning_skips.remove(skip_index); } - if (erase) { - warnings.erase(E); - } else if (warning_is_error) { - _set_error(w.get_message() + " (warning treated as error)", w.line); - return ERR_PARSE_ERROR; - } - E = next; } -#endif // DEBUG_ENABLED - - return OK; -} - -Error GDScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path, const String &p_self_path) { - - clear(); - - self_path = p_self_path; - GDScriptTokenizerBuffer *tb = memnew(GDScriptTokenizerBuffer); - tb->set_code_buffer(p_bytecode); - tokenizer = tb; - Error ret = _parse(p_base_path); - memdelete(tb); - tokenizer = nullptr; - return ret; -} - -Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion, Set<int> *r_safe_lines, bool p_dependencies_only) { - - clear(); - - self_path = p_self_path; - GDScriptTokenizerText *tt = memnew(GDScriptTokenizerText); - tt->set_code(p_code); - - validating = p_just_validate; - for_completion = p_for_completion; - dependencies_only = p_dependencies_only; -#ifdef DEBUG_ENABLED - safe_lines = r_safe_lines; -#endif // DEBUG_ENABLED - tokenizer = tt; - Error ret = _parse(p_base_path); - memdelete(tt); - tokenizer = nullptr; - return ret; -} - -bool GDScriptParser::is_tool_script() const { - return (head && head->type == Node::TYPE_CLASS && static_cast<const ClassNode *>(head)->tool); + decrease_indent(); + push_line(); } -const GDScriptParser::Node *GDScriptParser::get_parse_tree() const { +void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) { + push_text("While "); + print_expression(p_while->condition); + push_line(" :"); - return head; + increase_indent(); + print_suite(p_while->loop); + decrease_indent(); } -void GDScriptParser::clear() { - - while (list) { +void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) { + ERR_FAIL_COND_MSG(p_parser.get_tree() == nullptr, "Parse the code before printing the parse tree."); - Node *l = list; - list = list->next; - memdelete(l); + if (p_parser.is_tool()) { + push_line("@tool"); } + if (!p_parser.get_tree()->icon_path.empty()) { + push_text(R"(@icon (")"); + push_text(p_parser.get_tree()->icon_path); + push_line("\")"); + } + print_class(p_parser.get_tree()); - head = nullptr; - list = nullptr; - - completion_type = COMPLETION_NONE; - completion_node = nullptr; - completion_class = nullptr; - completion_function = nullptr; - completion_block = nullptr; - current_block = nullptr; - current_class = nullptr; - - completion_found = false; - rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; - - current_function = nullptr; - - validating = false; - for_completion = false; - error_set = false; - indent_level.clear(); - indent_level.push_back(IndentLevel(0, 0)); - error_line = 0; - error_column = 0; - pending_newline = -1; - parenthesis = 0; - current_export.type = Variant::NIL; - check_types = true; - dependencies_only = false; - dependencies.clear(); - error = ""; -#ifdef DEBUG_ENABLED - safe_lines = nullptr; -#endif // DEBUG_ENABLED -} - -GDScriptParser::CompletionType GDScriptParser::get_completion_type() { - - return completion_type; -} - -StringName GDScriptParser::get_completion_cursor() { - - return completion_cursor; -} - -int GDScriptParser::get_completion_line() { - - return completion_line; -} - -Variant::Type GDScriptParser::get_completion_built_in_constant() { - - return completion_built_in_constant; -} - -GDScriptParser::Node *GDScriptParser::get_completion_node() { - - return completion_node; -} - -GDScriptParser::BlockNode *GDScriptParser::get_completion_block() { - - return completion_block; -} - -GDScriptParser::ClassNode *GDScriptParser::get_completion_class() { - - return completion_class; -} - -GDScriptParser::FunctionNode *GDScriptParser::get_completion_function() { - - return completion_function; -} - -int GDScriptParser::get_completion_argument_index() { - - return completion_argument; -} - -int GDScriptParser::get_completion_identifier_is_function() { - - return completion_ident_is_call; -} - -GDScriptParser::GDScriptParser() { - - head = nullptr; - list = nullptr; - tokenizer = nullptr; - pending_newline = -1; - clear(); + print_line(printed); } -GDScriptParser::~GDScriptParser() { - - clear(); -} +#endif // DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index eca5f83f7a..edfe330c0c 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -31,664 +31,1319 @@ #ifndef GDSCRIPT_PARSER_H #define GDSCRIPT_PARSER_H +#include "core/hash_map.h" +#include "core/io/multiplayer_api.h" +#include "core/list.h" #include "core/map.h" -#include "core/object.h" +#include "core/reference.h" +#include "core/resource.h" #include "core/script_language.h" +#include "core/string_name.h" +#include "core/ustring.h" +#include "core/variant.h" +#include "core/vector.h" +#include "gdscript_cache.h" #include "gdscript_functions.h" #include "gdscript_tokenizer.h" -struct GDScriptDataType; -struct GDScriptWarning; +#ifdef DEBUG_ENABLED +#include "core/string_builder.h" +#include "gdscript_warning.h" +#endif // DEBUG_ENABLED class GDScriptParser { + struct AnnotationInfo; + public: + // Forward-declare all parser nodes, to avoid ordering issues. + struct AnnotationNode; + struct ArrayNode; + struct AssertNode; + struct AssignmentNode; + struct AwaitNode; + struct BinaryOpNode; + struct BreakNode; + struct BreakpointNode; + struct CallNode; + struct CastNode; struct ClassNode; + struct ConstantNode; + struct ContinueNode; + struct DictionaryNode; + struct EnumNode; + struct ExpressionNode; + struct ForNode; + struct FunctionNode; + struct GetNodeNode; + struct IdentifierNode; + struct IfNode; + struct LiteralNode; + struct MatchNode; + struct MatchBranchNode; + struct ParameterNode; + struct PassNode; + struct PatternNode; + struct PreloadNode; + struct ReturnNode; + struct SelfNode; + struct SignalNode; + struct SubscriptNode; + struct SuiteNode; + struct TernaryOpNode; + struct TypeNode; + struct UnaryOpNode; + struct VariableNode; + struct WhileNode; struct DataType { - enum { + enum Kind { BUILTIN, NATIVE, SCRIPT, - GDSCRIPT, - CLASS, - UNRESOLVED - } kind; + CLASS, // GDScript. + ENUM, // Full enumeration. + ENUM_VALUE, // Value from enumeration. + VARIANT, // Can be any type. + UNRESOLVED, + // TODO: Enum + }; + Kind kind = UNRESOLVED; + + enum TypeSource { + UNDETECTED, // Can be any type. + INFERRED, // Has inferred type, but still dynamic. + ANNOTATED_EXPLICIT, // Has a specific type annotated. + ANNOTATED_INFERRED, // Has a static type but comes from the assigned value. + }; + TypeSource type_source = UNDETECTED; - bool has_type; - bool is_constant; - bool is_meta_type; // Whether the value can be used as a type - bool infer_type; - bool may_yield; // For function calls + bool is_constant = false; + bool is_meta_type = false; + bool is_coroutine = false; // For function calls. - Variant::Type builtin_type; + Variant::Type builtin_type = Variant::NIL; StringName native_type; + StringName enum_type; // Enum name or the value name in an enum. Ref<Script> script_type; - ClassNode *class_type; + String script_path; + ClassNode *class_type = nullptr; + MethodInfo method_info; // For callable/signals. + HashMap<StringName, int> enum_values; // For enums. + + _FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; } + _FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; } + _FORCE_INLINE_ bool is_variant() const { return kind == VARIANT || kind == UNRESOLVED; } + _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } String to_string() const; - bool operator==(const DataType &other) const { - if (!has_type || !other.has_type) { - return true; // Can be considered equal for parsing purpose + bool operator==(const DataType &p_other) const { + if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) { + return true; // Can be consireded equal for parsing purposes. + } + + if (type_source == INFERRED || p_other.type_source == INFERRED) { + return true; // Can be consireded equal for parsing purposes. } - if (kind != other.kind) { + + if (kind != p_other.kind) { return false; } + switch (kind) { - case BUILTIN: { - return builtin_type == other.builtin_type; - } break; - case NATIVE: { - return native_type == other.native_type; - } break; - case GDSCRIPT: - case SCRIPT: { - return script_type == other.script_type; - } break; - case CLASS: { - return class_type == other.class_type; - } break; - case UNRESOLVED: { - } break; + case VARIANT: + return true; // All variants are the same. + case BUILTIN: + return builtin_type == p_other.builtin_type; + case NATIVE: + case ENUM: + return native_type == p_other.native_type; + case ENUM_VALUE: + return native_type == p_other.native_type && enum_type == p_other.enum_type; + case SCRIPT: + return script_type == p_other.script_type; + case CLASS: + return class_type == p_other.class_type; + case UNRESOLVED: + break; } + return false; } - DataType() : - kind(UNRESOLVED), - has_type(false), - is_constant(false), - is_meta_type(false), - infer_type(false), - may_yield(false), - builtin_type(Variant::NIL), - class_type(nullptr) {} + bool operator!=(const DataType &p_other) const { + return !(this->operator==(p_other)); + } }; - struct Node { + struct ParserError { + // TODO: Do I really need a "type"? + // enum Type { + // NO_ERROR, + // EMPTY_FILE, + // CLASS_NAME_USED_TWICE, + // EXTENDS_USED_TWICE, + // EXPECTED_END_STATEMENT, + // }; + // Type type = NO_ERROR; + String message; + int line = 0, column = 0; + }; + struct Node { enum Type { - TYPE_CLASS, - TYPE_FUNCTION, - TYPE_BUILT_IN_FUNCTION, - TYPE_BLOCK, - TYPE_IDENTIFIER, - TYPE_TYPE, - TYPE_CONSTANT, - TYPE_ARRAY, - TYPE_DICTIONARY, - TYPE_SELF, - TYPE_OPERATOR, - TYPE_CONTROL_FLOW, - TYPE_LOCAL_VAR, - TYPE_CAST, - TYPE_ASSERT, - TYPE_BREAKPOINT, - TYPE_NEWLINE, + NONE, + ANNOTATION, + ARRAY, + ASSERT, + ASSIGNMENT, + AWAIT, + BINARY_OPERATOR, + BREAK, + BREAKPOINT, + CALL, + CAST, + CLASS, + CONSTANT, + CONTINUE, + DICTIONARY, + ENUM, + FOR, + FUNCTION, + GET_NODE, + IDENTIFIER, + IF, + LITERAL, + MATCH, + MATCH_BRANCH, + PARAMETER, + PASS, + PATTERN, + PRELOAD, + RETURN, + SELF, + SIGNAL, + SUBSCRIPT, + SUITE, + TERNARY_OPERATOR, + TYPE, + UNARY_OPERATOR, + VARIABLE, + WHILE, }; - Node *next; - int line; - int column; - Type type; + Type type = NONE; + int start_line = 0, end_line = 0; + int start_column = 0, end_column = 0; + int leftmost_column = 0, rightmost_column = 0; + Node *next = nullptr; + List<AnnotationNode *> annotations; - virtual DataType get_datatype() const { return DataType(); } - virtual void set_datatype(const DataType &p_datatype) {} + DataType datatype; + + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } + + virtual bool is_expression() const { return false; } virtual ~Node() {} }; - struct FunctionNode; - struct BlockNode; - struct ConstantNode; - struct LocalVarNode; - struct OperatorNode; + struct ExpressionNode : public Node { + // Base type for all expression kinds. + bool reduced = false; + bool is_constant = false; + Variant reduced_value; - struct ClassNode : public Node { + virtual bool is_expression() const { return true; } + virtual ~ExpressionNode() {} + + protected: + ExpressionNode() {} + }; - bool tool; + struct AnnotationNode : public Node { StringName name; - bool extends_used; - bool classname_used; - StringName extends_file; - Vector<StringName> extends_class; - DataType base_type; - String icon_path; + Vector<ExpressionNode *> arguments; + Vector<Variant> resolved_arguments; - struct Member { - PropertyInfo _export; -#ifdef TOOLS_ENABLED - Variant default_value; -#endif - StringName identifier; - DataType data_type; - StringName setter; - StringName getter; - int line; - Node *expression; - OperatorNode *initial_assignment; - MultiplayerAPI::RPCMode rpc_mode; - int usages; - }; + AnnotationInfo *info = nullptr; - struct Constant { - Node *expression; - DataType type; - }; + bool apply(GDScriptParser *p_this, Node *p_target) const; + bool applies_to(uint32_t p_target_kinds) const; - struct Signal { - StringName name; - Vector<StringName> arguments; - int emissions; - int line; - }; + AnnotationNode() { + type = ANNOTATION; + } + }; - Vector<ClassNode *> subclasses; - Vector<Member> variables; - Map<StringName, Constant> constant_expressions; - Vector<FunctionNode *> functions; - Vector<FunctionNode *> static_functions; - Vector<Signal> _signals; - BlockNode *initializer; - BlockNode *ready; - ClassNode *owner; - //Vector<Node*> initializers; - int end_line; + struct ArrayNode : public ExpressionNode { + Vector<ExpressionNode *> elements; - ClassNode() { - tool = false; - type = TYPE_CLASS; - extends_used = false; - classname_used = false; - end_line = -1; - owner = nullptr; + ArrayNode() { + type = ARRAY; } }; - struct FunctionNode : public Node { + struct AssertNode : public Node { + ExpressionNode *condition = nullptr; + LiteralNode *message = nullptr; - bool _static; - MultiplayerAPI::RPCMode rpc_mode; - bool has_yield; - bool has_unreachable_code; - StringName name; - DataType return_type; - Vector<StringName> arguments; - Vector<DataType> argument_types; - Vector<Node *> default_values; - BlockNode *body; -#ifdef DEBUG_ENABLED - Vector<int> arguments_usage; -#endif // DEBUG_ENABLED + AssertNode() { + type = ASSERT; + } + }; - virtual DataType get_datatype() const { return return_type; } - virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; } - int get_required_argument_count() { return arguments.size() - default_values.size(); } + struct AssignmentNode : public ExpressionNode { + // Assignment is not really an expression but it's easier to parse as if it were. + enum Operation { + OP_NONE, + OP_ADDITION, + OP_SUBTRACTION, + OP_MULTIPLICATION, + OP_DIVISION, + OP_MODULO, + OP_BIT_SHIFT_LEFT, + OP_BIT_SHIFT_RIGHT, + OP_BIT_AND, + OP_BIT_OR, + OP_BIT_XOR, + }; - FunctionNode() { - type = TYPE_FUNCTION; - _static = false; - rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; - has_yield = false; - has_unreachable_code = false; + Operation operation = OP_NONE; + Variant::Operator variant_op = Variant::OP_MAX; + ExpressionNode *assignee = nullptr; + ExpressionNode *assigned_value = nullptr; + + AssignmentNode() { + type = ASSIGNMENT; } }; - struct BlockNode : public Node { + struct AwaitNode : public ExpressionNode { + ExpressionNode *to_await = nullptr; - ClassNode *parent_class; - BlockNode *parent_block; - List<Node *> statements; - Map<StringName, LocalVarNode *> variables; - bool has_return; + AwaitNode() { + type = AWAIT; + } + }; - Node *if_condition; //tiny hack to improve code completion on if () blocks + struct BinaryOpNode : public ExpressionNode { + enum OpType { + OP_ADDITION, + OP_SUBTRACTION, + OP_MULTIPLICATION, + OP_DIVISION, + OP_MODULO, + OP_BIT_LEFT_SHIFT, + OP_BIT_RIGHT_SHIFT, + OP_BIT_AND, + OP_BIT_OR, + OP_BIT_XOR, + OP_LOGIC_AND, + OP_LOGIC_OR, + OP_TYPE_TEST, + OP_CONTENT_TEST, + OP_COMP_EQUAL, + OP_COMP_NOT_EQUAL, + OP_COMP_LESS, + OP_COMP_LESS_EQUAL, + OP_COMP_GREATER, + OP_COMP_GREATER_EQUAL, + }; - //the following is useful for code completion - List<BlockNode *> sub_blocks; - int end_line; - BlockNode() { - if_condition = nullptr; - type = TYPE_BLOCK; - end_line = -1; - parent_block = nullptr; - parent_class = nullptr; - has_return = false; + OpType operation; + Variant::Operator variant_op = Variant::OP_MAX; + ExpressionNode *left_operand = nullptr; + ExpressionNode *right_operand = nullptr; + + BinaryOpNode() { + type = BINARY_OPERATOR; } }; - struct TypeNode : public Node { - - Variant::Type vtype; - TypeNode() { type = TYPE_TYPE; } + struct BreakNode : public Node { + BreakNode() { + type = BREAK; + } }; - struct BuiltInFunctionNode : public Node { - GDScriptFunctions::Function function; - BuiltInFunctionNode() { type = TYPE_BUILT_IN_FUNCTION; } + + struct BreakpointNode : public Node { + BreakpointNode() { + type = BREAKPOINT; + } }; - struct IdentifierNode : public Node { + struct CallNode : public ExpressionNode { + ExpressionNode *callee = nullptr; + Vector<ExpressionNode *> arguments; + StringName function_name; + bool is_super = false; - StringName name; - BlockNode *declared_block; // Simplify lookup by checking if it is declared locally - DataType datatype; - virtual DataType get_datatype() const { return datatype; } - virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } - IdentifierNode() { - type = TYPE_IDENTIFIER; - declared_block = nullptr; + CallNode() { + type = CALL; + } + + Type get_callee_type() const { + if (callee == nullptr) { + return Type::NONE; + } else { + return callee->type; + } } }; - struct LocalVarNode : public Node { + struct CastNode : public ExpressionNode { + ExpressionNode *operand = nullptr; + TypeNode *cast_type = nullptr; - StringName name; - Node *assign; - OperatorNode *assign_op; - int assignments; - int usages; - DataType datatype; - virtual DataType get_datatype() const { return datatype; } - virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } - LocalVarNode() { - type = TYPE_LOCAL_VAR; - assign = nullptr; - assign_op = nullptr; - assignments = 0; - usages = 0; + CastNode() { + type = CAST; } }; - struct ConstantNode : public Node { - Variant value; - DataType datatype; - virtual DataType get_datatype() const { return datatype; } - virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } - ConstantNode() { type = TYPE_CONSTANT; } + struct EnumNode : public Node { + struct Value { + IdentifierNode *identifier = nullptr; + LiteralNode *custom_value = nullptr; + int value = 0; + int line = 0; + int leftmost_column = 0; + int rightmost_column = 0; + }; + IdentifierNode *identifier = nullptr; + Vector<Value> values; + + EnumNode() { + type = ENUM; + } }; - struct ArrayNode : public Node { + struct ClassNode : public Node { + struct Member { + enum Type { + UNDEFINED, + CLASS, + CONSTANT, + FUNCTION, + SIGNAL, + VARIABLE, + ENUM, + ENUM_VALUE, // For unnamed enums. + }; + + Type type = UNDEFINED; + + union { + ClassNode *m_class = nullptr; + ConstantNode *constant; + FunctionNode *function; + SignalNode *signal; + VariableNode *variable; + EnumNode *m_enum; + }; + EnumNode::Value enum_value; + + String get_type_name() const { + switch (type) { + case UNDEFINED: + return "???"; + case CLASS: + return "class"; + case CONSTANT: + return "constant"; + case FUNCTION: + return "function"; + case SIGNAL: + return "signal"; + case VARIABLE: + return "variable"; + case ENUM: + return "enum"; + case ENUM_VALUE: + return "enum value"; + } + return ""; + } + + int get_line() const { + switch (type) { + case CLASS: + return m_class->start_line; + case CONSTANT: + return constant->start_line; + case FUNCTION: + return function->start_line; + case VARIABLE: + return variable->start_line; + case ENUM_VALUE: + return enum_value.line; + case ENUM: + return m_enum->start_line; + case SIGNAL: + return signal->start_line; + case UNDEFINED: + ERR_FAIL_V_MSG(-1, "Reaching undefined member type."); + } + ERR_FAIL_V_MSG(-1, "Reaching unhandled type."); + } - Vector<Node *> elements; - DataType datatype; - virtual DataType get_datatype() const { return datatype; } - virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } - ArrayNode() { - type = TYPE_ARRAY; - datatype.has_type = true; - datatype.kind = DataType::BUILTIN; - datatype.builtin_type = Variant::ARRAY; + DataType get_datatype() const { + switch (type) { + case CLASS: + return m_class->get_datatype(); + case CONSTANT: + return constant->get_datatype(); + case FUNCTION: + return function->get_datatype(); + case VARIABLE: + return variable->get_datatype(); + case ENUM: + return m_enum->get_datatype(); + case ENUM_VALUE: { + // Always integer. + DataType type; + type.type_source = DataType::ANNOTATED_EXPLICIT; + type.kind = DataType::BUILTIN; + type.builtin_type = Variant::INT; + return type; + } + case SIGNAL: { + DataType type; + type.type_source = DataType::ANNOTATED_EXPLICIT; + type.kind = DataType::BUILTIN; + type.builtin_type = Variant::SIGNAL; + // TODO: Add parameter info. + return type; + } + case UNDEFINED: + return DataType(); + } + ERR_FAIL_V_MSG(DataType(), "Reaching unhandled type."); + } + + Member() {} + + Member(ClassNode *p_class) { + type = CLASS; + m_class = p_class; + } + Member(ConstantNode *p_constant) { + type = CONSTANT; + constant = p_constant; + } + Member(VariableNode *p_variable) { + type = VARIABLE; + variable = p_variable; + } + Member(SignalNode *p_signal) { + type = SIGNAL; + signal = p_signal; + } + Member(FunctionNode *p_function) { + type = FUNCTION; + function = p_function; + } + Member(EnumNode *p_enum) { + type = ENUM; + m_enum = p_enum; + } + Member(const EnumNode::Value &p_enum_value) { + type = ENUM_VALUE; + enum_value = p_enum_value; + } + }; + + IdentifierNode *identifier = nullptr; + String icon_path; + Vector<Member> members; + HashMap<StringName, int> members_indices; + ClassNode *outer = nullptr; + bool extends_used = false; + bool onready_used = false; + String extends_path; + Vector<StringName> extends; // List for indexing: extends A.B.C + DataType base_type; + String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project. + + bool resolved_interface = false; + bool resolved_body = false; + + Member get_member(const StringName &p_name) const { + return members[members_indices[p_name]]; + } + bool has_member(const StringName &p_name) const { + return members_indices.has(p_name); + } + bool has_function(const StringName &p_name) const { + return has_member(p_name) && members[members_indices[p_name]].type == Member::FUNCTION; + } + template <class T> + void add_member(T *p_member_node) { + members_indices[p_member_node->identifier->name] = members.size(); + members.push_back(Member(p_member_node)); + } + void add_member(const EnumNode::Value &p_enum_value) { + members_indices[p_enum_value.identifier->name] = members.size(); + members.push_back(Member(p_enum_value)); + } + + ClassNode() { + type = CLASS; } }; - struct DictionaryNode : public Node { + struct ConstantNode : public Node { + IdentifierNode *identifier = nullptr; + ExpressionNode *initializer = nullptr; + TypeNode *datatype_specifier = nullptr; + bool infer_datatype = false; + int usages = 0; + + ConstantNode() { + type = CONSTANT; + } + }; + + struct ContinueNode : public Node { + ContinueNode() { + type = CONTINUE; + } + }; + struct DictionaryNode : public ExpressionNode { struct Pair { + ExpressionNode *key = nullptr; + ExpressionNode *value = nullptr; + }; + Vector<Pair> elements; - Node *key; - Node *value; + enum Style { + LUA_TABLE, + PYTHON_DICT, }; + Style style = PYTHON_DICT; - Vector<Pair> elements; - DataType datatype; - virtual DataType get_datatype() const { return datatype; } - virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } DictionaryNode() { - type = TYPE_DICTIONARY; - datatype.has_type = true; - datatype.kind = DataType::BUILTIN; - datatype.builtin_type = Variant::DICTIONARY; - } - }; - - struct SelfNode : public Node { - SelfNode() { type = TYPE_SELF; } - }; - - struct OperatorNode : public Node { - enum Operator { - //call/constructor operator - OP_CALL, - OP_PARENT_CALL, - OP_YIELD, - OP_IS, - OP_IS_BUILTIN, - //indexing operator - OP_INDEX, - OP_INDEX_NAMED, - //unary operators - OP_NEG, - OP_POS, - OP_NOT, - OP_BIT_INVERT, - //binary operators (in precedence order) - OP_IN, - OP_EQUAL, - OP_NOT_EQUAL, - OP_LESS, - OP_LESS_EQUAL, - OP_GREATER, - OP_GREATER_EQUAL, - OP_AND, - OP_OR, - OP_ADD, - OP_SUB, - OP_MUL, - OP_DIV, - OP_MOD, - OP_SHIFT_LEFT, - OP_SHIFT_RIGHT, - OP_INIT_ASSIGN, - OP_ASSIGN, - OP_ASSIGN_ADD, - OP_ASSIGN_SUB, - OP_ASSIGN_MUL, - OP_ASSIGN_DIV, - OP_ASSIGN_MOD, - OP_ASSIGN_SHIFT_LEFT, - OP_ASSIGN_SHIFT_RIGHT, - OP_ASSIGN_BIT_AND, - OP_ASSIGN_BIT_OR, - OP_ASSIGN_BIT_XOR, - OP_BIT_AND, - OP_BIT_OR, - OP_BIT_XOR, - //ternary operators - OP_TERNARY_IF, - OP_TERNARY_ELSE, - }; + type = DICTIONARY; + } + }; - Operator op; + struct ForNode : public Node { + IdentifierNode *variable = nullptr; + ExpressionNode *list = nullptr; + SuiteNode *loop = nullptr; - Vector<Node *> arguments; - DataType datatype; - virtual DataType get_datatype() const { return datatype; } - virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } - OperatorNode() { type = TYPE_OPERATOR; } + ForNode() { + type = FOR; + } }; - struct PatternNode : public Node { + struct FunctionNode : public Node { + IdentifierNode *identifier = nullptr; + Vector<ParameterNode *> parameters; + HashMap<StringName, int> parameters_indices; + TypeNode *return_type = nullptr; + SuiteNode *body = nullptr; + bool is_static = false; + bool is_coroutine = false; + MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + MethodInfo info; + + bool resolved_signature = false; + bool resolved_body = false; - enum PatternType { - PT_CONSTANT, - PT_BIND, - PT_DICTIONARY, - PT_ARRAY, - PT_IGNORE_REST, - PT_WILDCARD + FunctionNode() { + type = FUNCTION; + } + }; + + struct GetNodeNode : public ExpressionNode { + LiteralNode *string = nullptr; + Vector<IdentifierNode *> chain; + + GetNodeNode() { + type = GET_NODE; + } + }; + + struct IdentifierNode : public ExpressionNode { + StringName name; + + enum Source { + UNDEFINED_SOURCE, + FUNCTION_PARAMETER, + LOCAL_CONSTANT, + LOCAL_VARIABLE, + LOCAL_ITERATOR, // `for` loop iterator. + LOCAL_BIND, // Pattern bind. + MEMBER_VARIABLE, + MEMBER_CONSTANT, }; + Source source = UNDEFINED_SOURCE; - PatternType pt_type; + union { + ParameterNode *parameter_source = nullptr; + ConstantNode *constant_source; + VariableNode *variable_source; + IdentifierNode *bind_source; + }; - Node *constant; - StringName bind; - Map<ConstantNode *, PatternNode *> dictionary; - Vector<PatternNode *> array; + int usages = 0; // Useful for binds/iterator variable. + + IdentifierNode() { + type = IDENTIFIER; + } }; - struct PatternBranchNode : public Node { - Vector<PatternNode *> patterns; - BlockNode *body; + struct IfNode : public Node { + ExpressionNode *condition = nullptr; + SuiteNode *true_block = nullptr; + SuiteNode *false_block = nullptr; + + IfNode() { + type = IF; + } + }; + + struct LiteralNode : public ExpressionNode { + Variant value; + + LiteralNode() { + type = LITERAL; + } }; struct MatchNode : public Node { - Node *val_to_match; - Vector<PatternBranchNode *> branches; + ExpressionNode *test = nullptr; + Vector<MatchBranchNode *> branches; - struct CompiledPatternBranch { - Node *compiled_pattern; - BlockNode *body; - }; + MatchNode() { + type = MATCH; + } + }; - Vector<CompiledPatternBranch> compiled_pattern_branches; + struct MatchBranchNode : public Node { + Vector<PatternNode *> patterns; + SuiteNode *block; + bool has_wildcard = false; + + MatchBranchNode() { + type = MATCH_BRANCH; + } }; - struct ControlFlowNode : public Node { - enum CFType { - CF_IF, - CF_FOR, - CF_WHILE, - CF_BREAK, - CF_CONTINUE, - CF_RETURN, - CF_MATCH + struct ParameterNode : public Node { + IdentifierNode *identifier = nullptr; + ExpressionNode *default_value = nullptr; + TypeNode *datatype_specifier = nullptr; + bool infer_datatype = false; + int usages = 0; + + ParameterNode() { + type = PARAMETER; + } + }; + + struct PassNode : public Node { + PassNode() { + type = PASS; + } + }; + + struct PatternNode : public Node { + enum Type { + PT_LITERAL, + PT_EXPRESSION, + PT_BIND, + PT_ARRAY, + PT_DICTIONARY, + PT_REST, + PT_WILDCARD, }; + Type pattern_type = PT_LITERAL; - CFType cf_type; - Vector<Node *> arguments; - BlockNode *body; - BlockNode *body_else; + union { + LiteralNode *literal = nullptr; + IdentifierNode *bind; + ExpressionNode *expression; + }; + Vector<PatternNode *> array; + bool rest_used = false; // For array/dict patterns. - MatchNode *match; + struct Pair { + ExpressionNode *key = nullptr; + PatternNode *value_pattern = nullptr; + }; + Vector<Pair> dictionary; + + HashMap<StringName, IdentifierNode *> binds; - ControlFlowNode *_else; //used for if - ControlFlowNode() { - type = TYPE_CONTROL_FLOW; - cf_type = CF_IF; - body = nullptr; - body_else = nullptr; + bool has_bind(const StringName &p_name); + IdentifierNode *get_bind(const StringName &p_name); + + PatternNode() { + type = PATTERN; } }; + struct PreloadNode : public ExpressionNode { + ExpressionNode *path = nullptr; + String resolved_path; + Ref<Resource> resource; - struct CastNode : public Node { - Node *source_node; - DataType cast_type; - DataType return_type; - virtual DataType get_datatype() const { return return_type; } - virtual void set_datatype(const DataType &p_datatype) { return_type = p_datatype; } - CastNode() { type = TYPE_CAST; } + PreloadNode() { + type = PRELOAD; + } }; - struct AssertNode : public Node { - Node *condition; - Node *message; - AssertNode() : - condition(0), - message(0) { - type = TYPE_ASSERT; + struct ReturnNode : public Node { + ExpressionNode *return_value = nullptr; + + ReturnNode() { + type = RETURN; } }; - struct BreakpointNode : public Node { - BreakpointNode() { type = TYPE_BREAKPOINT; } - }; + struct SelfNode : public ExpressionNode { + ClassNode *current_class = nullptr; - struct NewLineNode : public Node { - NewLineNode() { type = TYPE_NEWLINE; } + SelfNode() { + type = SELF; + } }; - struct Expression { + struct SignalNode : public Node { + IdentifierNode *identifier = nullptr; + Vector<ParameterNode *> parameters; + HashMap<StringName, int> parameters_indices; - bool is_op; + SignalNode() { + type = SIGNAL; + } + }; + + struct SubscriptNode : public ExpressionNode { + ExpressionNode *base = nullptr; union { - OperatorNode::Operator op; - Node *node; + ExpressionNode *index = nullptr; + IdentifierNode *attribute; }; - }; - enum CompletionType { - COMPLETION_NONE, - COMPLETION_BUILT_IN_TYPE_CONSTANT, - COMPLETION_GET_NODE, - COMPLETION_FUNCTION, - COMPLETION_IDENTIFIER, - COMPLETION_PARENT_FUNCTION, - COMPLETION_METHOD, - COMPLETION_CALL_ARGUMENTS, - COMPLETION_RESOURCE_PATH, - COMPLETION_INDEX, - COMPLETION_VIRTUAL_FUNC, - COMPLETION_YIELD, - COMPLETION_ASSIGN, - COMPLETION_TYPE_HINT, - COMPLETION_TYPE_HINT_INDEX, + bool is_attribute = false; + + SubscriptNode() { + type = SUBSCRIPT; + } }; -private: - GDScriptTokenizer *tokenizer; + struct SuiteNode : public Node { + SuiteNode *parent_block = nullptr; + Vector<Node *> statements; + struct Local { + enum Type { + UNDEFINED, + CONSTANT, + VARIABLE, + PARAMETER, + FOR_VARIABLE, + PATTERN_BIND, + }; + Type type = UNDEFINED; + union { + ConstantNode *constant = nullptr; + VariableNode *variable; + ParameterNode *parameter; + IdentifierNode *bind; + }; + StringName name; - Node *head; - Node *list; - template <class T> - T *alloc_node(); - - bool validating; - bool for_completion; - int parenthesis; - bool error_set; - String error; - int error_line; - int error_column; - bool check_types; - bool dependencies_only; - List<String> dependencies; -#ifdef DEBUG_ENABLED - Set<int> *safe_lines; -#endif // DEBUG_ENABLED + int start_line = 0, end_line = 0; + int start_column = 0, end_column = 0; + int leftmost_column = 0, rightmost_column = 0; + + DataType get_datatype() const; + String get_name() const; + + Local() {} + Local(ConstantNode *p_constant) { + type = CONSTANT; + constant = p_constant; + name = p_constant->identifier->name; + + start_line = p_constant->start_line; + end_line = p_constant->end_line; + start_column = p_constant->start_column; + end_column = p_constant->end_column; + leftmost_column = p_constant->leftmost_column; + rightmost_column = p_constant->rightmost_column; + } + Local(VariableNode *p_variable) { + type = VARIABLE; + variable = p_variable; + name = p_variable->identifier->name; + + start_line = p_variable->start_line; + end_line = p_variable->end_line; + start_column = p_variable->start_column; + end_column = p_variable->end_column; + leftmost_column = p_variable->leftmost_column; + rightmost_column = p_variable->rightmost_column; + } + Local(ParameterNode *p_parameter) { + type = PARAMETER; + parameter = p_parameter; + name = p_parameter->identifier->name; + + start_line = p_parameter->start_line; + end_line = p_parameter->end_line; + start_column = p_parameter->start_column; + end_column = p_parameter->end_column; + leftmost_column = p_parameter->leftmost_column; + rightmost_column = p_parameter->rightmost_column; + } + Local(IdentifierNode *p_identifier) { + type = FOR_VARIABLE; + bind = p_identifier; + name = p_identifier->name; + + start_line = p_identifier->start_line; + end_line = p_identifier->end_line; + start_column = p_identifier->start_column; + end_column = p_identifier->end_column; + leftmost_column = p_identifier->leftmost_column; + rightmost_column = p_identifier->rightmost_column; + } + }; + Local empty; + Vector<Local> locals; + HashMap<StringName, int> locals_indices; + + FunctionNode *parent_function = nullptr; + ForNode *parent_for = nullptr; + IfNode *parent_if = nullptr; + + bool has_return = false; + bool has_continue = false; + bool has_unreachable_code = false; // Just so warnings aren't given more than once per block. + + bool has_local(const StringName &p_name) const; + const Local &get_local(const StringName &p_name) const; + template <class T> + void add_local(T *p_local) { + locals_indices[p_local->identifier->name] = locals.size(); + locals.push_back(Local(p_local)); + } + void add_local(const Local &p_local) { + locals_indices[p_local.name] = locals.size(); + locals.push_back(p_local); + } -#ifdef DEBUG_ENABLED - List<GDScriptWarning> warnings; -#endif // DEBUG_ENABLED + SuiteNode() { + type = SUITE; + } + }; - int pending_newline; + struct TernaryOpNode : public ExpressionNode { + // Only one ternary operation exists, so no abstraction here. + ExpressionNode *condition = nullptr; + ExpressionNode *true_expr = nullptr; + ExpressionNode *false_expr = nullptr; - struct IndentLevel { - int indent; - int tabs; + TernaryOpNode() { + type = TERNARY_OPERATOR; + } + }; + + struct TypeNode : public Node { + Vector<IdentifierNode *> type_chain; - bool is_mixed(IndentLevel other) { - return ( - (indent == other.indent && tabs != other.tabs) || - (indent > other.indent && tabs < other.tabs) || - (indent < other.indent && tabs > other.tabs)); + TypeNode() { + type = TYPE; } + }; - IndentLevel() : - indent(0), - tabs(0) {} + struct UnaryOpNode : public ExpressionNode { + enum OpType { + OP_POSITIVE, + OP_NEGATIVE, + OP_COMPLEMENT, + OP_LOGIC_NOT, + }; + + OpType operation; + Variant::Operator variant_op = Variant::OP_MAX; + ExpressionNode *operand = nullptr; - IndentLevel(int p_indent, int p_tabs) : - indent(p_indent), - tabs(p_tabs) {} + UnaryOpNode() { + type = UNARY_OPERATOR; + } }; - List<IndentLevel> indent_level; + struct VariableNode : public Node { + enum PropertyStyle { + PROP_NONE, + PROP_INLINE, + PROP_SETGET, + }; - String base_path; - String self_path; + IdentifierNode *identifier = nullptr; + ExpressionNode *initializer = nullptr; + TypeNode *datatype_specifier = nullptr; + bool infer_datatype = false; - ClassNode *current_class; - FunctionNode *current_function; - BlockNode *current_block; + PropertyStyle property = PROP_NONE; + union { + SuiteNode *setter = nullptr; + IdentifierNode *setter_pointer; + }; + IdentifierNode *setter_parameter = nullptr; + union { + SuiteNode *getter = nullptr; + IdentifierNode *getter_pointer; + }; - bool _get_completable_identifier(CompletionType p_type, StringName &identifier); - void _make_completable_call(int p_arg); + bool exported = false; + bool onready = false; + PropertyInfo export_info; + MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + int assignments = 0; + int usages = 0; - CompletionType completion_type; - StringName completion_cursor; - Variant::Type completion_built_in_constant; - Node *completion_node; - ClassNode *completion_class; - FunctionNode *completion_function; - BlockNode *completion_block; - int completion_line; - int completion_argument; - bool completion_found; - bool completion_ident_is_call; + VariableNode() { + type = VARIABLE; + } + }; - PropertyInfo current_export; + struct WhileNode : public Node { + ExpressionNode *condition = nullptr; + SuiteNode *loop = nullptr; - MultiplayerAPI::RPCMode rpc_mode; + WhileNode() { + type = WHILE; + } + }; - void _set_error(const String &p_error, int p_line = -1, int p_column = -1); -#ifdef DEBUG_ENABLED - void _add_warning(int p_code, int p_line = -1, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String()); - void _add_warning(int p_code, int p_line, const Vector<String> &p_symbols); -#endif // DEBUG_ENABLED - bool _recover_from_completion(); - - bool _parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete = false, bool p_parsing_constant = false); - bool _enter_indent_block(BlockNode *p_block = nullptr); - bool _parse_newline(); - Node *_parse_expression(Node *p_parent, bool p_static, bool p_allow_assign = false, bool p_parsing_constant = false); - Node *_reduce_expression(Node *p_node, bool p_to_const = false); - Node *_parse_and_reduce_expression(Node *p_parent, bool p_static, bool p_reduce_const = false, bool p_allow_assign = false); - - PatternNode *_parse_pattern(bool p_static); - void _parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static); - void _transform_match_statment(MatchNode *p_match_statement); - void _generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings); - - void _parse_block(BlockNode *p_block, bool p_static); - void _parse_extends(ClassNode *p_class); - void _parse_class(ClassNode *p_class); - bool _end_statement(); - - void _determine_inheritance(ClassNode *p_class, bool p_recursive = true); - bool _parse_type(DataType &r_type, bool p_can_be_void = false); - DataType _resolve_type(const DataType &p_source, int p_line); - DataType _type_from_variant(const Variant &p_value) const; - DataType _type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant = true) const; - DataType _type_from_gdtype(const GDScriptDataType &p_gdtype) const; - DataType _get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const; - Variant::Operator _get_variant_operation(const OperatorNode::Operator &p_op) const; - bool _get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const; - bool _get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const; - bool _is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion = false) const; - Node *_get_default_value_for_type(const DataType &p_type, int p_line = -1); - - DataType _reduce_node_type(Node *p_node); - DataType _reduce_function_call_type(const OperatorNode *p_call); - DataType _reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line, bool p_is_indexing); - void _check_class_level_types(ClassNode *p_class); - void _check_class_blocks_types(ClassNode *p_class); - void _check_function_types(FunctionNode *p_function); - void _check_block_types(BlockNode *p_block); - _FORCE_INLINE_ void _mark_line_as_safe(int p_line) const { -#ifdef DEBUG_ENABLED - if (safe_lines) safe_lines->insert(p_line); -#endif // DEBUG_ENABLED - } - _FORCE_INLINE_ void _mark_line_as_unsafe(int p_line) const { -#ifdef DEBUG_ENABLED - if (safe_lines) safe_lines->erase(p_line); -#endif // DEBUG_ENABLED - } + enum CompletionType { + COMPLETION_NONE, + COMPLETION_ANNOTATION, // Annotation (following @). + COMPLETION_ANNOTATION_ARGUMENTS, // Annotation arguments hint. + COMPLETION_ASSIGN, // Assignment based on type (e.g. enum values). + COMPLETION_ATTRIBUTE, // After id.| to look for members. + COMPLETION_ATTRIBUTE_METHOD, // After id.| to look for methods. + COMPLETION_BUILT_IN_TYPE_CONSTANT, // Constants inside a built-in type (e.g. Color.blue). + COMPLETION_CALL_ARGUMENTS, // Complete with nodes, input actions, enum values (or usual expressions). + // TODO: COMPLETION_DECLARATION, // Potential declaration (var, const, func). + COMPLETION_GET_NODE, // Get node with $ notation. + COMPLETION_IDENTIFIER, // List available identifiers in scope. + COMPLETION_INHERIT_TYPE, // Type after extends. Exclude non-viable types (built-ins, enums, void). Includes subtypes using the argument index. + COMPLETION_METHOD, // List available methods in scope. + COMPLETION_OVERRIDE_METHOD, // Override implementation, also for native virtuals. + COMPLETION_PROPERTY_DECLARATION, // Property declaration (get, set). + COMPLETION_PROPERTY_DECLARATION_OR_TYPE, // Property declaration (get, set) or a type hint. + COMPLETION_PROPERTY_METHOD, // Property setter or getter (list available methods). + COMPLETION_RESOURCE_PATH, // For load/preload. + COMPLETION_SUBSCRIPT, // Inside id[|]. + COMPLETION_SUPER_METHOD, // After super. + COMPLETION_TYPE_ATTRIBUTE, // Attribute in type name (Type.|). + COMPLETION_TYPE_NAME, // Name of type (after :). + COMPLETION_TYPE_NAME_OR_VOID, // Same as TYPE_NAME, but allows void (in function return type). + }; - Error _parse(const String &p_base_path); + struct CompletionContext { + CompletionType type = COMPLETION_NONE; + ClassNode *current_class = nullptr; + FunctionNode *current_function = nullptr; + SuiteNode *current_suite = nullptr; + int current_line = -1; + int current_argument = -1; + Variant::Type builtin_type = Variant::VARIANT_MAX; + Node *node = nullptr; + Object *base = nullptr; + List<Ref<GDScriptParserRef>> dependent_parsers; + }; -public: - bool has_error() const; - String get_error() const; - int get_error_line() const; - int get_error_column() const; + struct CompletionCall { + Node *call = nullptr; + int argument = -1; + }; + +private: + friend class GDScriptAnalyzer; + + bool _is_tool = false; + String script_path; + bool for_completion = false; + bool panic_mode = false; + bool can_break = false; + bool can_continue = false; + bool is_ignoring_warnings = false; + List<bool> multiline_stack; + + ClassNode *head = nullptr; + Node *list = nullptr; + List<ParserError> errors; #ifdef DEBUG_ENABLED - const List<GDScriptWarning> &get_warnings() const { return warnings; } -#endif // DEBUG_ENABLED - Error parse(const String &p_code, const String &p_base_path = "", bool p_just_validate = false, const String &p_self_path = "", bool p_for_completion = false, Set<int> *r_safe_lines = nullptr, bool p_dependencies_only = false); - Error parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path = "", const String &p_self_path = ""); + List<GDScriptWarning> warnings; + Set<String> ignored_warnings; + Set<int> unsafe_lines; +#endif - bool is_tool_script() const; - const Node *get_parse_tree() const; + GDScriptTokenizer tokenizer; + GDScriptTokenizer::Token previous; + GDScriptTokenizer::Token current; + + ClassNode *current_class = nullptr; + FunctionNode *current_function = nullptr; + SuiteNode *current_suite = nullptr; + + CompletionContext completion_context; + CompletionCall completion_call; + List<CompletionCall> completion_call_stack; + bool passed_cursor = false; + + typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target); + struct AnnotationInfo { + enum TargetKind { + NONE = 0, + SCRIPT = 1 << 0, + CLASS = 1 << 1, + VARIABLE = 1 << 2, + CONSTANT = 1 << 3, + SIGNAL = 1 << 4, + FUNCTION = 1 << 5, + STATEMENT = 1 << 6, + CLASS_LEVEL = CLASS | VARIABLE | FUNCTION, + }; + uint32_t target_kind = 0; // Flags. + AnnotationAction apply = nullptr; + MethodInfo info; + }; + HashMap<StringName, AnnotationInfo> valid_annotations; + List<AnnotationNode *> annotation_stack; + + typedef ExpressionNode *(GDScriptParser::*ParseFunction)(ExpressionNode *p_previous_operand, bool p_can_assign); + // Higher value means higher precedence (i.e. is evaluated first). + enum Precedence { + PREC_NONE, + PREC_ASSIGNMENT, + PREC_CAST, + PREC_TERNARY, + PREC_LOGIC_OR, + PREC_LOGIC_AND, + PREC_LOGIC_NOT, + PREC_CONTENT_TEST, + PREC_COMPARISON, + PREC_BIT_OR, + PREC_BIT_XOR, + PREC_BIT_AND, + PREC_BIT_SHIFT, + PREC_SUBTRACTION, + PREC_ADDITION, + PREC_FACTOR, + PREC_SIGN, + PREC_BIT_NOT, + PREC_TYPE_TEST, + PREC_AWAIT, + PREC_CALL, + PREC_ATTRIBUTE, + PREC_SUBSCRIPT, + PREC_PRIMARY, + }; + struct ParseRule { + ParseFunction prefix = nullptr; + ParseFunction infix = nullptr; + Precedence precedence = PREC_NONE; + }; + static ParseRule *get_rule(GDScriptTokenizer::Token::Type p_token_type); - //completion info + template <class T> + T *alloc_node() { + T *node = memnew(T); - CompletionType get_completion_type(); - StringName get_completion_cursor(); - int get_completion_line(); - Variant::Type get_completion_built_in_constant(); - Node *get_completion_node(); - ClassNode *get_completion_class(); - BlockNode *get_completion_block(); - FunctionNode *get_completion_function(); - int get_completion_argument_index(); - int get_completion_identifier_is_function(); + node->next = list; + list = node; - const List<String> &get_dependencies() const { return dependencies; } + // TODO: Properly set positions for all nodes. + node->start_line = previous.start_line; + node->end_line = previous.end_line; + node->start_column = previous.start_column; + node->end_column = previous.end_column; + node->leftmost_column = previous.leftmost_column; + node->rightmost_column = previous.rightmost_column; + return node; + } void clear(); + void push_error(const String &p_message, const Node *p_origin = nullptr); +#ifdef DEBUG_ENABLED + void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String()); + void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols); +#endif + + void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false); + void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false); + void push_completion_call(Node *p_call); + void pop_completion_call(); + void set_last_completion_call_arg(int p_argument); + + GDScriptTokenizer::Token advance(); + bool match(GDScriptTokenizer::Token::Type p_token_type); + bool check(GDScriptTokenizer::Token::Type p_token_type); + bool consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message); + bool is_at_end(); + bool is_statement_end(); + void end_statement(const String &p_context); + void synchronize(); + void push_multiline(bool p_state); + void pop_multiline(); + + // Main blocks. + void parse_program(); + ClassNode *parse_class(); + void parse_class_name(); + void parse_extends(); + void parse_class_body(); + template <class T> + void parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind); + SignalNode *parse_signal(); + EnumNode *parse_enum(); + ParameterNode *parse_parameter(); + FunctionNode *parse_function(); + SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr); + // Annotations + AnnotationNode *parse_annotation(uint32_t p_valid_targets); + bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, int p_optional_arguments = 0, bool p_is_vararg = false); + bool validate_annotation_arguments(AnnotationNode *p_annotation); + void clear_unused_annotations(); + bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target); + bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target); + bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target); + template <PropertyHint t_hint, Variant::Type t_type> + bool export_annotations(const AnnotationNode *p_annotation, Node *p_target); + bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target); + template <MultiplayerAPI::RPCMode t_mode> + bool network_annotations(const AnnotationNode *p_annotation, Node *p_target); + // Statements. + Node *parse_statement(); + VariableNode *parse_variable(); + VariableNode *parse_variable(bool p_allow_property); + VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent); + void parse_property_getter(VariableNode *p_variable); + void parse_property_setter(VariableNode *p_variable); + ConstantNode *parse_constant(); + AssertNode *parse_assert(); + BreakNode *parse_break(); + ContinueNode *parse_continue(); + ForNode *parse_for(); + IfNode *parse_if(const String &p_token = "if"); + MatchNode *parse_match(); + MatchBranchNode *parse_match_branch(); + PatternNode *parse_match_pattern(PatternNode *p_root_pattern = nullptr); + WhileNode *parse_while(); + // Expressions. + ExpressionNode *parse_expression(bool p_can_assign, bool p_stop_on_assign = false); + ExpressionNode *parse_precedence(Precedence p_precedence, bool p_can_assign, bool p_stop_on_assign = false); + ExpressionNode *parse_literal(ExpressionNode *p_previous_operand, bool p_can_assign); + LiteralNode *parse_literal(); + ExpressionNode *parse_self(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign); + IdentifierNode *parse_identifier(); + ExpressionNode *parse_builtin_constant(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_unary_operator(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_ternary_operator(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_assignment(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_array(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_dictionary(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_call(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_preload(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_grouping(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_await(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign); + TypeNode *parse_type(bool p_allow_void = false); + +public: + Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion); + ClassNode *get_tree() const { return head; } + bool is_tool() const { return _is_tool; } + static Variant::Type get_builtin_type(const StringName &p_type); + static GDScriptFunctions::Function get_builtin_function(const StringName &p_name); + + CompletionContext get_completion_context() const { return completion_context; } + CompletionCall get_completion_call() const { return completion_call; } + void get_annotation_list(List<MethodInfo> *r_annotations) const; + + const List<ParserError> &get_errors() const { return errors; } + const List<String> get_dependencies() const { + // TODO: Keep track of deps. + return List<String>(); + } +#ifdef DEBUG_ENABLED + const List<GDScriptWarning> &get_warnings() const { return warnings; } + const Set<int> &get_unsafe_lines() const { return unsafe_lines; } + int get_last_line_number() const { return current.end_line; } +#endif + GDScriptParser(); ~GDScriptParser(); + +#ifdef DEBUG_ENABLED + class TreePrinter { + int indent_level = 0; + String indent; + StringBuilder printed; + bool pending_indent = false; + + void increase_indent(); + void decrease_indent(); + void push_line(const String &p_line = String()); + void push_text(const String &p_text); + + void print_annotation(AnnotationNode *p_annotation); + void print_array(ArrayNode *p_array); + void print_assert(AssertNode *p_assert); + void print_assignment(AssignmentNode *p_assignment); + void print_await(AwaitNode *p_await); + void print_binary_op(BinaryOpNode *p_binary_op); + void print_call(CallNode *p_call); + void print_cast(CastNode *p_cast); + void print_class(ClassNode *p_class); + void print_constant(ConstantNode *p_constant); + void print_dictionary(DictionaryNode *p_dictionary); + void print_expression(ExpressionNode *p_expression); + void print_enum(EnumNode *p_enum); + void print_for(ForNode *p_for); + void print_function(FunctionNode *p_function); + void print_get_node(GetNodeNode *p_get_node); + void print_if(IfNode *p_if, bool p_is_elif = false); + void print_identifier(IdentifierNode *p_identifier); + void print_literal(LiteralNode *p_literal); + void print_match(MatchNode *p_match); + void print_match_branch(MatchBranchNode *p_match_branch); + void print_match_pattern(PatternNode *p_match_pattern); + void print_parameter(ParameterNode *p_parameter); + void print_preload(PreloadNode *p_preload); + void print_return(ReturnNode *p_return); + void print_self(SelfNode *p_self); + void print_signal(SignalNode *p_signal); + void print_statement(Node *p_statement); + void print_subscript(SubscriptNode *p_subscript); + void print_suite(SuiteNode *p_suite); + void print_type(TypeNode *p_type); + void print_ternary_op(TernaryOpNode *p_ternary_op); + void print_unary_op(UnaryOpNode *p_unary_op); + void print_variable(VariableNode *p_variable); + void print_while(WhileNode *p_while); + + public: + void print_tree(const GDScriptParser &p_parser); + }; +#endif // DEBUG_ENABLED + static void cleanup(); }; #endif // GDSCRIPT_PARSER_H diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index d42ca52731..7a4bdd88ba 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -30,1503 +30,1294 @@ #include "gdscript_tokenizer.h" -#include "core/io/marshalls.h" -#include "core/map.h" -#include "core/print_string.h" -#include "gdscript_functions.h" - -const char *GDScriptTokenizer::token_names[TK_MAX] = { - "Empty", - "Identifier", - "Constant", - "Self", - "Built-In Type", - "Built-In Func", - "In", - "'=='", - "'!='", - "'<'", - "'<='", - "'>'", - "'>='", - "'and'", - "'or'", - "'not'", - "'+'", - "'-'", - "'*'", - "'/'", - "'%'", - "'<<'", - "'>>'", - "'='", - "'+='", - "'-='", - "'*='", - "'/='", - "'%='", - "'<<='", - "'>>='", - "'&='", - "'|='", - "'^='", - "'&'", - "'|'", - "'^'", - "'~'", - //"Plus Plus", - //"Minus Minus", - "if", - "elif", - "else", - "for", - "while", - "break", - "continue", - "pass", - "return", - "match", - "func", - "class", - "class_name", - "extends", - "is", - "onready", - "tool", - "static", - "export", - "setget", - "const", - "var", - "as", - "void", - "enum", - "preload", - "assert", - "yield", - "signal", - "breakpoint", - "remote", - "master", - "puppet", - "remotesync", - "mastersync", - "puppetsync", - "'['", - "']'", - "'{'", - "'}'", - "'('", - "')'", - "','", - "';'", - "'.'", - "'?'", - "':'", - "'$'", - "'->'", - "'\\n'", - "PI", - "TAU", - "_", - "INF", - "NAN", - "Error", - "EOF", - "Cursor" +#include "core/error_macros.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif + +static const char *token_names[] = { + "Empty", // EMPTY, + // Basic + "Annotation", // ANNOTATION + "Identifier", // IDENTIFIER, + "Literal", // LITERAL, + // Comparison + "<", // LESS, + "<=", // LESS_EQUAL, + ">", // GREATER, + ">=", // GREATER_EQUAL, + "==", // EQUAL_EQUAL, + "!=", // BANG_EQUAL, + // Logical + "and", // AND, + "or", // OR, + "not", // NOT, + "&&", // AMPERSAND_AMPERSAND, + "||", // PIPE_PIPE, + "!", // BANG, + // Bitwise + "&", // AMPERSAND, + "|", // PIPE, + "~", // TILDE, + "^", // CARET, + "<<", // LESS_LESS, + ">>", // GREATER_GREATER, + // Math + "+", // PLUS, + "-", // MINUS, + "*", // STAR, + "/", // SLASH, + "%", // PERCENT, + // Assignment + "=", // EQUAL, + "+=", // PLUS_EQUAL, + "-=", // MINUS_EQUAL, + "*=", // STAR_EQUAL, + "/=", // SLASH_EQUAL, + "%=", // PERCENT_EQUAL, + "<<=", // LESS_LESS_EQUAL, + ">>=", // GREATER_GREATER_EQUAL, + "&=", // AMPERSAND_EQUAL, + "|=", // PIPE_EQUAL, + "^=", // CARET_EQUAL, + // Control flow + "if", // IF, + "elif", // ELIF, + "else", // ELSE, + "for", // FOR, + "while", // WHILE, + "break", // BREAK, + "continue", // CONTINUE, + "pass", // PASS, + "return", // RETURN, + "match", // MATCH, + // Keywords + "as", // AS, + "assert", // ASSERT, + "await", // AWAIT, + "breakpoint", // BREAKPOINT, + "class", // CLASS, + "class_name", // CLASS_NAME, + "const", // CONST, + "enum", // ENUM, + "extends", // EXTENDS, + "func", // FUNC, + "in", // IN, + "is", // IS, + "namespace", // NAMESPACE + "preload", // PRELOAD, + "self", // SELF, + "signal", // SIGNAL, + "static", // STATIC, + "super", // SUPER, + "trait", // TRAIT, + "var", // VAR, + "void", // VOID, + "yield", // YIELD, + // Punctuation + "[", // BRACKET_OPEN, + "]", // BRACKET_CLOSE, + "{", // BRACE_OPEN, + "}", // BRACE_CLOSE, + "(", // PARENTHESIS_OPEN, + ")", // PARENTHESIS_CLOSE, + ",", // COMMA, + ";", // SEMICOLON, + ".", // PERIOD, + "..", // PERIOD_PERIOD, + ":", // COLON, + "$", // DOLLAR, + "->", // FORWARD_ARROW, + "_", // UNDERSCORE, + // Whitespace + "Newline", // NEWLINE, + "Indent", // INDENT, + "Dedent", // DEDENT, + // Constants + "PI", // CONST_PI, + "TAU", // CONST_TAU, + "INF", // CONST_INF, + "NaN", // CONST_NAN, + // Error message improvement + "VCS conflict marker", // VCS_CONFLICT_MARKER, + "`", // BACKTICK, + "?", // QUESTION_MARK, + // Special + "Error", // ERROR, + "End of file", // EOF, }; -struct _bit { - Variant::Type type; - const char *text; -}; -//built in types - -static const _bit _type_list[] = { - //types - { Variant::BOOL, "bool" }, - { Variant::INT, "int" }, - { Variant::FLOAT, "float" }, - { Variant::STRING, "String" }, - { Variant::VECTOR2, "Vector2" }, - { Variant::VECTOR2I, "Vector2i" }, - { Variant::RECT2, "Rect2" }, - { Variant::RECT2I, "Rect2i" }, - { Variant::TRANSFORM2D, "Transform2D" }, - { Variant::VECTOR3, "Vector3" }, - { Variant::VECTOR3I, "Vector3i" }, - { Variant::AABB, "AABB" }, - { Variant::PLANE, "Plane" }, - { Variant::QUAT, "Quat" }, - { Variant::BASIS, "Basis" }, - { Variant::TRANSFORM, "Transform" }, - { Variant::COLOR, "Color" }, - { Variant::_RID, "RID" }, - { Variant::OBJECT, "Object" }, - { Variant::STRING_NAME, "StringName" }, - { Variant::NODE_PATH, "NodePath" }, - { Variant::DICTIONARY, "Dictionary" }, - { Variant::CALLABLE, "Callable" }, - { Variant::SIGNAL, "Signal" }, - { Variant::ARRAY, "Array" }, - { Variant::PACKED_BYTE_ARRAY, "PackedByteArray" }, - { Variant::PACKED_INT32_ARRAY, "PackedInt32Array" }, - { Variant::PACKED_INT64_ARRAY, "PackedInt64Array" }, - { Variant::PACKED_FLOAT32_ARRAY, "PackedFloat32Array" }, - { Variant::PACKED_FLOAT64_ARRAY, "PackedFloat64Array" }, - { Variant::PACKED_STRING_ARRAY, "PackedStringArray" }, - { Variant::PACKED_VECTOR2_ARRAY, "PackedVector2Array" }, - { Variant::PACKED_VECTOR3_ARRAY, "PackedVector3Array" }, - { Variant::PACKED_COLOR_ARRAY, "PackedColorArray" }, - { Variant::VARIANT_MAX, nullptr }, -}; - -struct _kws { - GDScriptTokenizer::Token token; - const char *text; -}; - -static const _kws _keyword_list[] = { - //ops - { GDScriptTokenizer::TK_OP_IN, "in" }, - { GDScriptTokenizer::TK_OP_NOT, "not" }, - { GDScriptTokenizer::TK_OP_OR, "or" }, - { GDScriptTokenizer::TK_OP_AND, "and" }, - //func - { GDScriptTokenizer::TK_PR_FUNCTION, "func" }, - { GDScriptTokenizer::TK_PR_CLASS, "class" }, - { GDScriptTokenizer::TK_PR_CLASS_NAME, "class_name" }, - { GDScriptTokenizer::TK_PR_EXTENDS, "extends" }, - { GDScriptTokenizer::TK_PR_IS, "is" }, - { GDScriptTokenizer::TK_PR_ONREADY, "onready" }, - { GDScriptTokenizer::TK_PR_TOOL, "tool" }, - { GDScriptTokenizer::TK_PR_STATIC, "static" }, - { GDScriptTokenizer::TK_PR_EXPORT, "export" }, - { GDScriptTokenizer::TK_PR_SETGET, "setget" }, - { GDScriptTokenizer::TK_PR_VAR, "var" }, - { GDScriptTokenizer::TK_PR_AS, "as" }, - { GDScriptTokenizer::TK_PR_VOID, "void" }, - { GDScriptTokenizer::TK_PR_PRELOAD, "preload" }, - { GDScriptTokenizer::TK_PR_ASSERT, "assert" }, - { GDScriptTokenizer::TK_PR_YIELD, "yield" }, - { GDScriptTokenizer::TK_PR_SIGNAL, "signal" }, - { GDScriptTokenizer::TK_PR_BREAKPOINT, "breakpoint" }, - { GDScriptTokenizer::TK_PR_REMOTE, "remote" }, - { GDScriptTokenizer::TK_PR_MASTER, "master" }, - { GDScriptTokenizer::TK_PR_PUPPET, "puppet" }, - { GDScriptTokenizer::TK_PR_REMOTESYNC, "remotesync" }, - { GDScriptTokenizer::TK_PR_MASTERSYNC, "mastersync" }, - { GDScriptTokenizer::TK_PR_PUPPETSYNC, "puppetsync" }, - { GDScriptTokenizer::TK_PR_CONST, "const" }, - { GDScriptTokenizer::TK_PR_ENUM, "enum" }, - //controlflow - { GDScriptTokenizer::TK_CF_IF, "if" }, - { GDScriptTokenizer::TK_CF_ELIF, "elif" }, - { GDScriptTokenizer::TK_CF_ELSE, "else" }, - { GDScriptTokenizer::TK_CF_FOR, "for" }, - { GDScriptTokenizer::TK_CF_WHILE, "while" }, - { GDScriptTokenizer::TK_CF_BREAK, "break" }, - { GDScriptTokenizer::TK_CF_CONTINUE, "continue" }, - { GDScriptTokenizer::TK_CF_RETURN, "return" }, - { GDScriptTokenizer::TK_CF_MATCH, "match" }, - { GDScriptTokenizer::TK_CF_PASS, "pass" }, - { GDScriptTokenizer::TK_SELF, "self" }, - { GDScriptTokenizer::TK_CONST_PI, "PI" }, - { GDScriptTokenizer::TK_CONST_TAU, "TAU" }, - { GDScriptTokenizer::TK_WILDCARD, "_" }, - { GDScriptTokenizer::TK_CONST_INF, "INF" }, - { GDScriptTokenizer::TK_CONST_NAN, "NAN" }, - { GDScriptTokenizer::TK_ERROR, nullptr } -}; - -const char *GDScriptTokenizer::get_token_name(Token p_token) { +// Avoid desync. +static_assert(sizeof(token_names) / sizeof(token_names[0]) == GDScriptTokenizer::Token::TK_MAX, "Amount of token names don't match the amount of token types."); - ERR_FAIL_INDEX_V(p_token, TK_MAX, "<error>"); - return token_names[p_token]; +const char *GDScriptTokenizer::Token::get_name() const { + ERR_FAIL_INDEX_V_MSG(type, TK_MAX, "<error>", "Using token type out of the enum."); + return token_names[type]; } -bool GDScriptTokenizer::is_token_literal(int p_offset, bool variable_safe) const { - switch (get_token(p_offset)) { - // Can always be literal: - case TK_IDENTIFIER: - - case TK_PR_ONREADY: - case TK_PR_TOOL: - case TK_PR_STATIC: - case TK_PR_EXPORT: - case TK_PR_SETGET: - case TK_PR_SIGNAL: - case TK_PR_REMOTE: - case TK_PR_MASTER: - case TK_PR_PUPPET: - case TK_PR_REMOTESYNC: - case TK_PR_MASTERSYNC: - case TK_PR_PUPPETSYNC: - return true; - - // Literal for non-variables only: - case TK_BUILT_IN_TYPE: - case TK_BUILT_IN_FUNC: - - case TK_OP_IN: - //case TK_OP_NOT: - //case TK_OP_OR: - //case TK_OP_AND: - - case TK_PR_CLASS: - case TK_PR_CONST: - case TK_PR_ENUM: - case TK_PR_PRELOAD: - case TK_PR_FUNCTION: - case TK_PR_EXTENDS: - case TK_PR_ASSERT: - case TK_PR_YIELD: - case TK_PR_VAR: - - case TK_CF_IF: - case TK_CF_ELIF: - case TK_CF_ELSE: - case TK_CF_FOR: - case TK_CF_WHILE: - case TK_CF_BREAK: - case TK_CF_CONTINUE: - case TK_CF_RETURN: - case TK_CF_MATCH: - case TK_CF_PASS: - case TK_SELF: - case TK_CONST_PI: - case TK_CONST_TAU: - case TK_WILDCARD: - case TK_CONST_INF: - case TK_CONST_NAN: - case TK_ERROR: - return !variable_safe; - - case TK_CONSTANT: { - switch (get_token_constant(p_offset).get_type()) { - case Variant::NIL: - case Variant::BOOL: - return true; - default: - return false; - } - } - default: - return false; - } +String GDScriptTokenizer::get_token_name(Token::Type p_token_type) { + ERR_FAIL_INDEX_V_MSG(p_token_type, Token::TK_MAX, "<error>", "Using token type out of the enum."); + return token_names[p_token_type]; } -StringName GDScriptTokenizer::get_token_literal(int p_offset) const { - Token token = get_token(p_offset); - switch (token) { - case TK_IDENTIFIER: - return get_token_identifier(p_offset); - case TK_BUILT_IN_TYPE: { - Variant::Type type = get_token_type(p_offset); - int idx = 0; - - while (_type_list[idx].text) { - if (type == _type_list[idx].type) { - return _type_list[idx].text; - } - idx++; - } - } break; // Shouldn't get here, stuff happens - case TK_BUILT_IN_FUNC: - return GDScriptFunctions::get_func_name(get_token_built_in_func(p_offset)); - case TK_CONSTANT: { - const Variant value = get_token_constant(p_offset); - - switch (value.get_type()) { - case Variant::NIL: - return "null"; - case Variant::BOOL: - return value ? "true" : "false"; - default: { - } - } - } break; - case TK_OP_AND: - case TK_OP_OR: - break; // Don't get into default, since they can be non-literal - default: { - int idx = 0; - - while (_keyword_list[idx].text) { - if (token == _keyword_list[idx].token) { - return _keyword_list[idx].text; - } - idx++; - } - } +void GDScriptTokenizer::set_source_code(const String &p_source_code) { + source = p_source_code; + if (source.empty()) { + _source = L""; + } else { + _source = source.ptr(); } - ERR_FAIL_V_MSG("", "Failed to get token literal."); + _current = _source; + line = 1; + column = 1; + length = p_source_code.length(); + position = 0; } -static bool _is_text_char(CharType c) { - - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; +void GDScriptTokenizer::set_cursor_position(int p_line, int p_column) { + cursor_line = p_line; + cursor_column = p_column; } -static bool _is_number(CharType c) { - - return (c >= '0' && c <= '9'); +void GDScriptTokenizer::set_multiline_mode(bool p_state) { + multiline_mode = p_state; } -static bool _is_hex(CharType c) { - - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +int GDScriptTokenizer::get_cursor_line() const { + return cursor_line; } -static bool _is_bin(CharType c) { - - return (c == '0' || c == '1'); +int GDScriptTokenizer::get_cursor_column() const { + return cursor_column; } -void GDScriptTokenizerText::_make_token(Token p_type) { - - TokenData &tk = tk_rb[tk_rb_pos]; - - tk.type = p_type; - tk.line = line; - tk.col = column; - - tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; +bool GDScriptTokenizer::is_past_cursor() const { + if (line < cursor_line) { + return false; + } + if (line > cursor_line) { + return true; + } + if (column < cursor_column) { + return false; + } + return true; } -void GDScriptTokenizerText::_make_identifier(const StringName &p_identifier) { - - TokenData &tk = tk_rb[tk_rb_pos]; - - tk.type = TK_IDENTIFIER; - tk.identifier = p_identifier; - tk.line = line; - tk.col = column; - tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; +CharType GDScriptTokenizer::_advance() { + if (unlikely(_is_at_end())) { + return '\0'; + } + _current++; + column++; + position++; + if (column > rightmost_column) { + rightmost_column = column; + } + if (unlikely(_is_at_end())) { + // Add extra newline even if it's not there, to satisfy the parser. + newline(true); + // Also add needed unindent. + check_indent(); + } + return _peek(-1); } -void GDScriptTokenizerText::_make_built_in_func(GDScriptFunctions::Function p_func) { - - TokenData &tk = tk_rb[tk_rb_pos]; - - tk.type = TK_BUILT_IN_FUNC; - tk.func = p_func; - tk.line = line; - tk.col = column; - - tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; +void GDScriptTokenizer::push_paren(CharType p_char) { + paren_stack.push_back(p_char); } -void GDScriptTokenizerText::_make_constant(const Variant &p_constant) { - TokenData &tk = tk_rb[tk_rb_pos]; - - tk.type = TK_CONSTANT; - tk.constant = p_constant; - tk.line = line; - tk.col = column; +bool GDScriptTokenizer::pop_paren(CharType p_expected) { + if (paren_stack.empty()) { + return false; + } + CharType actual = paren_stack.back()->get(); + paren_stack.pop_back(); - tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; + return actual == p_expected; } -void GDScriptTokenizerText::_make_type(const Variant::Type &p_type) { - - TokenData &tk = tk_rb[tk_rb_pos]; - - tk.type = TK_BUILT_IN_TYPE; - tk.vtype = p_type; - tk.line = line; - tk.col = column; - - tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; +GDScriptTokenizer::Token GDScriptTokenizer::pop_error() { + Token error = error_stack.back()->get(); + error_stack.pop_back(); + return error; } -void GDScriptTokenizerText::_make_error(const String &p_error) { - - error_flag = true; - last_error = p_error; - - TokenData &tk = tk_rb[tk_rb_pos]; - tk.type = TK_ERROR; - tk.constant = p_error; - tk.line = line; - tk.col = column; - tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; +static bool _is_alphanumeric(CharType c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; } -void GDScriptTokenizerText::_make_newline(int p_indentation, int p_tabs) { - - TokenData &tk = tk_rb[tk_rb_pos]; - tk.type = TK_NEWLINE; - tk.constant = Vector2(p_indentation, p_tabs); - tk.line = line; - tk.col = column; - tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; +static bool _is_digit(CharType c) { + return (c >= '0' && c <= '9'); } -void GDScriptTokenizerText::_advance() { - - if (error_flag) { - //parser broke - _make_error(last_error); - return; - } - - if (code_pos >= len) { - _make_token(TK_EOF); - return; - } -#define GETCHAR(m_ofs) ((m_ofs + code_pos) >= len ? 0 : _code[m_ofs + code_pos]) -#define INCPOS(m_amount) \ - { \ - code_pos += m_amount; \ - column += m_amount; \ - } - while (true) { - - bool is_string_name = false; - StringMode string_mode = STRING_DOUBLE_QUOTE; - - switch (GETCHAR(0)) { - case 0: - _make_token(TK_EOF); - break; - case '\\': - INCPOS(1); - if (GETCHAR(0) == '\r') { - INCPOS(1); - } - - if (GETCHAR(0) != '\n') { - _make_error("Expected newline after '\\'."); - return; - } - - INCPOS(1); - line++; - - while (GETCHAR(0) == ' ' || GETCHAR(0) == '\t') { - INCPOS(1); - } - - continue; - case '\t': - case '\r': - case ' ': - INCPOS(1); - continue; - case '#': { // line comment skip -#ifdef DEBUG_ENABLED - String comment; -#endif // DEBUG_ENABLED - while (GETCHAR(0) != '\n') { -#ifdef DEBUG_ENABLED - comment += GETCHAR(0); -#endif // DEBUG_ENABLED - code_pos++; - if (GETCHAR(0) == 0) { //end of file - //_make_error("Unterminated Comment"); - _make_token(TK_EOF); - return; - } - } -#ifdef DEBUG_ENABLED - String comment_content = comment.trim_prefix("#").trim_prefix(" "); - if (comment_content.begins_with("warning-ignore:")) { - String code = comment_content.get_slice(":", 1); - warning_skips.push_back(Pair<int, String>(line, code.strip_edges().to_lower())); - } else if (comment_content.begins_with("warning-ignore-all:")) { - String code = comment_content.get_slice(":", 1); - warning_global_skips.insert(code.strip_edges().to_lower()); - } else if (comment_content.strip_edges() == "warnings-disable") { - ignore_warnings = true; - } -#endif // DEBUG_ENABLED - [[fallthrough]]; - } - case '\n': { - line++; - INCPOS(1); - bool used_spaces = false; - int tabs = 0; - column = 1; - int i = 0; - while (true) { - if (GETCHAR(i) == ' ') { - i++; - used_spaces = true; - } else if (GETCHAR(i) == '\t') { - if (used_spaces) { - _make_error("Spaces used before tabs on a line"); - return; - } - i++; - tabs++; - } else { - break; // not indentation anymore - } - } - - _make_newline(i, tabs); - return; - } - case '/': { - - switch (GETCHAR(1)) { - case '=': { // diveq - - _make_token(TK_OP_ASSIGN_DIV); - INCPOS(1); - - } break; - default: - _make_token(TK_OP_DIV); - } - } break; - case '=': { - if (GETCHAR(1) == '=') { - _make_token(TK_OP_EQUAL); - INCPOS(1); - - } else - _make_token(TK_OP_ASSIGN); - - } break; - case '<': { - if (GETCHAR(1) == '=') { - - _make_token(TK_OP_LESS_EQUAL); - INCPOS(1); - } else if (GETCHAR(1) == '<') { - if (GETCHAR(2) == '=') { - _make_token(TK_OP_ASSIGN_SHIFT_LEFT); - INCPOS(1); - } else { - _make_token(TK_OP_SHIFT_LEFT); - } - INCPOS(1); - } else - _make_token(TK_OP_LESS); - - } break; - case '>': { - if (GETCHAR(1) == '=') { - _make_token(TK_OP_GREATER_EQUAL); - INCPOS(1); - } else if (GETCHAR(1) == '>') { - if (GETCHAR(2) == '=') { - _make_token(TK_OP_ASSIGN_SHIFT_RIGHT); - INCPOS(1); - - } else { - _make_token(TK_OP_SHIFT_RIGHT); - } - INCPOS(1); - } else { - _make_token(TK_OP_GREATER); - } - - } break; - case '!': { - if (GETCHAR(1) == '=') { - _make_token(TK_OP_NOT_EQUAL); - INCPOS(1); - } else { - _make_token(TK_OP_NOT); - } - - } break; - //case '"' //string - no strings in shader - //case '\'' //string - no strings in shader - case '{': - _make_token(TK_CURLY_BRACKET_OPEN); - break; - case '}': - _make_token(TK_CURLY_BRACKET_CLOSE); - break; - case '[': - _make_token(TK_BRACKET_OPEN); - break; - case ']': - _make_token(TK_BRACKET_CLOSE); - break; - case '(': - _make_token(TK_PARENTHESIS_OPEN); - break; - case ')': - _make_token(TK_PARENTHESIS_CLOSE); - break; - case ',': - _make_token(TK_COMMA); - break; - case ';': - _make_token(TK_SEMICOLON); - break; - case '?': - _make_token(TK_QUESTION_MARK); - break; - case ':': - _make_token(TK_COLON); //for methods maybe but now useless. - break; - case '$': - _make_token(TK_DOLLAR); //for the get_node() shortener - break; - case '^': { - if (GETCHAR(1) == '=') { - _make_token(TK_OP_ASSIGN_BIT_XOR); - INCPOS(1); - } else { - _make_token(TK_OP_BIT_XOR); - } +static bool _is_hex_digit(CharType c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} - } break; - case '~': - _make_token(TK_OP_BIT_INVERT); - break; - case '&': { - if (GETCHAR(1) == '&') { - - _make_token(TK_OP_AND); - INCPOS(1); - } else if (GETCHAR(1) == '=') { - _make_token(TK_OP_ASSIGN_BIT_AND); - INCPOS(1); - } else { - _make_token(TK_OP_BIT_AND); - } - } break; - case '|': { - if (GETCHAR(1) == '|') { - - _make_token(TK_OP_OR); - INCPOS(1); - } else if (GETCHAR(1) == '=') { - _make_token(TK_OP_ASSIGN_BIT_OR); - INCPOS(1); - } else { - _make_token(TK_OP_BIT_OR); - } - } break; - case '*': { +static bool _is_binary_digit(CharType c) { + return (c == '0' || c == '1'); +} - if (GETCHAR(1) == '=') { - _make_token(TK_OP_ASSIGN_MUL); - INCPOS(1); - } else { - _make_token(TK_OP_MUL); - } - } break; - case '+': { - - if (GETCHAR(1) == '=') { - _make_token(TK_OP_ASSIGN_ADD); - INCPOS(1); - /* - } else if (GETCHAR(1)=='+') { - _make_token(TK_OP_PLUS_PLUS); - INCPOS(1); - */ +GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) { + Token token(p_type); + token.start_line = start_line; + token.end_line = line; + token.start_column = start_column; + token.end_column = column; + token.leftmost_column = leftmost_column; + token.rightmost_column = rightmost_column; + token.source = String(_start, _current - _start); + + if (p_type != Token::ERROR && cursor_line > -1) { + // Also count whitespace after token. + int offset = 0; + while (_peek(offset) == ' ' || _peek(offset) == '\t') { + offset++; + } + int last_column = column + offset; + // Check cursor position in token. + if (start_line == line) { + // Single line token. + if (cursor_line == start_line && cursor_column >= start_column && cursor_column <= last_column) { + token.cursor_position = cursor_column - start_column; + if (cursor_column == start_column) { + token.cursor_place = CURSOR_BEGINNING; + } else if (cursor_column < column) { + token.cursor_place = CURSOR_MIDDLE; } else { - _make_token(TK_OP_ADD); + token.cursor_place = CURSOR_END; } - - } break; - case '-': { - - if (GETCHAR(1) == '=') { - _make_token(TK_OP_ASSIGN_SUB); - INCPOS(1); - } else if (GETCHAR(1) == '>') { - _make_token(TK_FORWARD_ARROW); - INCPOS(1); + } + } else { + // Multi line token. + if (cursor_line == start_line && cursor_column >= start_column) { + // Is in first line. + token.cursor_position = cursor_column - start_column; + if (cursor_column == start_column) { + token.cursor_place = CURSOR_BEGINNING; } else { - _make_token(TK_OP_SUB); + token.cursor_place = CURSOR_MIDDLE; } - } break; - case '%': { - - if (GETCHAR(1) == '=') { - _make_token(TK_OP_ASSIGN_MOD); - INCPOS(1); + } else if (cursor_line == line && cursor_column <= last_column) { + // Is in last line. + token.cursor_position = cursor_column - start_column; + if (cursor_column < column) { + token.cursor_place = CURSOR_MIDDLE; } else { - _make_token(TK_OP_MOD); - } - } break; - case '@': - if (CharType(GETCHAR(1)) != '"' && CharType(GETCHAR(1)) != '\'') { - _make_error("Unexpected '@'"); - return; - } - INCPOS(1); - is_string_name = true; - [[fallthrough]]; - case '\'': - case '"': { - - if (GETCHAR(0) == '\'') - string_mode = STRING_SINGLE_QUOTE; - - int i = 1; - if (string_mode == STRING_DOUBLE_QUOTE && GETCHAR(i) == '"' && GETCHAR(i + 1) == '"') { - i += 2; - string_mode = STRING_MULTILINE; + token.cursor_place = CURSOR_END; } - - String str; - while (true) { - if (CharType(GETCHAR(i)) == 0) { - - _make_error("Unterminated String"); - return; - } else if (string_mode == STRING_DOUBLE_QUOTE && CharType(GETCHAR(i)) == '"') { - break; - } else if (string_mode == STRING_SINGLE_QUOTE && CharType(GETCHAR(i)) == '\'') { - break; - } else if (string_mode == STRING_MULTILINE && CharType(GETCHAR(i)) == '\"' && CharType(GETCHAR(i + 1)) == '\"' && CharType(GETCHAR(i + 2)) == '\"') { - i += 2; - break; - } else if (string_mode != STRING_MULTILINE && CharType(GETCHAR(i)) == '\n') { - _make_error("Unexpected EOL at String."); - return; - } else if (CharType(GETCHAR(i)) == 0xFFFF) { - //string ends here, next will be TK - i--; - break; - } else if (CharType(GETCHAR(i)) == '\\') { - //escaped characters... - i++; - CharType next = GETCHAR(i); - if (next == 0) { - _make_error("Unterminated String"); - return; - } - CharType res = 0; - - switch (next) { - - case 'a': res = 7; break; - case 'b': res = 8; break; - case 't': res = 9; break; - case 'n': res = 10; break; - case 'v': res = 11; break; - case 'f': res = 12; break; - case 'r': res = 13; break; - case '\'': res = '\''; break; - case '\"': res = '\"'; break; - case '\\': res = '\\'; break; - case '/': - res = '/'; - break; //wtf - - case 'u': { - // hex number - i += 1; - for (int j = 0; j < 4; j++) { - CharType c = GETCHAR(i + j); - if (c == 0) { - _make_error("Unterminated String"); - return; - } - - CharType v = 0; - if (c >= '0' && c <= '9') { - v = c - '0'; - } else if (c >= 'a' && c <= 'f') { - v = c - 'a'; - v += 10; - } else if (c >= 'A' && c <= 'F') { - v = c - 'A'; - v += 10; - } else { - _make_error("Malformed hex constant in string"); - return; - } - - res <<= 4; - res |= v; - } - i += 3; - - } break; - default: { - - _make_error("Invalid escape sequence"); - return; - } break; - } - - str += res; - - } else { - if (CharType(GETCHAR(i)) == '\n') { - line++; - column = 1; - } - - str += CharType(GETCHAR(i)); - } - i++; - } - INCPOS(i); - - if (is_string_name) { - _make_constant(StringName(str)); - } else { - _make_constant(str); - } - - } break; - case 0xFFFF: { - _make_token(TK_CURSOR); - } break; - default: { - - if (_is_number(GETCHAR(0)) || (GETCHAR(0) == '.' && _is_number(GETCHAR(1)))) { - // parse number - bool period_found = false; - bool exponent_found = false; - bool hexa_found = false; - bool bin_found = false; - bool sign_found = false; - - String str; - int i = 0; - - while (true) { - if (GETCHAR(i) == '.') { - if (period_found || exponent_found) { - _make_error("Invalid numeric constant at '.'"); - return; - } else if (bin_found) { - _make_error("Invalid binary constant at '.'"); - return; - } else if (hexa_found) { - _make_error("Invalid hexadecimal constant at '.'"); - return; - } - period_found = true; - } else if (GETCHAR(i) == 'x') { - if (hexa_found || bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) { - _make_error("Invalid numeric constant at 'x'"); - return; - } - hexa_found = true; - } else if (hexa_found && _is_hex(GETCHAR(i))) { - - } else if (!hexa_found && GETCHAR(i) == 'b') { - if (bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) { - _make_error("Invalid numeric constant at 'b'"); - return; - } - bin_found = true; - } else if (!hexa_found && GETCHAR(i) == 'e') { - if (exponent_found || bin_found) { - _make_error("Invalid numeric constant at 'e'"); - return; - } - exponent_found = true; - } else if (_is_number(GETCHAR(i))) { - //all ok - - } else if (bin_found && _is_bin(GETCHAR(i))) { - - } else if ((GETCHAR(i) == '-' || GETCHAR(i) == '+') && exponent_found) { - if (sign_found) { - _make_error("Invalid numeric constant at '-'"); - return; - } - sign_found = true; - } else if (GETCHAR(i) == '_') { - i++; - continue; // Included for readability, shouldn't be a part of the string - } else - break; - - str += CharType(GETCHAR(i)); - i++; - } - - if (!(_is_number(str[str.length() - 1]) || (hexa_found && _is_hex(str[str.length() - 1])))) { - _make_error("Invalid numeric constant: " + str); - return; - } - - INCPOS(i); - if (hexa_found) { - int64_t val = str.hex_to_int64(); - _make_constant(val); - } else if (bin_found) { - int64_t val = str.bin_to_int64(); - _make_constant(val); - } else if (period_found || exponent_found) { - double val = str.to_double(); - _make_constant(val); - } else { - int64_t val = str.to_int64(); - _make_constant(val); - } - - return; - } - - if (GETCHAR(0) == '.') { - //parse period - _make_token(TK_PERIOD); - break; - } - - if (_is_text_char(GETCHAR(0))) { - // parse identifier - String str; - str += CharType(GETCHAR(0)); - - int i = 1; - while (_is_text_char(GETCHAR(i))) { - str += CharType(GETCHAR(i)); - i++; - } - - bool identifier = false; - - if (str == "null") { - _make_constant(Variant()); - - } else if (str == "true") { - _make_constant(true); - - } else if (str == "false") { - _make_constant(false); - } else { - - bool found = false; - - { - - int idx = 0; - - while (_type_list[idx].text) { - - if (str == _type_list[idx].text) { - _make_type(_type_list[idx].type); - found = true; - break; - } - idx++; - } - } - - if (!found) { - - //built in func? - - for (int j = 0; j < GDScriptFunctions::FUNC_MAX; j++) { - - if (str == GDScriptFunctions::get_func_name(GDScriptFunctions::Function(j))) { - - _make_built_in_func(GDScriptFunctions::Function(j)); - found = true; - break; - } - } - } - - if (!found) { - //keyword - - int idx = 0; - found = false; - - while (_keyword_list[idx].text) { - - if (str == _keyword_list[idx].text) { - _make_token(_keyword_list[idx].token); - found = true; - break; - } - idx++; - } - } - - if (!found) - identifier = true; - } - - if (identifier) { - _make_identifier(str); - } - INCPOS(str.length()); - return; - } - - _make_error("Unknown character"); - return; - - } break; + } else if (cursor_line > start_line && cursor_line < line) { + // Is in middle line. + token.cursor_position = CURSOR_MIDDLE; + } } - - INCPOS(1); - break; } -} - -void GDScriptTokenizerText::set_code(const String &p_code) { - code = p_code; - len = p_code.length(); - if (len) { - _code = &code[0]; - } else { - _code = nullptr; - } - code_pos = 0; - line = 1; //it is stand-ar-ized that lines begin in 1 in code.. - column = 1; //the same holds for columns - tk_rb_pos = 0; - error_flag = false; -#ifdef DEBUG_ENABLED - ignore_warnings = false; -#endif // DEBUG_ENABLED - last_error = ""; - for (int i = 0; i < MAX_LOOKAHEAD + 1; i++) - _advance(); + return token; } -GDScriptTokenizerText::Token GDScriptTokenizerText::get_token(int p_offset) const { - ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, TK_ERROR); - ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, TK_ERROR); - - int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; - return tk_rb[ofs].type; +GDScriptTokenizer::Token GDScriptTokenizer::make_literal(const Variant &p_literal) { + Token token = make_token(Token::LITERAL); + token.literal = p_literal; + return token; } -int GDScriptTokenizerText::get_token_line(int p_offset) const { - ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, -1); - ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, -1); - - int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; - return tk_rb[ofs].line; +GDScriptTokenizer::Token GDScriptTokenizer::make_identifier(const StringName &p_identifier) { + Token identifier = make_token(Token::IDENTIFIER); + identifier.literal = p_identifier; + return identifier; } -int GDScriptTokenizerText::get_token_column(int p_offset) const { - ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, -1); - ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, -1); +GDScriptTokenizer::Token GDScriptTokenizer::make_error(const String &p_message) { + Token error = make_token(Token::ERROR); + error.literal = p_message; - int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; - return tk_rb[ofs].col; + return error; } -const Variant &GDScriptTokenizerText::get_token_constant(int p_offset) const { - ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, tk_rb[0].constant); - ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, tk_rb[0].constant); - - int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; - ERR_FAIL_COND_V(tk_rb[ofs].type != TK_CONSTANT, tk_rb[0].constant); - return tk_rb[ofs].constant; +void GDScriptTokenizer::push_error(const String &p_message) { + Token error = make_error(p_message); + error_stack.push_back(error); } -StringName GDScriptTokenizerText::get_token_identifier(int p_offset) const { - - ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, StringName()); - ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, StringName()); - - int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; - ERR_FAIL_COND_V(tk_rb[ofs].type != TK_IDENTIFIER, StringName()); - return tk_rb[ofs].identifier; +void GDScriptTokenizer::push_error(const Token &p_error) { + error_stack.push_back(p_error); } -GDScriptFunctions::Function GDScriptTokenizerText::get_token_built_in_func(int p_offset) const { - - ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, GDScriptFunctions::FUNC_MAX); - ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, GDScriptFunctions::FUNC_MAX); - - int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; - ERR_FAIL_COND_V(tk_rb[ofs].type != TK_BUILT_IN_FUNC, GDScriptFunctions::FUNC_MAX); - return tk_rb[ofs].func; +GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(CharType p_paren) { + if (paren_stack.empty()) { + return make_error(vformat("Closing \"%c\" doesn't have an opening counterpart.", p_paren)); + } + Token error = make_error(vformat("Closing \"%c\" doesn't match the opening \"%c\".", p_paren, paren_stack.back()->get())); + paren_stack.pop_back(); // Remove opening one anyway. + return error; } -Variant::Type GDScriptTokenizerText::get_token_type(int p_offset) const { +GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(CharType p_test, Token::Type p_double_type) { + const CharType *next = _current + 1; + int chars = 2; // Two already matched. - ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, Variant::NIL); - ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, Variant::NIL); + // Test before consuming characters, since we don't want to consume more than needed. + while (*next == p_test) { + chars++; + next++; + } + if (chars >= 7) { + // It is a VCS conflict marker. + while (chars > 1) { + // Consume all characters (first was already consumed by scan()). + _advance(); + chars--; + } + return make_token(Token::VCS_CONFLICT_MARKER); + } else { + // It is only a regular double character token, so we consume the second character. + _advance(); + return make_token(p_double_type); + } +} - int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; - ERR_FAIL_COND_V(tk_rb[ofs].type != TK_BUILT_IN_TYPE, Variant::NIL); - return tk_rb[ofs].vtype; +GDScriptTokenizer::Token GDScriptTokenizer::annotation() { + if (!_is_alphanumeric(_peek())) { + push_error("Expected annotation identifier after \"@\"."); + } + while (_is_alphanumeric(_peek())) { + // Consume all identifier characters. + _advance(); + } + Token annotation = make_token(Token::ANNOTATION); + annotation.literal = StringName(annotation.source); + return annotation; } -int GDScriptTokenizerText::get_token_line_indent(int p_offset) const { +GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { +#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \ + KEYWORD_GROUP('a') \ + KEYWORD("as", Token::AS) \ + KEYWORD("and", Token::AND) \ + KEYWORD("assert", Token::ASSERT) \ + KEYWORD("await", Token::AWAIT) \ + KEYWORD_GROUP('b') \ + KEYWORD("break", Token::BREAK) \ + KEYWORD("breakpoint", Token::BREAKPOINT) \ + KEYWORD_GROUP('c') \ + KEYWORD("class", Token::CLASS) \ + KEYWORD("class_name", Token::CLASS_NAME) \ + KEYWORD("const", Token::CONST) \ + KEYWORD("continue", Token::CONTINUE) \ + KEYWORD_GROUP('e') \ + KEYWORD("elif", Token::ELIF) \ + KEYWORD("else", Token::ELSE) \ + KEYWORD("enum", Token::ENUM) \ + KEYWORD("extends", Token::EXTENDS) \ + KEYWORD_GROUP('f') \ + KEYWORD("for", Token::FOR) \ + KEYWORD("func", Token::FUNC) \ + KEYWORD_GROUP('i') \ + KEYWORD("if", Token::IF) \ + KEYWORD("in", Token::IN) \ + KEYWORD("is", Token::IS) \ + KEYWORD_GROUP('m') \ + KEYWORD("match", Token::MATCH) \ + KEYWORD_GROUP('n') \ + KEYWORD("namespace", Token::NAMESPACE) \ + KEYWORD("not", Token::NOT) \ + KEYWORD_GROUP('o') \ + KEYWORD("or", Token::OR) \ + KEYWORD_GROUP('p') \ + KEYWORD("pass", Token::PASS) \ + KEYWORD("preload", Token::PRELOAD) \ + KEYWORD_GROUP('r') \ + KEYWORD("return", Token::RETURN) \ + KEYWORD_GROUP('s') \ + KEYWORD("self", Token::SELF) \ + KEYWORD("signal", Token::SIGNAL) \ + KEYWORD("static", Token::STATIC) \ + KEYWORD("super", Token::SUPER) \ + KEYWORD_GROUP('t') \ + KEYWORD("trait", Token::TRAIT) \ + KEYWORD_GROUP('v') \ + KEYWORD("var", Token::VAR) \ + KEYWORD("void", Token::VOID) \ + KEYWORD_GROUP('w') \ + KEYWORD("while", Token::WHILE) \ + KEYWORD_GROUP('y') \ + KEYWORD("yield", Token::YIELD) \ + KEYWORD_GROUP('I') \ + KEYWORD("INF", Token::CONST_INF) \ + KEYWORD_GROUP('N') \ + KEYWORD("NAN", Token::CONST_NAN) \ + KEYWORD_GROUP('P') \ + KEYWORD("PI", Token::CONST_PI) \ + KEYWORD_GROUP('T') \ + KEYWORD("TAU", Token::CONST_TAU) + +#define MIN_KEYWORD_LENGTH 2 +#define MAX_KEYWORD_LENGTH 10 + + // Consume all alphanumeric characters. + while (_is_alphanumeric(_peek())) { + _advance(); + } - ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, 0); - ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, 0); + int length = _current - _start; - int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; - ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0); - return tk_rb[ofs].constant.operator Vector2().x; -} + if (length == 1 && _peek(-1) == '_') { + // Lone underscore. + return make_token(Token::UNDERSCORE); + } -int GDScriptTokenizerText::get_token_line_tab_indent(int p_offset) const { + String name(_start, length); + if (length < MIN_KEYWORD_LENGTH || length > MAX_KEYWORD_LENGTH) { + // Cannot be a keyword, as the length doesn't match any. + return make_identifier(name); + } - ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, 0); - ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, 0); + // Define some helper macros for the switch case. +#define KEYWORD_GROUP_CASE(char) \ + break; \ + case char: +#define KEYWORD(keyword, token_type) \ + { \ + const int keyword_length = sizeof(keyword) - 1; \ + static_assert(keyword_length <= MAX_KEYWORD_LENGTH, "There's a keyword longer than the defined maximum length"); \ + static_assert(keyword_length >= MIN_KEYWORD_LENGTH, "There's a keyword shorter than the defined minimum length"); \ + if (keyword_length == length && name == keyword) { \ + return make_token(token_type); \ + } \ + } - int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; - ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0); - return tk_rb[ofs].constant.operator Vector2().y; -} + // Find if it's a keyword. + switch (_start[0]) { + default: + KEYWORDS(KEYWORD_GROUP_CASE, KEYWORD) + break; + } -String GDScriptTokenizerText::get_token_error(int p_offset) const { + // Check if it's a special literal + if (length == 4) { + if (name == "true") { + return make_literal(true); + } else if (name == "null") { + return make_literal(Variant()); + } + } else if (length == 5) { + if (name == "false") { + return make_literal(false); + } + } - ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, String()); - ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, String()); + // Not a keyword, so must be an identifier. + return make_identifier(name); - int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; - ERR_FAIL_COND_V(tk_rb[ofs].type != TK_ERROR, String()); - return tk_rb[ofs].constant; +#undef KEYWORDS +#undef MIN_KEYWORD_LENGTH +#undef MAX_KEYWORD_LENGTH +#undef KEYWORD_GROUP_CASE +#undef KEYWORD } -void GDScriptTokenizerText::advance(int p_amount) { +void GDScriptTokenizer::newline(bool p_make_token) { + // Don't overwrite previous newline, nor create if we want a line contination. + if (p_make_token && !pending_newline && !line_continuation) { + Token newline(Token::NEWLINE); + newline.start_line = line; + newline.end_line = line; + newline.start_column = column - 1; + newline.end_column = column; + newline.leftmost_column = newline.start_column; + newline.rightmost_column = newline.end_column; + pending_newline = true; + last_newline = newline; + } - ERR_FAIL_COND(p_amount <= 0); - for (int i = 0; i < p_amount; i++) - _advance(); + // Increment line/column counters. + line++; + column = 1; + leftmost_column = 1; } -////////////////////////////////////////////////////////////////////////////////////////////////////// - -#define BYTECODE_VERSION 13 - -Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) { - - const uint8_t *buf = p_buffer.ptr(); - int total_len = p_buffer.size(); - ERR_FAIL_COND_V(p_buffer.size() < 24 || p_buffer[0] != 'G' || p_buffer[1] != 'D' || p_buffer[2] != 'S' || p_buffer[3] != 'C', ERR_INVALID_DATA); - - int version = decode_uint32(&buf[4]); - ERR_FAIL_COND_V_MSG(version > BYTECODE_VERSION, ERR_INVALID_DATA, "Bytecode is too recent! Please use a newer engine version."); - - int identifier_count = decode_uint32(&buf[8]); - int constant_count = decode_uint32(&buf[12]); - int line_count = decode_uint32(&buf[16]); - int token_count = decode_uint32(&buf[20]); - - const uint8_t *b = &buf[24]; - total_len -= 24; - - identifiers.resize(identifier_count); - for (int i = 0; i < identifier_count; i++) { - - int len = decode_uint32(b); - ERR_FAIL_COND_V(len > total_len, ERR_INVALID_DATA); - b += 4; - Vector<uint8_t> cs; - cs.resize(len); - for (int j = 0; j < len; j++) { - cs.write[j] = b[j] ^ 0xb6; +GDScriptTokenizer::Token GDScriptTokenizer::number() { + int base = 10; + bool has_decimal = false; + bool has_exponent = false; + bool has_error = false; + bool (*digit_check_func)(CharType) = _is_digit; + + if (_peek(-1) == '.') { + has_decimal = true; + } else if (_peek(-1) == '0') { + if (_peek() == 'x') { + // Hexadecimal. + base = 16; + digit_check_func = _is_hex_digit; + _advance(); + } else if (_peek() == 'b') { + // Binary. + base = 2; + digit_check_func = _is_binary_digit; + _advance(); } - - cs.write[cs.size() - 1] = 0; - String s; - s.parse_utf8((const char *)cs.ptr()); - b += len; - total_len -= len + 4; - identifiers.write[i] = s; } - constants.resize(constant_count); - for (int i = 0; i < constant_count; i++) { - - Variant v; - int len; - // An object cannot be constant, never decode objects - Error err = decode_variant(v, b, total_len, &len, false); - if (err) - return err; - b += len; - total_len -= len; - constants.write[i] = v; + // Allow '_' to be used in a number, for readability. + while (digit_check_func(_peek()) || _peek() == '_') { + _advance(); } - ERR_FAIL_COND_V(line_count * 8 > total_len, ERR_INVALID_DATA); - - for (int i = 0; i < line_count; i++) { - - uint32_t token = decode_uint32(b); - b += 4; - uint32_t linecol = decode_uint32(b); - b += 4; + // It might be a ".." token (instead of decimal point) so we check if it's not. + if (_peek() == '.' && _peek(1) != '.') { + if (base == 10 && !has_decimal) { + has_decimal = true; + } else if (base == 10) { + Token error = make_error("Cannot use a decimal point twice in a number."); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + has_error = true; + } else if (base == 16) { + Token error = make_error("Cannot use a decimal point in a hexadecimal number."); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + has_error = true; + } else { + Token error = make_error("Cannot use a decimal point in a binary number."); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + has_error = true; + } + if (!has_error) { + _advance(); - lines.insert(token, linecol); - total_len -= 8; + // Consume decimal digits. + while (_is_digit(_peek()) || _peek() == '_') { + _advance(); + } + } } - - tokens.resize(token_count); - - for (int i = 0; i < token_count; i++) { - - ERR_FAIL_COND_V(total_len < 1, ERR_INVALID_DATA); - - if ((*b) & TOKEN_BYTE_MASK) { //little endian always - ERR_FAIL_COND_V(total_len < 4, ERR_INVALID_DATA); - - tokens.write[i] = decode_uint32(b) & ~TOKEN_BYTE_MASK; - b += 4; - } else { - tokens.write[i] = *b; - b += 1; - total_len--; + if (base == 10) { + if (_peek() == 'e' || _peek() == 'E') { + has_exponent = true; + _advance(); + if (_peek() == '+' || _peek() == '-') { + // Exponent sign. + _advance(); + } + // Consume exponent digits. + while (_is_digit(_peek()) || _peek() == '_') { + _advance(); + } } } - token = 0; + // Detect extra decimal point. + if (!has_error && has_decimal && _peek() == '.' && _peek(1) != '.') { + Token error = make_error("Cannot use a decimal point twice in a number."); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + has_error = true; + } else if (_is_alphanumeric(_peek())) { + // Letter at the end of the number. + push_error("Invalid numeric notation."); + } - return OK; + // Create a string with the whole number. + int length = _current - _start; + String number = String(_start, length).replace("_", ""); + + // Convert to the appropriate literal type. + if (base == 16) { + int64_t value = number.hex_to_int(); + return make_literal(value); + } else if (base == 2) { + int64_t value = number.bin_to_int(); + return make_literal(value); + } else if (has_decimal || has_exponent) { + double value = number.to_float(); + return make_literal(value); + } else { + int64_t value = number.to_int(); + return make_literal(value); + } } -Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code) { +GDScriptTokenizer::Token GDScriptTokenizer::string() { + enum StringType { + STRING_REGULAR, + STRING_NAME, + STRING_NODEPATH, + }; - Vector<uint8_t> buf; + bool is_multiline = false; + StringType type = STRING_REGULAR; - Map<StringName, int> identifier_map; - HashMap<Variant, int, VariantHasher, VariantComparator> constant_map; - Map<uint32_t, int> line_map; - Vector<uint32_t> token_array; + if (_peek(-1) == '&') { + type = STRING_NAME; + _advance(); + } else if (_peek(-1) == '^') { + type = STRING_NODEPATH; + _advance(); + } - GDScriptTokenizerText tt; - tt.set_code(p_code); - int line = -1; + CharType quote_char = _peek(-1); - while (true) { + if (_peek() == quote_char && _peek(1) == quote_char) { + is_multiline = true; + // Consume all quotes. + _advance(); + _advance(); + } - if (tt.get_token_line() != line) { + String result; - line = tt.get_token_line(); - line_map[line] = token_array.size(); + for (;;) { + // Consume actual string. + if (_is_at_end()) { + return make_error("Unterminated string."); } - uint32_t token = tt.get_token(); - switch (tt.get_token()) { + CharType ch = _peek(); - case TK_IDENTIFIER: { - StringName id = tt.get_token_identifier(); - if (!identifier_map.has(id)) { - int idx = identifier_map.size(); - identifier_map[id] = idx; - } - token |= identifier_map[id] << TOKEN_BITS; - } break; - case TK_CONSTANT: { - - const Variant &c = tt.get_token_constant(); - if (!constant_map.has(c)) { - int idx = constant_map.size(); - constant_map[c] = idx; - } - token |= constant_map[c] << TOKEN_BITS; - } break; - case TK_BUILT_IN_TYPE: { + if (ch == '\\') { + // Escape pattern. + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } - token |= tt.get_token_type() << TOKEN_BITS; - } break; - case TK_BUILT_IN_FUNC: { + // Grab escape character. + CharType code = _peek(); + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } - token |= tt.get_token_built_in_func() << TOKEN_BITS; + CharType escaped = 0; + bool valid_escape = true; - } break; - case TK_NEWLINE: { + switch (code) { + case 'a': + escaped = '\a'; + break; + case 'b': + escaped = '\b'; + break; + case 'f': + escaped = '\f'; + break; + case 'n': + escaped = '\n'; + break; + case 'r': + escaped = '\r'; + break; + case 't': + escaped = '\t'; + break; + case 'v': + escaped = '\v'; + break; + case '\'': + escaped = '\''; + break; + case '\"': + escaped = '\"'; + break; + case '\\': + escaped = '\\'; + break; + case 'u': + // Hexadecimal sequence. + for (int i = 0; i < 4; i++) { + if (_is_at_end()) { + return make_error("Unterminated string."); + } - token |= tt.get_token_line_indent() << TOKEN_BITS; - } break; - case TK_ERROR: { + CharType digit = _peek(); + CharType value = 0; + if (digit >= '0' && digit <= '9') { + value = digit - '0'; + } else if (digit >= 'a' && digit <= 'f') { + value = digit - 'a'; + value += 10; + } else if (digit >= 'A' && digit <= 'F') { + value = digit - 'A'; + value += 10; + } else { + // Make error, but keep parsing the string. + Token error = make_error("Invalid hexadecimal digit in unicode escape sequence."); + error.start_column = column; + error.leftmost_column = error.start_column; + error.end_column = column + 1; + error.rightmost_column = error.end_column; + push_error(error); + valid_escape = false; + break; + } + + escaped <<= 4; + escaped |= value; - ERR_FAIL_V(Vector<uint8_t>()); - } break; - default: { + _advance(); + } + break; + case '\r': + if (_peek() != '\n') { + // Carriage return without newline in string. (???) + // Just add it to the string and keep going. + result += ch; + _advance(); + break; + } + [[fallthrough]]; + case '\n': + // Escaping newline. + newline(false); + valid_escape = false; // Don't add to the string. + break; + default: + Token error = make_error("Invalid escape in string."); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + break; } - }; - token_array.push_back(token); + if (valid_escape) { + result += escaped; + } + } else if (ch == quote_char) { + _advance(); + if (is_multiline) { + if (_peek() == quote_char && _peek(1) == quote_char) { + // Ended the multiline string. Consume all quotes. + _advance(); + _advance(); + break; + } + } else { + // Ended single-line string. + break; + } + } else { + result += ch; + _advance(); + if (ch == '\n') { + newline(false); + } + } + } - if (tt.get_token() == TK_EOF) + // Make the literal. + Variant string; + switch (type) { + case STRING_NAME: + string = StringName(result); + break; + case STRING_NODEPATH: + string = NodePath(result); + break; + case STRING_REGULAR: + string = result; break; - tt.advance(); } - //reverse maps + return make_literal(string); +} - Map<int, StringName> rev_identifier_map; - for (Map<StringName, int>::Element *E = identifier_map.front(); E; E = E->next()) { - rev_identifier_map[E->get()] = E->key(); - } +void GDScriptTokenizer::check_indent() { + ERR_FAIL_COND_MSG(column != 1, "Checking tokenizer indentation in the middle of a line."); - Map<int, Variant> rev_constant_map; - const Variant *K = nullptr; - while ((K = constant_map.next(K))) { - rev_constant_map[constant_map[*K]] = *K; + if (_is_at_end()) { + // Send dedents for every indent level. + pending_indents -= indent_level(); + indent_stack.clear(); + return; } - Map<int, uint32_t> rev_line_map; - for (Map<uint32_t, int>::Element *E = line_map.front(); E; E = E->next()) { - rev_line_map[E->get()] = E->key(); - } + for (;;) { + CharType current_indent_char = _peek(); + int indent_count = 0; - //save header - buf.resize(24); - buf.write[0] = 'G'; - buf.write[1] = 'D'; - buf.write[2] = 'S'; - buf.write[3] = 'C'; - encode_uint32(BYTECODE_VERSION, &buf.write[4]); - encode_uint32(identifier_map.size(), &buf.write[8]); - encode_uint32(constant_map.size(), &buf.write[12]); - encode_uint32(line_map.size(), &buf.write[16]); - encode_uint32(token_array.size(), &buf.write[20]); - - //save identifiers - - for (Map<int, StringName>::Element *E = rev_identifier_map.front(); E; E = E->next()) { - - CharString cs = String(E->get()).utf8(); - int len = cs.length() + 1; - int extra = 4 - (len % 4); - if (extra == 4) - extra = 0; - - uint8_t ibuf[4]; - encode_uint32(len + extra, ibuf); - for (int i = 0; i < 4; i++) { - buf.push_back(ibuf[i]); + if (current_indent_char != ' ' && current_indent_char != '\t' && current_indent_char != '\r' && current_indent_char != '\n' && current_indent_char != '#') { + // First character of the line is not whitespace, so we clear all indentation levels. + // Unless we are in a continuation or in multiline mode (inside expression). + if (line_continuation || multiline_mode) { + return; + } + pending_indents -= indent_level(); + indent_stack.clear(); + return; } - for (int i = 0; i < len; i++) { - buf.push_back(cs[i] ^ 0xb6); + + if (_peek() == '\r') { + _advance(); + if (_peek() != '\n') { + push_error("Stray carriage return character in source code."); + } } - for (int i = 0; i < extra; i++) { - buf.push_back(0 ^ 0xb6); + if (_peek() == '\n') { + // Empty line, keep going. + _advance(); + newline(false); + continue; } - } - - for (Map<int, Variant>::Element *E = rev_constant_map.front(); E; E = E->next()) { - - int len; - // Objects cannot be constant, never encode objects - Error err = encode_variant(E->get(), nullptr, len, false); - ERR_FAIL_COND_V_MSG(err != OK, Vector<uint8_t>(), "Error when trying to encode Variant."); - int pos = buf.size(); - buf.resize(pos + len); - encode_variant(E->get(), &buf.write[pos], len, false); - } - for (Map<int, uint32_t>::Element *E = rev_line_map.front(); E; E = E->next()) { - - uint8_t ibuf[8]; - encode_uint32(E->key(), &ibuf[0]); - encode_uint32(E->get(), &ibuf[4]); - for (int i = 0; i < 8; i++) - buf.push_back(ibuf[i]); - } + // Check indent level. + bool mixed = false; + while (!_is_at_end()) { + CharType space = _peek(); + if (space == '\t') { + // Consider individual tab columns. + column += tab_size - 1; + indent_count += tab_size; + } else if (space == ' ') { + indent_count += 1; + } else { + break; + } + mixed = mixed || space != current_indent_char; + _advance(); + } - for (int i = 0; i < token_array.size(); i++) { + if (mixed) { + Token error = make_error("Mixed use of tabs and spaces for indentation."); + error.start_line = line; + error.start_column = 1; + error.leftmost_column = 1; + error.rightmost_column = column; + push_error(error); + } - uint32_t token = token_array[i]; + if (_is_at_end()) { + // Reached the end with an empty line, so just dedent as much as needed. + pending_indents -= indent_level(); + indent_stack.clear(); + return; + } - if (token & ~TOKEN_MASK) { - uint8_t buf4[4]; - encode_uint32(token_array[i] | TOKEN_BYTE_MASK, &buf4[0]); - for (int j = 0; j < 4; j++) { - buf.push_back(buf4[j]); + if (_peek() == '\r') { + _advance(); + if (_peek() != '\n') { + push_error("Stray carriage return character in source code."); } - } else { - buf.push_back(token); } - } - - return buf; -} + if (_peek() == '\n') { + // Empty line, keep going. + _advance(); + newline(false); + continue; + } + if (_peek() == '#') { + // Comment. Advance to the next line. + while (_peek() != '\n' && !_is_at_end()) { + _advance(); + } + if (_is_at_end()) { + // Reached the end with an empty line, so just dedent as much as needed. + pending_indents -= indent_level(); + indent_stack.clear(); + return; + } + _advance(); // Consume '\n'. + newline(false); + continue; + } -GDScriptTokenizerBuffer::Token GDScriptTokenizerBuffer::get_token(int p_offset) const { + if (line_continuation || multiline_mode) { + // We cleared up all the whitespace at the beginning of the line. + // But if this is a continuation or multiline mode and we don't want any indentation change. + return; + } - int offset = token + p_offset; + // Check if indentation character is consistent. + if (indent_char == '\0') { + // First time indenting, choose character now. + indent_char = current_indent_char; + } else if (current_indent_char != indent_char) { + Token error = make_error(vformat("Used \"%c\" for indentation instead \"%c\" as used before in the file.", String(¤t_indent_char, 1).c_escape(), String(&indent_char, 1).c_escape())); + error.start_line = line; + error.start_column = 1; + error.leftmost_column = 1; + error.rightmost_column = column; + push_error(error); + } - if (offset < 0 || offset >= tokens.size()) - return TK_EOF; + // Now we can do actual indentation changes. - return GDScriptTokenizerBuffer::Token(tokens[offset] & TOKEN_MASK); + // Check if indent or dedent. + int previous_indent = 0; + if (indent_level() > 0) { + previous_indent = indent_stack.back()->get(); + } + if (indent_count == previous_indent) { + // No change in indentation. + return; + } + if (indent_count > previous_indent) { + // Indentation increased. + indent_stack.push_back(indent_count); + pending_indents++; + } else { + // Indentation decreased (dedent). + if (indent_level() == 0) { + push_error("Tokenizer bug: trying to dedent without previous indent."); + return; + } + while (indent_level() > 0 && indent_stack.back()->get() > indent_count) { + indent_stack.pop_back(); + pending_indents--; + } + if ((indent_level() > 0 && indent_stack.back()->get() != indent_count) || (indent_level() == 0 && indent_count != 0)) { + // Mismatched indentation alignment. + Token error = make_error("Unindent doesn't match the previous indentation level."); + error.start_line = line; + error.start_column = 1; + error.leftmost_column = 1; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + // Still, we'll be lenient and keep going, so keep this level in the stack. + indent_stack.push_back(indent_count); + } + } + break; // Get out of the loop in any case. + } } -StringName GDScriptTokenizerBuffer::get_token_identifier(int p_offset) const { - - int offset = token + p_offset; - - ERR_FAIL_INDEX_V(offset, tokens.size(), StringName()); - uint32_t identifier = tokens[offset] >> TOKEN_BITS; - ERR_FAIL_UNSIGNED_INDEX_V(identifier, (uint32_t)identifiers.size(), StringName()); +void GDScriptTokenizer::_skip_whitespace() { + if (pending_indents != 0) { + // Still have some indent/dedent tokens to give. + return; + } - return identifiers[identifier]; -} + bool is_bol = column == 1; // Beginning of line. -GDScriptFunctions::Function GDScriptTokenizerBuffer::get_token_built_in_func(int p_offset) const { + if (is_bol) { + check_indent(); + return; + } - int offset = token + p_offset; - ERR_FAIL_INDEX_V(offset, tokens.size(), GDScriptFunctions::FUNC_MAX); - return GDScriptFunctions::Function(tokens[offset] >> TOKEN_BITS); + for (;;) { + CharType c = _peek(); + switch (c) { + case ' ': + _advance(); + break; + case '\t': + _advance(); + // Consider individual tab columns. + column += tab_size - 1; + break; + case '\r': + _advance(); // Consume either way. + if (_peek() != '\n') { + push_error("Stray carriage return character in source code."); + return; + } + break; + case '\n': + _advance(); + newline(!is_bol); // Don't create new line token if line is empty. + check_indent(); + break; + case '#': + // Comment. + while (_peek() != '\n' && !_is_at_end()) { + _advance(); + } + if (_is_at_end()) { + return; + } + _advance(); // Consume '\n' + newline(!is_bol); + check_indent(); + break; + default: + return; + } + } } -Variant::Type GDScriptTokenizerBuffer::get_token_type(int p_offset) const { - - int offset = token + p_offset; - ERR_FAIL_INDEX_V(offset, tokens.size(), Variant::NIL); +GDScriptTokenizer::Token GDScriptTokenizer::scan() { + if (has_error()) { + return pop_error(); + } - return Variant::Type(tokens[offset] >> TOKEN_BITS); -} + _skip_whitespace(); -int GDScriptTokenizerBuffer::get_token_line(int p_offset) const { + if (pending_newline) { + pending_newline = false; + if (!multiline_mode) { + // Don't return newline tokens on multine mode. + return last_newline; + } + } - int offset = token + p_offset; - int pos = lines.find_nearest(offset); + // Check for potential errors after skipping whitespace(). + if (has_error()) { + return pop_error(); + } - if (pos < 0) - return -1; - if (pos >= lines.size()) - pos = lines.size() - 1; + _start = _current; + start_line = line; + start_column = column; + leftmost_column = column; + rightmost_column = column; + + if (pending_indents != 0) { + // Adjust position for indent. + _start -= start_column - 1; + start_column = 1; + leftmost_column = 1; + if (pending_indents > 0) { + // Indents. + pending_indents--; + return make_token(Token::INDENT); + } else { + // Dedents. + pending_indents++; + Token dedent = make_token(Token::DEDENT); + dedent.end_column += 1; + dedent.rightmost_column += 1; + return dedent; + } + } - uint32_t l = lines.getv(pos); - return l & TOKEN_LINE_MASK; -} -int GDScriptTokenizerBuffer::get_token_column(int p_offset) const { + if (_is_at_end()) { + return make_token(Token::TK_EOF); + } - int offset = token + p_offset; - int pos = lines.find_nearest(offset); - if (pos < 0) - return -1; - if (pos >= lines.size()) - pos = lines.size() - 1; + const CharType c = _advance(); - uint32_t l = lines.getv(pos); - return l >> TOKEN_LINE_BITS; -} -int GDScriptTokenizerBuffer::get_token_line_indent(int p_offset) const { + if (c == '\\') { + // Line continuation with backslash. + if (_peek() == '\r') { + if (_peek(1) != '\n') { + return make_error("Unexpected carriage return character."); + } + _advance(); + } + if (_peek() != '\n') { + return make_error("Expected new line after \"\\\"."); + } + _advance(); + newline(false); + line_continuation = true; + return scan(); // Recurse to get next token. + } - int offset = token + p_offset; - ERR_FAIL_INDEX_V(offset, tokens.size(), 0); - return tokens[offset] >> TOKEN_BITS; -} -const Variant &GDScriptTokenizerBuffer::get_token_constant(int p_offset) const { + line_continuation = false; - int offset = token + p_offset; - ERR_FAIL_INDEX_V(offset, tokens.size(), nil); - uint32_t constant = tokens[offset] >> TOKEN_BITS; - ERR_FAIL_UNSIGNED_INDEX_V(constant, (uint32_t)constants.size(), nil); - return constants[constant]; -} -String GDScriptTokenizerBuffer::get_token_error(int p_offset) const { + if (_is_digit(c)) { + return number(); + } else if (_is_alphanumeric(c)) { + return potential_identifier(); + } - ERR_FAIL_V(String()); -} + switch (c) { + // String literals. + case '"': + case '\'': + return string(); + + // Annotation. + case '@': + return annotation(); + + // Single characters. + case '~': + return make_token(Token::TILDE); + case ',': + return make_token(Token::COMMA); + case ':': + return make_token(Token::COLON); + case ';': + return make_token(Token::SEMICOLON); + case '$': + return make_token(Token::DOLLAR); + case '?': + return make_token(Token::QUESTION_MARK); + case '`': + return make_token(Token::BACKTICK); + + // Parens. + case '(': + push_paren('('); + return make_token(Token::PARENTHESIS_OPEN); + case '[': + push_paren('['); + return make_token(Token::BRACKET_OPEN); + case '{': + push_paren('{'); + return make_token(Token::BRACE_OPEN); + case ')': + if (!pop_paren('(')) { + return make_paren_error(c); + } + return make_token(Token::PARENTHESIS_CLOSE); + case ']': + if (!pop_paren('[')) { + return make_paren_error(c); + } + return make_token(Token::BRACKET_CLOSE); + case '}': + if (!pop_paren('{')) { + return make_paren_error(c); + } + return make_token(Token::BRACE_CLOSE); + + // Double characters. + case '!': + if (_peek() == '=') { + _advance(); + return make_token(Token::BANG_EQUAL); + } else { + return make_token(Token::BANG); + } + case '.': + if (_peek() == '.') { + _advance(); + return make_token(Token::PERIOD_PERIOD); + } else if (_is_digit(_peek())) { + // Number starting with '.'. + return number(); + } else { + return make_token(Token::PERIOD); + } + case '+': + if (_peek() == '=') { + _advance(); + return make_token(Token::PLUS_EQUAL); + } else { + return make_token(Token::PLUS); + } + case '-': + if (_peek() == '=') { + _advance(); + return make_token(Token::MINUS_EQUAL); + } else if (_peek() == '>') { + _advance(); + return make_token(Token::FORWARD_ARROW); + } else { + return make_token(Token::MINUS); + } + case '*': + if (_peek() == '=') { + _advance(); + return make_token(Token::STAR_EQUAL); + } else { + return make_token(Token::STAR); + } + case '/': + if (_peek() == '=') { + _advance(); + return make_token(Token::SLASH_EQUAL); + } else { + return make_token(Token::SLASH); + } + case '%': + if (_peek() == '=') { + _advance(); + return make_token(Token::PERCENT_EQUAL); + } else { + return make_token(Token::PERCENT); + } + case '^': + if (_peek() == '=') { + _advance(); + return make_token(Token::CARET_EQUAL); + } else if (_peek() == '"' || _peek() == '\'') { + // Node path + return string(); + } else { + return make_token(Token::CARET); + } + case '&': + if (_peek() == '&') { + _advance(); + return make_token(Token::AMPERSAND_AMPERSAND); + } else if (_peek() == '=') { + _advance(); + return make_token(Token::AMPERSAND_EQUAL); + } else if (_peek() == '"' || _peek() == '\'') { + // String Name + return string(); + } else { + return make_token(Token::AMPERSAND); + } + case '|': + if (_peek() == '|') { + _advance(); + return make_token(Token::PIPE_PIPE); + } else if (_peek() == '=') { + _advance(); + return make_token(Token::PIPE_EQUAL); + } else { + return make_token(Token::PIPE); + } -void GDScriptTokenizerBuffer::advance(int p_amount) { + // Potential VCS conflict markers. + case '=': + if (_peek() == '=') { + return check_vcs_marker('=', Token::EQUAL_EQUAL); + } else { + return make_token(Token::EQUAL); + } + case '<': + if (_peek() == '=') { + _advance(); + return make_token(Token::LESS_EQUAL); + } else if (_peek() == '<') { + if (_peek(1) == '=') { + _advance(); + _advance(); // Advance both '<' and '=' + return make_token(Token::LESS_LESS_EQUAL); + } else { + return check_vcs_marker('<', Token::LESS_LESS); + } + } else { + return make_token(Token::LESS); + } + case '>': + if (_peek() == '=') { + _advance(); + return make_token(Token::GREATER_EQUAL); + } else if (_peek() == '>') { + if (_peek(1) == '=') { + _advance(); + _advance(); // Advance both '>' and '=' + return make_token(Token::GREATER_GREATER_EQUAL); + } else { + return check_vcs_marker('>', Token::GREATER_GREATER); + } + } else { + return make_token(Token::GREATER); + } - ERR_FAIL_INDEX(p_amount + token, tokens.size()); - token += p_amount; + default: + return make_error(vformat(R"(Unknown character "%s".")", String(&c, 1))); + } } -GDScriptTokenizerBuffer::GDScriptTokenizerBuffer() { - token = 0; +GDScriptTokenizer::GDScriptTokenizer() { +#ifdef TOOLS_ENABLED + if (EditorSettings::get_singleton()) { + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + } +#endif // TOOLS_ENABLED } diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 1b432ae8c1..059a226924 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -31,269 +31,221 @@ #ifndef GDSCRIPT_TOKENIZER_H #define GDSCRIPT_TOKENIZER_H -#include "core/pair.h" -#include "core/string_name.h" -#include "core/ustring.h" +#include "core/list.h" +#include "core/set.h" #include "core/variant.h" -#include "core/vmap.h" -#include "gdscript_functions.h" +#include "core/vector.h" class GDScriptTokenizer { public: - enum Token { - - TK_EMPTY, - TK_IDENTIFIER, - TK_CONSTANT, - TK_SELF, - TK_BUILT_IN_TYPE, - TK_BUILT_IN_FUNC, - TK_OP_IN, - TK_OP_EQUAL, - TK_OP_NOT_EQUAL, - TK_OP_LESS, - TK_OP_LESS_EQUAL, - TK_OP_GREATER, - TK_OP_GREATER_EQUAL, - TK_OP_AND, - TK_OP_OR, - TK_OP_NOT, - TK_OP_ADD, - TK_OP_SUB, - TK_OP_MUL, - TK_OP_DIV, - TK_OP_MOD, - TK_OP_SHIFT_LEFT, - TK_OP_SHIFT_RIGHT, - TK_OP_ASSIGN, - TK_OP_ASSIGN_ADD, - TK_OP_ASSIGN_SUB, - TK_OP_ASSIGN_MUL, - TK_OP_ASSIGN_DIV, - TK_OP_ASSIGN_MOD, - TK_OP_ASSIGN_SHIFT_LEFT, - TK_OP_ASSIGN_SHIFT_RIGHT, - TK_OP_ASSIGN_BIT_AND, - TK_OP_ASSIGN_BIT_OR, - TK_OP_ASSIGN_BIT_XOR, - TK_OP_BIT_AND, - TK_OP_BIT_OR, - TK_OP_BIT_XOR, - TK_OP_BIT_INVERT, - //TK_OP_PLUS_PLUS, - //TK_OP_MINUS_MINUS, - TK_CF_IF, - TK_CF_ELIF, - TK_CF_ELSE, - TK_CF_FOR, - TK_CF_WHILE, - TK_CF_BREAK, - TK_CF_CONTINUE, - TK_CF_PASS, - TK_CF_RETURN, - TK_CF_MATCH, - TK_PR_FUNCTION, - TK_PR_CLASS, - TK_PR_CLASS_NAME, - TK_PR_EXTENDS, - TK_PR_IS, - TK_PR_ONREADY, - TK_PR_TOOL, - TK_PR_STATIC, - TK_PR_EXPORT, - TK_PR_SETGET, - TK_PR_CONST, - TK_PR_VAR, - TK_PR_AS, - TK_PR_VOID, - TK_PR_ENUM, - TK_PR_PRELOAD, - TK_PR_ASSERT, - TK_PR_YIELD, - TK_PR_SIGNAL, - TK_PR_BREAKPOINT, - TK_PR_REMOTE, - TK_PR_MASTER, - TK_PR_PUPPET, - TK_PR_REMOTESYNC, - TK_PR_MASTERSYNC, - TK_PR_PUPPETSYNC, - TK_BRACKET_OPEN, - TK_BRACKET_CLOSE, - TK_CURLY_BRACKET_OPEN, - TK_CURLY_BRACKET_CLOSE, - TK_PARENTHESIS_OPEN, - TK_PARENTHESIS_CLOSE, - TK_COMMA, - TK_SEMICOLON, - TK_PERIOD, - TK_QUESTION_MARK, - TK_COLON, - TK_DOLLAR, - TK_FORWARD_ARROW, - TK_NEWLINE, - TK_CONST_PI, - TK_CONST_TAU, - TK_WILDCARD, - TK_CONST_INF, - TK_CONST_NAN, - TK_ERROR, - TK_EOF, - TK_CURSOR, //used for code completion - TK_MAX - }; - -protected: - enum StringMode { - STRING_SINGLE_QUOTE, - STRING_DOUBLE_QUOTE, - STRING_MULTILINE + enum CursorPlace { + CURSOR_NONE, + CURSOR_BEGINNING, + CURSOR_MIDDLE, + CURSOR_END, }; - static const char *token_names[TK_MAX]; - -public: - static const char *get_token_name(Token p_token); - - bool is_token_literal(int p_offset = 0, bool variable_safe = false) const; - StringName get_token_literal(int p_offset = 0) const; - - virtual const Variant &get_token_constant(int p_offset = 0) const = 0; - virtual Token get_token(int p_offset = 0) const = 0; - virtual StringName get_token_identifier(int p_offset = 0) const = 0; - virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const = 0; - virtual Variant::Type get_token_type(int p_offset = 0) const = 0; - virtual int get_token_line(int p_offset = 0) const = 0; - virtual int get_token_column(int p_offset = 0) const = 0; - virtual int get_token_line_indent(int p_offset = 0) const = 0; - virtual int get_token_line_tab_indent(int p_offset = 0) const = 0; - virtual String get_token_error(int p_offset = 0) const = 0; - virtual void advance(int p_amount = 1) = 0; -#ifdef DEBUG_ENABLED - virtual const Vector<Pair<int, String>> &get_warning_skips() const = 0; - virtual const Set<String> &get_warning_global_skips() const = 0; - virtual bool is_ignoring_warnings() const = 0; -#endif // DEBUG_ENABLED - - virtual ~GDScriptTokenizer(){}; -}; - -class GDScriptTokenizerText : public GDScriptTokenizer { - - enum { - MAX_LOOKAHEAD = 4, - TK_RB_SIZE = MAX_LOOKAHEAD * 2 + 1 + struct Token { + enum Type { + EMPTY, + // Basic + ANNOTATION, + IDENTIFIER, + LITERAL, + // Comparison + LESS, + LESS_EQUAL, + GREATER, + GREATER_EQUAL, + EQUAL_EQUAL, + BANG_EQUAL, + // Logical + AND, + OR, + NOT, + AMPERSAND_AMPERSAND, + PIPE_PIPE, + BANG, + // Bitwise + AMPERSAND, + PIPE, + TILDE, + CARET, + LESS_LESS, + GREATER_GREATER, + // Math + PLUS, + MINUS, + STAR, + SLASH, + PERCENT, + // Assignment + EQUAL, + PLUS_EQUAL, + MINUS_EQUAL, + STAR_EQUAL, + SLASH_EQUAL, + PERCENT_EQUAL, + LESS_LESS_EQUAL, + GREATER_GREATER_EQUAL, + AMPERSAND_EQUAL, + PIPE_EQUAL, + CARET_EQUAL, + // Control flow + IF, + ELIF, + ELSE, + FOR, + WHILE, + BREAK, + CONTINUE, + PASS, + RETURN, + MATCH, + // Keywords + AS, + ASSERT, + AWAIT, + BREAKPOINT, + CLASS, + CLASS_NAME, + CONST, + ENUM, + EXTENDS, + FUNC, + IN, + IS, + NAMESPACE, + PRELOAD, + SELF, + SIGNAL, + STATIC, + SUPER, + TRAIT, + VAR, + VOID, + YIELD, + // Punctuation + BRACKET_OPEN, + BRACKET_CLOSE, + BRACE_OPEN, + BRACE_CLOSE, + PARENTHESIS_OPEN, + PARENTHESIS_CLOSE, + COMMA, + SEMICOLON, + PERIOD, + PERIOD_PERIOD, + COLON, + DOLLAR, + FORWARD_ARROW, + UNDERSCORE, + // Whitespace + NEWLINE, + INDENT, + DEDENT, + // Constants + CONST_PI, + CONST_TAU, + CONST_INF, + CONST_NAN, + // Error message improvement + VCS_CONFLICT_MARKER, + BACKTICK, + QUESTION_MARK, + // Special + ERROR, + TK_EOF, // "EOF" is reserved + TK_MAX + }; - }; + Type type = EMPTY; + Variant literal; + int start_line = 0, end_line = 0, start_column = 0, end_column = 0; + int leftmost_column = 0, rightmost_column = 0; // Column span for multiline tokens. + int cursor_position = -1; + CursorPlace cursor_place = CURSOR_NONE; + String source; + + const char *get_name() const; + // TODO: Allow some keywords as identifiers? + bool is_identifier() const { return type == IDENTIFIER; } + StringName get_identifier() const { return literal; } + + Token(Type p_type) { + type = p_type; + } - struct TokenData { - Token type; - StringName identifier; //for identifier types - Variant constant; //for constant types - union { - Variant::Type vtype; //for type types - GDScriptFunctions::Function func; //function for built in functions - int warning_code; //for warning skip - }; - int line, col; - TokenData() { - type = TK_EMPTY; - line = col = 0; - vtype = Variant::NIL; + Token() { + type = EMPTY; } }; - void _make_token(Token p_type); - void _make_newline(int p_indentation = 0, int p_tabs = 0); - void _make_identifier(const StringName &p_identifier); - void _make_built_in_func(GDScriptFunctions::Function p_func); - void _make_constant(const Variant &p_constant); - void _make_type(const Variant::Type &p_type); - void _make_error(const String &p_error); - - String code; - int len; - int code_pos; - const CharType *_code; - int line; - int column; - TokenData tk_rb[TK_RB_SIZE * 2 + 1]; - int tk_rb_pos; - String last_error; - bool error_flag; - -#ifdef DEBUG_ENABLED - Vector<Pair<int, String>> warning_skips; - Set<String> warning_global_skips; - bool ignore_warnings; -#endif // DEBUG_ENABLED - - void _advance(); +private: + String source; + const CharType *_source = nullptr; + const CharType *_current = nullptr; + int line = -1, column = -1; + int cursor_line = -1, cursor_column = -1; + int tab_size = 4; + + // Keep track of multichar tokens. + const CharType *_start = nullptr; + int start_line = 0, start_column = 0; + int leftmost_column = 0, rightmost_column = 0; + + // Info cache. + bool line_continuation = false; // Whether this line is a continuation of the previous, like when using '\'. + bool multiline_mode = false; + List<Token> error_stack; + bool pending_newline = false; + Token last_newline; + int pending_indents = 0; + List<int> indent_stack; + List<CharType> paren_stack; + CharType indent_char = '\0'; + int position = 0; + int length = 0; + + _FORCE_INLINE_ bool _is_at_end() { return position >= length; } + _FORCE_INLINE_ CharType _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; } + int indent_level() const { return indent_stack.size(); } + bool has_error() const { return !error_stack.empty(); } + Token pop_error(); + CharType _advance(); + void _skip_whitespace(); + void check_indent(); + + Token make_error(const String &p_message); + void push_error(const String &p_message); + void push_error(const Token &p_error); + Token make_paren_error(CharType p_paren); + Token make_token(Token::Type p_type); + Token make_literal(const Variant &p_literal); + Token make_identifier(const StringName &p_identifier); + Token check_vcs_marker(CharType p_test, Token::Type p_double_type); + void push_paren(CharType p_char); + bool pop_paren(CharType p_expected); + + void newline(bool p_make_token); + Token number(); + Token potential_identifier(); + Token string(); + Token annotation(); public: - void set_code(const String &p_code); - virtual Token get_token(int p_offset = 0) const; - virtual StringName get_token_identifier(int p_offset = 0) const; - virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const; - virtual Variant::Type get_token_type(int p_offset = 0) const; - virtual int get_token_line(int p_offset = 0) const; - virtual int get_token_column(int p_offset = 0) const; - virtual int get_token_line_indent(int p_offset = 0) const; - virtual int get_token_line_tab_indent(int p_offset = 0) const; - virtual const Variant &get_token_constant(int p_offset = 0) const; - virtual String get_token_error(int p_offset = 0) const; - virtual void advance(int p_amount = 1); -#ifdef DEBUG_ENABLED - virtual const Vector<Pair<int, String>> &get_warning_skips() const { return warning_skips; } - virtual const Set<String> &get_warning_global_skips() const { return warning_global_skips; } - virtual bool is_ignoring_warnings() const { return ignore_warnings; } -#endif // DEBUG_ENABLED -}; - -class GDScriptTokenizerBuffer : public GDScriptTokenizer { + Token scan(); - enum { + void set_source_code(const String &p_source_code); - TOKEN_BYTE_MASK = 0x80, - TOKEN_BITS = 8, - TOKEN_MASK = (1 << TOKEN_BITS) - 1, - TOKEN_LINE_BITS = 24, - TOKEN_LINE_MASK = (1 << TOKEN_LINE_BITS) - 1, - }; + int get_cursor_line() const; + int get_cursor_column() const; + void set_cursor_position(int p_line, int p_column); + void set_multiline_mode(bool p_state); + bool is_past_cursor() const; + static String get_token_name(Token::Type p_token_type); - Vector<StringName> identifiers; - Vector<Variant> constants; - VMap<uint32_t, uint32_t> lines; - Vector<uint32_t> tokens; - Variant nil; - int token; - -public: - Error set_code_buffer(const Vector<uint8_t> &p_buffer); - static Vector<uint8_t> parse_code_string(const String &p_code); - virtual Token get_token(int p_offset = 0) const; - virtual StringName get_token_identifier(int p_offset = 0) const; - virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const; - virtual Variant::Type get_token_type(int p_offset = 0) const; - virtual int get_token_line(int p_offset = 0) const; - virtual int get_token_column(int p_offset = 0) const; - virtual int get_token_line_indent(int p_offset = 0) const; - virtual int get_token_line_tab_indent(int p_offset = 0) const { return 0; } - virtual const Variant &get_token_constant(int p_offset = 0) const; - virtual String get_token_error(int p_offset = 0) const; - virtual void advance(int p_amount = 1); -#ifdef DEBUG_ENABLED - virtual const Vector<Pair<int, String>> &get_warning_skips() const { - static Vector<Pair<int, String>> v; - return v; - } - virtual const Set<String> &get_warning_global_skips() const { - static Set<String> s; - return s; - } - virtual bool is_ignoring_warnings() const { return true; } -#endif // DEBUG_ENABLED - GDScriptTokenizerBuffer(); + GDScriptTokenizer(); }; -#endif // GDSCRIPT_TOKENIZER_H +#endif diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp new file mode 100644 index 0000000000..105facd9d0 --- /dev/null +++ b/modules/gdscript/gdscript_warning.cpp @@ -0,0 +1,210 @@ +/*************************************************************************/ +/* gdscript_warning.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_warning.h" + +#include "core/variant.h" + +#ifdef DEBUG_ENABLED + +String GDScriptWarning::get_message() const { +#define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String()); + + switch (code) { + case UNASSIGNED_VARIABLE_OP_ASSIGN: { + CHECK_SYMBOLS(1); + return "Using assignment with operation but the variable '" + symbols[0] + "' was not previously assigned a value."; + } break; + case UNASSIGNED_VARIABLE: { + CHECK_SYMBOLS(1); + return "The variable '" + symbols[0] + "' was used but never assigned a value."; + } break; + case UNUSED_VARIABLE: { + CHECK_SYMBOLS(1); + return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'"; + } break; + case UNUSED_LOCAL_CONSTANT: { + CHECK_SYMBOLS(1); + return "The local constant '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'"; + } break; + case SHADOWED_VARIABLE: { + CHECK_SYMBOLS(4); + return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]); + } break; + case SHADOWED_VARIABLE_BASE_CLASS: { + CHECK_SYMBOLS(4); + return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]); + } break; + case UNUSED_PRIVATE_CLASS_VARIABLE: { + CHECK_SYMBOLS(1); + return "The class variable '" + symbols[0] + "' is declared but never used in the script."; + } break; + case UNUSED_PARAMETER: { + CHECK_SYMBOLS(2); + return "The parameter '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'"; + } break; + case UNREACHABLE_CODE: { + CHECK_SYMBOLS(1); + return "Unreachable code (statement after return) in function '" + symbols[0] + "()'."; + } break; + case UNREACHABLE_PATTERN: { + return "Unreachable pattern (pattern after wildcard or bind)."; + } break; + case STANDALONE_EXPRESSION: { + return "Standalone expression (the line has no effect)."; + } break; + case VOID_ASSIGNMENT: { + CHECK_SYMBOLS(1); + return "Assignment operation, but the function '" + symbols[0] + "()' returns void."; + } break; + case NARROWING_CONVERSION: { + return "Narrowing conversion (float is converted to int and loses precision)."; + } break; + case INCOMPATIBLE_TERNARY: { + return "Values of the ternary conditional are not mutually compatible."; + } break; + case UNUSED_SIGNAL: { + CHECK_SYMBOLS(1); + return "The signal '" + symbols[0] + "' is declared but never emitted."; + } break; + case RETURN_VALUE_DISCARDED: { + CHECK_SYMBOLS(1); + return "The function '" + symbols[0] + "()' returns a value, but this value is never used."; + } break; + case PROPERTY_USED_AS_FUNCTION: { + CHECK_SYMBOLS(2); + return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a property with the same name. Did you mean to access it?"; + } break; + case CONSTANT_USED_AS_FUNCTION: { + CHECK_SYMBOLS(2); + return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a constant with the same name. Did you mean to access it?"; + } break; + case FUNCTION_USED_AS_PROPERTY: { + CHECK_SYMBOLS(2); + return "The property '" + symbols[0] + "' was not found in base '" + symbols[1] + "' but there's a method with the same name. Did you mean to call it?"; + } break; + case INTEGER_DIVISION: { + return "Integer division, decimal part will be discarded."; + } break; + case UNSAFE_PROPERTY_ACCESS: { + CHECK_SYMBOLS(2); + return "The property '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype)."; + } break; + case UNSAFE_METHOD_ACCESS: { + CHECK_SYMBOLS(2); + return "The method '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype)."; + } break; + case UNSAFE_CAST: { + CHECK_SYMBOLS(1); + return "The value is cast to '" + symbols[0] + "' but has an unknown type."; + } break; + case UNSAFE_CALL_ARGUMENT: { + CHECK_SYMBOLS(4); + return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided"; + } break; + case DEPRECATED_KEYWORD: { + CHECK_SYMBOLS(2); + return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'."; + } break; + case STANDALONE_TERNARY: { + return "Standalone ternary conditional operator: the return value is being discarded."; + } + case ASSERT_ALWAYS_TRUE: { + return "Assert statement is redundant because the expression is always true."; + } + case ASSERT_ALWAYS_FALSE: { + return "Assert statement will raise an error because the expression is always false."; + } + case REDUNDANT_AWAIT: { + return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)"; + } + case WARNING_MAX: + break; // Can't happen, but silences warning + } + ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + "."); + +#undef CHECK_SYMBOLS +} + +String GDScriptWarning::get_name() const { + return get_name_from_code(code); +} + +String GDScriptWarning::get_name_from_code(Code p_code) { + ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String()); + + static const char *names[] = { + "UNASSIGNED_VARIABLE", + "UNASSIGNED_VARIABLE_OP_ASSIGN", + "UNUSED_VARIABLE", + "UNUSED_LOCAL_CONSTANT", + "SHADOWED_VARIABLE", + "SHADOWED_VARIABLE_BASE_CLASS", + "UNUSED_PRIVATE_CLASS_VARIABLE", + "UNUSED_PARAMETER", + "UNREACHABLE_CODE", + "UNREACHABLE_PATTERN", + "STANDALONE_EXPRESSION", + "VOID_ASSIGNMENT", + "NARROWING_CONVERSION", + "INCOMPATIBLE_TERNARY", + "UNUSED_SIGNAL", + "RETURN_VALUE_DISCARDED", + "PROPERTY_USED_AS_FUNCTION", + "CONSTANT_USED_AS_FUNCTION", + "FUNCTION_USED_AS_PROPERTY", + "INTEGER_DIVISION", + "UNSAFE_PROPERTY_ACCESS", + "UNSAFE_METHOD_ACCESS", + "UNSAFE_CAST", + "UNSAFE_CALL_ARGUMENT", + "DEPRECATED_KEYWORD", + "STANDALONE_TERNARY", + "ASSERT_ALWAYS_TRUE", + "ASSERT_ALWAYS_FALSE", + "REDUNDANT_AWAIT", + }; + + static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names."); + + return names[(int)p_code]; +} + +GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) { + for (int i = 0; i < WARNING_MAX; i++) { + if (get_name_from_code((Code)i) == p_name) { + return (Code)i; + } + } + + ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name); +} + +#endif // DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h new file mode 100644 index 0000000000..e183d6f302 --- /dev/null +++ b/modules/gdscript/gdscript_warning.h @@ -0,0 +1,87 @@ +/*************************************************************************/ +/* gdscript_warning.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_WARNINGS +#define GDSCRIPT_WARNINGS + +#ifdef DEBUG_ENABLED + +#include "core/ustring.h" +#include "core/vector.h" + +class GDScriptWarning { +public: + enum Code { + UNASSIGNED_VARIABLE, // Variable used but never assigned. + UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc). + UNUSED_VARIABLE, // Local variable is declared but never used. + UNUSED_LOCAL_CONSTANT, // Local constant is declared but never used. + SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class. + SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class. + UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the file. + UNUSED_PARAMETER, // Function parameter is never used. + UNREACHABLE_CODE, // Code after a return statement. + UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind). + STANDALONE_EXPRESSION, // Expression not assigned to a variable. + VOID_ASSIGNMENT, // Function returns void but it's assigned to a variable. + NARROWING_CONVERSION, // Float value into an integer slot, precision is lost. + INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible. + UNUSED_SIGNAL, // Signal is defined but never emitted. + RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used. + PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name. + CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name. + FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name. + INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded. + UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes). + UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes). + UNSAFE_CAST, // Cast used in an unknown type. + UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument. + DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced. + STANDALONE_TERNARY, // Return value of ternary expression is discarded. + ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. + ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false. + REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). + WARNING_MAX, + }; + + Code code = WARNING_MAX; + int start_line = -1, end_line = -1; + int leftmost_column = -1, rightmost_column = -1; + Vector<String> symbols; + + String get_name() const; + String get_message() const; + static String get_name_from_code(Code p_code); + static Code get_code_from_name(const String &p_name); +}; + +#endif // DEBUG_ENABLED + +#endif // GDSCRIPT_WARNINGS diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index b2c6b0e1ab..ae7898fdf2 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -29,24 +29,27 @@ /*************************************************************************/ #include "gdscript_extend_parser.h" + #include "../gdscript.h" +#include "../gdscript_analyzer.h" #include "core/io/json.h" #include "gdscript_language_protocol.h" #include "gdscript_workspace.h" void ExtendGDScriptParser::update_diagnostics() { - diagnostics.clear(); - if (has_error()) { + const List<ParserError> &errors = get_errors(); + for (const List<ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { + const ParserError &error = E->get(); lsp::Diagnostic diagnostic; diagnostic.severity = lsp::DiagnosticSeverity::Error; - diagnostic.message = get_error(); + diagnostic.message = error.message; diagnostic.source = "gdscript"; diagnostic.code = -1; lsp::Range range; lsp::Position pos; - int line = LINE_NUMBER_TO_INDEX(get_error_line()); + int line = LINE_NUMBER_TO_INDEX(error.line); const String &line_text = get_lines()[line]; pos.line = line; pos.character = line_text.length() - line_text.strip_edges(true, false).length(); @@ -62,12 +65,12 @@ void ExtendGDScriptParser::update_diagnostics() { const GDScriptWarning &warning = E->get(); lsp::Diagnostic diagnostic; diagnostic.severity = lsp::DiagnosticSeverity::Warning; - diagnostic.message = warning.get_message(); + diagnostic.message = "(" + warning.get_name() + "): " + warning.get_message(); diagnostic.source = "gdscript"; diagnostic.code = warning.code; lsp::Range range; lsp::Position pos; - int line = LINE_NUMBER_TO_INDEX(warning.line); + int line = LINE_NUMBER_TO_INDEX(warning.start_line); const String &line_text = get_lines()[line]; pos.line = line; pos.character = line_text.length() - line_text.strip_edges(true, false).length(); @@ -80,12 +83,10 @@ void ExtendGDScriptParser::update_diagnostics() { } void ExtendGDScriptParser::update_symbols() { - members.clear(); - const GDScriptParser::Node *head = get_parse_tree(); + const GDScriptParser::Node *head = get_tree(); if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) { - parse_class_symbol(gdclass, class_symbol); for (int i = 0; i < class_symbol.children.size(); i++) { @@ -108,15 +109,15 @@ void ExtendGDScriptParser::update_symbols() { void ExtendGDScriptParser::update_document_links(const String &p_code) { document_links.clear(); - GDScriptTokenizerText tokenizer; + GDScriptTokenizer tokenizer; FileAccessRef fs = FileAccess::create(FileAccess::ACCESS_RESOURCES); - tokenizer.set_code(p_code); + tokenizer.set_source_code(p_code); while (true) { - GDScriptTokenizerText::Token token = tokenizer.get_token(); - if (token == GDScriptTokenizer::TK_EOF || token == GDScriptTokenizer::TK_ERROR) { + GDScriptTokenizer::Token token = tokenizer.scan(); + if (token.type == GDScriptTokenizer::Token::TK_EOF) { break; - } else if (token == GDScriptTokenizer::TK_CONSTANT) { - const Variant &const_val = tokenizer.get_token_constant(); + } else if (token.type == GDScriptTokenizer::Token::LITERAL) { + const Variant &const_val = token.literal; if (const_val.get_type() == Variant::STRING) { String path = const_val; bool exists = fs->file_exists(path); @@ -128,239 +129,255 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) { String value = const_val; lsp::DocumentLink link; link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path); - link.range.start.line = LINE_NUMBER_TO_INDEX(tokenizer.get_token_line()); - link.range.end.line = link.range.start.line; - link.range.end.character = LINE_NUMBER_TO_INDEX(tokenizer.get_token_column()); - link.range.start.character = link.range.end.character - value.length(); + link.range.start.line = LINE_NUMBER_TO_INDEX(token.start_line); + link.range.end.line = LINE_NUMBER_TO_INDEX(token.end_line); + link.range.start.character = LINE_NUMBER_TO_INDEX(token.start_column); + link.range.end.character = LINE_NUMBER_TO_INDEX(token.end_column); document_links.push_back(link); } } } - tokenizer.advance(); } } void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) { - const String uri = get_uri(); r_symbol.uri = uri; r_symbol.script_path = path; r_symbol.children.clear(); - r_symbol.name = p_class->name; - if (r_symbol.name.empty()) + r_symbol.name = p_class->identifier != nullptr ? String(p_class->identifier->name) : String(); + if (r_symbol.name.empty()) { r_symbol.name = path.get_file(); + } r_symbol.kind = lsp::SymbolKind::Class; r_symbol.deprecated = false; - r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->line); - r_symbol.range.start.character = p_class->column; + r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->start_line); + r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_class->start_column); r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_class->end_line); r_symbol.selectionRange.start.line = r_symbol.range.start.line; r_symbol.detail = "class " + r_symbol.name; bool is_root_class = &r_symbol == &class_symbol; - r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class); - - for (int i = 0; i < p_class->variables.size(); ++i) { - - const GDScriptParser::ClassNode::Member &m = p_class->variables[i]; - - lsp::DocumentSymbol symbol; - symbol.name = m.identifier; - symbol.kind = lsp::SymbolKind::Variable; - symbol.deprecated = false; - const int line = LINE_NUMBER_TO_INDEX(m.line); - symbol.range.start.line = line; - symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length(); - symbol.range.end.line = line; - symbol.range.end.character = lines[line].length(); - symbol.selectionRange.start.line = symbol.range.start.line; - if (m._export.type != Variant::NIL) { - symbol.detail += "export "; - } - symbol.detail += "var " + m.identifier; - if (m.data_type.kind != GDScriptParser::DataType::UNRESOLVED) { - symbol.detail += ": " + m.data_type.to_string(); - } - if (m.default_value.get_type() != Variant::NIL) { - symbol.detail += " = " + JSON::print(m.default_value); - } - - symbol.documentation = parse_documentation(line); - symbol.uri = uri; - symbol.script_path = path; - - r_symbol.children.push_back(symbol); - } - - for (int i = 0; i < p_class->_signals.size(); ++i) { - const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i]; - - lsp::DocumentSymbol symbol; - symbol.name = signal.name; - symbol.kind = lsp::SymbolKind::Event; - symbol.deprecated = false; - const int line = LINE_NUMBER_TO_INDEX(signal.line); - symbol.range.start.line = line; - symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length(); - symbol.range.end.line = symbol.range.start.line; - symbol.range.end.character = lines[line].length(); - symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation(line); - symbol.uri = uri; - symbol.script_path = path; - symbol.detail = "signal " + signal.name + "("; - for (int j = 0; j < signal.arguments.size(); j++) { - if (j > 0) { - symbol.detail += ", "; - } - symbol.detail += signal.arguments[j]; - } - symbol.detail += ")"; - - r_symbol.children.push_back(symbol); - } + r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->start_line), is_root_class); + + for (int i = 0; i < p_class->members.size(); i++) { + const ClassNode::Member &m = p_class->members[i]; + + switch (m.type) { + case ClassNode::Member::VARIABLE: { + lsp::DocumentSymbol symbol; + symbol.name = m.variable->identifier->name; + symbol.kind = lsp::SymbolKind::Variable; + symbol.deprecated = false; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.variable->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.variable->start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.variable->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.variable->end_column); + symbol.selectionRange.start.line = symbol.range.start.line; + if (m.variable->exported) { + symbol.detail += "@export "; + } + symbol.detail += "var " + m.variable->identifier->name; + if (m.get_datatype().is_hard_type()) { + symbol.detail += ": " + m.get_datatype().to_string(); + } + if (m.variable->initializer != nullptr && m.variable->initializer->is_constant) { + symbol.detail += " = " + JSON::print(m.variable->initializer->reduced_value); + } - for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { - lsp::DocumentSymbol symbol; - const GDScriptParser::ClassNode::Constant &c = E->value(); - const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression); - ERR_FAIL_COND(!node); - symbol.name = E->key(); - symbol.kind = lsp::SymbolKind::Constant; - symbol.deprecated = false; - const int line = LINE_NUMBER_TO_INDEX(E->get().expression->line); - symbol.range.start.line = line; - symbol.range.start.character = E->get().expression->column; - symbol.range.end.line = symbol.range.start.line; - symbol.range.end.character = lines[line].length(); - symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation(line); - symbol.uri = uri; - symbol.script_path = path; + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.variable->start_line)); + symbol.uri = uri; + symbol.script_path = path; + + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::CONSTANT: { + lsp::DocumentSymbol symbol; + + symbol.name = m.constant->identifier->name; + symbol.kind = lsp::SymbolKind::Constant; + symbol.deprecated = false; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.constant->start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.constant->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.constant->start_column); + symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line); + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.constant->start_line)); + symbol.uri = uri; + symbol.script_path = path; + + symbol.detail = "const " + symbol.name; + if (m.constant->get_datatype().is_hard_type()) { + symbol.detail += ": " + m.constant->get_datatype().to_string(); + } - symbol.detail = "const " + symbol.name; - if (c.type.kind != GDScriptParser::DataType::UNRESOLVED) { - symbol.detail += ": " + c.type.to_string(); - } + const Variant &default_value = m.constant->initializer->reduced_value; + String value_text; + if (default_value.get_type() == Variant::OBJECT) { + RES res = default_value; + if (res.is_valid() && !res->get_path().empty()) { + value_text = "preload(\"" + res->get_path() + "\")"; + if (symbol.documentation.empty()) { + if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) { + symbol.documentation = S->get()->class_symbol.documentation; + } + } + } else { + value_text = JSON::print(default_value); + } + } else { + value_text = JSON::print(default_value); + } + if (!value_text.empty()) { + symbol.detail += " = " + value_text; + } - String value_text; - if (node->value.get_type() == Variant::OBJECT) { - RES res = node->value; - if (res.is_valid() && !res->get_path().empty()) { - value_text = "preload(\"" + res->get_path() + "\")"; - if (symbol.documentation.empty()) { - if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) { - symbol.documentation = S->get()->class_symbol.documentation; + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::ENUM_VALUE: { + lsp::DocumentSymbol symbol; + + symbol.name = m.constant->identifier->name; + symbol.kind = lsp::SymbolKind::EnumMember; + symbol.deprecated = false; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.enum_value.leftmost_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.enum_value.rightmost_column); + symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.enum_value.line)); + symbol.uri = uri; + symbol.script_path = path; + + symbol.detail = symbol.name + " = " + itos(m.enum_value.value); + + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::SIGNAL: { + lsp::DocumentSymbol symbol; + symbol.name = m.signal->identifier->name; + symbol.kind = lsp::SymbolKind::Event; + symbol.deprecated = false; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.signal->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.signal->start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.signal->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.signal->end_column); + symbol.selectionRange.start.line = symbol.range.start.line; + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.signal->start_line)); + symbol.uri = uri; + symbol.script_path = path; + symbol.detail = "signal " + String(m.signal->identifier->name) + "("; + for (int j = 0; j < m.signal->parameters.size(); j++) { + if (j > 0) { + symbol.detail += ", "; } + symbol.detail += m.signal->parameters[i]->identifier->name; } - } else { - value_text = JSON::print(node->value); - } - } else { - value_text = JSON::print(node->value); - } - if (!value_text.empty()) { - symbol.detail += " = " + value_text; + symbol.detail += ")"; + + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::ENUM: { + lsp::DocumentSymbol symbol; + symbol.kind = lsp::SymbolKind::Enum; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.m_enum->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.m_enum->start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.m_enum->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.m_enum->end_column); + symbol.selectionRange.start.line = symbol.range.start.line; + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.m_enum->start_line)); + symbol.uri = uri; + symbol.script_path = path; + + symbol.detail = "enum " + String(m.m_enum->identifier->name) + "{"; + for (int j = 0; j < m.m_enum->values.size(); j++) { + if (j > 0) { + symbol.detail += ", "; + } + symbol.detail += String(m.m_enum->values[j].identifier->name) + " = " + itos(m.m_enum->values[j].value); + } + symbol.detail += "}"; + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::FUNCTION: { + lsp::DocumentSymbol symbol; + parse_function_symbol(m.function, symbol); + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::CLASS: { + lsp::DocumentSymbol symbol; + parse_class_symbol(m.m_class, symbol); + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::UNDEFINED: + break; // Unreachable. } - - r_symbol.children.push_back(symbol); - } - - for (int i = 0; i < p_class->functions.size(); ++i) { - const GDScriptParser::FunctionNode *func = p_class->functions[i]; - lsp::DocumentSymbol symbol; - parse_function_symbol(func, symbol); - r_symbol.children.push_back(symbol); - } - - for (int i = 0; i < p_class->static_functions.size(); ++i) { - const GDScriptParser::FunctionNode *func = p_class->static_functions[i]; - lsp::DocumentSymbol symbol; - parse_function_symbol(func, symbol); - r_symbol.children.push_back(symbol); - } - - for (int i = 0; i < p_class->subclasses.size(); ++i) { - const GDScriptParser::ClassNode *subclass = p_class->subclasses[i]; - lsp::DocumentSymbol symbol; - parse_class_symbol(subclass, symbol); - r_symbol.children.push_back(symbol); } } void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol) { - const String uri = get_uri(); - r_symbol.name = p_func->name; + r_symbol.name = p_func->identifier->name; r_symbol.kind = lsp::SymbolKind::Function; - r_symbol.detail = "func " + p_func->name + "("; + r_symbol.detail = "func " + String(p_func->identifier->name) + "("; r_symbol.deprecated = false; - const int line = LINE_NUMBER_TO_INDEX(p_func->line); - r_symbol.range.start.line = line; - r_symbol.range.start.character = p_func->column; - r_symbol.range.end.line = MAX(p_func->body->end_line - 2, r_symbol.range.start.line); - r_symbol.range.end.character = lines[r_symbol.range.end.line].length(); + r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->start_line); + r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_func->start_column); + r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_func->start_line); + r_symbol.range.end.character = LINE_NUMBER_TO_INDEX(p_func->end_column); r_symbol.selectionRange.start.line = r_symbol.range.start.line; - r_symbol.documentation = parse_documentation(line); + r_symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(p_func->start_line)); r_symbol.uri = uri; r_symbol.script_path = path; - String arguments; - for (int i = 0; i < p_func->arguments.size(); i++) { + String parameters; + for (int i = 0; i < p_func->parameters.size(); i++) { + const ParameterNode *parameter = p_func->parameters[i]; lsp::DocumentSymbol symbol; symbol.kind = lsp::SymbolKind::Variable; - symbol.name = p_func->arguments[i]; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->body->line); - symbol.range.start.character = p_func->body->column; - symbol.range.end = symbol.range.start; + symbol.name = parameter->identifier->name; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(parameter->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_line); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(parameter->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(parameter->end_column); symbol.uri = uri; symbol.script_path = path; r_symbol.children.push_back(symbol); if (i > 0) { - arguments += ", "; + parameters += ", "; } - arguments += String(p_func->arguments[i]); - if (p_func->argument_types[i].kind != GDScriptParser::DataType::UNRESOLVED) { - arguments += ": " + p_func->argument_types[i].to_string(); + parameters += String(parameter->identifier->name); + if (parameter->get_datatype().is_hard_type()) { + parameters += ": " + parameter->get_datatype().to_string(); } - int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size()); - if (default_value_idx >= 0) { - const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]); - if (const_node == nullptr) { - const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]); - if (operator_node) { - const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next); - } - } - - if (const_node) { - String value = JSON::print(const_node->value); - arguments += " = " + value; - } + if (parameter->default_value != nullptr) { + String value = JSON::print(parameter->default_value->reduced_value); + parameters += " = " + value; } } - r_symbol.detail += arguments + ")"; - if (p_func->return_type.kind != GDScriptParser::DataType::UNRESOLVED) { - r_symbol.detail += " -> " + p_func->return_type.to_string(); + r_symbol.detail += parameters + ")"; + if (p_func->get_datatype().is_hard_type()) { + r_symbol.detail += " -> " + p_func->get_datatype().to_string(); } - for (const Map<StringName, LocalVarNode *>::Element *E = p_func->body->variables.front(); E; E = E->next()) { + for (int i = 0; i < p_func->body->locals.size(); i++) { + const SuiteNode::Local &local = p_func->body->locals[i]; lsp::DocumentSymbol symbol; - const GDScriptParser::LocalVarNode *var = E->value(); - symbol.name = E->key(); - symbol.kind = lsp::SymbolKind::Variable; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(E->get()->line); - symbol.range.start.character = E->get()->column; - symbol.range.end.line = symbol.range.start.line; - symbol.range.end.character = lines[symbol.range.end.line].length(); + symbol.name = local.name; + symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column); symbol.uri = uri; symbol.script_path = path; - symbol.detail = "var " + symbol.name; - if (var->datatype.kind != GDScriptParser::DataType::UNRESOLVED) { - symbol.detail += ": " + var->datatype.to_string(); + symbol.detail = SuiteNode::Local::CONSTANT ? "const " : "var "; + symbol.detail += symbol.name; + if (local.get_datatype().is_hard_type()) { + symbol.detail += ": " + local.get_datatype().to_string(); } - symbol.documentation = parse_documentation(line); + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line)); r_symbol.children.push_back(symbol); } } @@ -384,8 +401,9 @@ String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { int step = p_docs_down ? 1 : -1; int start_line = p_docs_down ? p_line : p_line - 1; for (int i = start_line; true; i += step) { - - if (i < 0 || i >= lines.size()) break; + if (i < 0 || i >= lines.size()) { + break; + } String line_comment = lines[i].strip_edges(true, false); if (line_comment.begins_with("#")) { @@ -408,22 +426,20 @@ String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { } String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_cursor) const { - String longthing; int len = lines.size(); for (int i = 0; i < len; i++) { - if (i == p_cursor.line) { longthing += lines[i].substr(0, p_cursor.character); longthing += String::chr(0xFFFF); //not unicode, represents the cursor longthing += lines[i].substr(p_cursor.character, lines[i].size()); } else { - longthing += lines[i]; } - if (i != len - 1) + if (i != len - 1) { longthing += "\n"; + } } return longthing; @@ -433,7 +449,6 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c String longthing; int len = lines.size(); for (int i = 0; i < len; i++) { - if (i == p_cursor.line) { String line = lines[i]; String first_part = line.substr(0, p_cursor.character); @@ -457,19 +472,18 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c } longthing += last_part; } else { - longthing += lines[i]; } - if (i != len - 1) + if (i != len - 1) { longthing += "\n"; + } } return longthing; } String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const { - ERR_FAIL_INDEX_V(p_position.line, lines.size(), ""); String line = lines[p_position.line]; ERR_FAIL_INDEX_V(p_position.character, line.size(), ""); @@ -524,7 +538,6 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(i } Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const { - ERR_FAIL_INDEX_V(p_position.line, lines.size(), ERR_INVALID_PARAMETER); int bracket_stack = 0; @@ -576,7 +589,6 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int } const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const { - if (p_subclass.empty()) { const lsp::DocumentSymbol *const *ptr = members.getptr(p_name); if (ptr) { @@ -599,12 +611,9 @@ const List<lsp::DocumentLink> &ExtendGDScriptParser::get_document_links() const } const Array &ExtendGDScriptParser::get_member_completions() { - if (member_completions.empty()) { - const String *name = members.next(nullptr); while (name) { - const lsp::DocumentSymbol *symbol = members.get(*name); lsp::CompletionItem item = symbol->make_completion_item(); item.data = JOIN_SYMBOLS(path, *name); @@ -615,7 +624,6 @@ const Array &ExtendGDScriptParser::get_member_completions() { const String *_class = inner_classes.next(nullptr); while (_class) { - const ClassMembers *inner_class = inner_classes.getptr(*_class); const String *member_name = inner_class->next(nullptr); while (member_name) { @@ -637,34 +645,24 @@ const Array &ExtendGDScriptParser::get_member_completions() { Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const { Dictionary func; ERR_FAIL_NULL_V(p_func, func); - func["name"] = p_func->name; - func["return_type"] = p_func->return_type.to_string(); + func["name"] = p_func->identifier->name; + func["return_type"] = p_func->get_datatype().to_string(); func["rpc_mode"] = p_func->rpc_mode; - Array arguments; - for (int i = 0; i < p_func->arguments.size(); i++) { + Array parameters; + for (int i = 0; i < p_func->parameters.size(); i++) { Dictionary arg; - arg["name"] = p_func->arguments[i]; - arg["type"] = p_func->argument_types[i].to_string(); - int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size()); - if (default_value_idx >= 0) { - const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]); - if (const_node == nullptr) { - const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]); - if (operator_node) { - const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next); - } - } - if (const_node) { - arg["default_value"] = const_node->value; - } + arg["name"] = p_func->parameters[i]->identifier->name; + arg["type"] = p_func->parameters[i]->get_datatype().to_string(); + if (p_func->parameters[i]->default_value != nullptr) { + arg["default_value"] = p_func->parameters[i]->default_value->reduced_value; } - arguments.push_back(arg); + parameters.push_back(arg); } - if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->line))) { + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->start_line))) { func["signature"] = symbol->detail; func["description"] = symbol->documentation; } - func["arguments"] = arguments; + func["arguments"] = parameters; return func; } @@ -673,101 +671,125 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode ERR_FAIL_NULL_V(p_class, class_api); - class_api["name"] = String(p_class->name); + class_api["name"] = p_class->identifier != nullptr ? String(p_class->identifier->name) : String(); class_api["path"] = path; Array extends_class; - for (int i = 0; i < p_class->extends_class.size(); i++) { - extends_class.append(String(p_class->extends_class[i])); + for (int i = 0; i < p_class->extends.size(); i++) { + extends_class.append(String(p_class->extends[i])); } class_api["extends_class"] = extends_class; - class_api["extends_file"] = String(p_class->extends_file); + class_api["extends_file"] = String(p_class->extends_path); class_api["icon"] = String(p_class->icon_path); - if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->line))) { + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->start_line))) { class_api["signature"] = symbol->detail; class_api["description"] = symbol->documentation; } - Array subclasses; - for (int i = 0; i < p_class->subclasses.size(); i++) { - subclasses.push_back(dump_class_api(p_class->subclasses[i])); - } - class_api["sub_classes"] = subclasses; - + Array nested_classes; Array constants; - for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { - - const GDScriptParser::ClassNode::Constant &c = E->value(); - const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression); - ERR_FAIL_COND_V(!node, class_api); - - Dictionary api; - api["name"] = E->key(); - api["value"] = node->value; - api["data_type"] = node->datatype.to_string(); - if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(node->line))) { - api["signature"] = symbol->detail; - api["description"] = symbol->documentation; - } - constants.push_back(api); - } - class_api["constants"] = constants; - Array members; - for (int i = 0; i < p_class->variables.size(); ++i) { - const GDScriptParser::ClassNode::Member &m = p_class->variables[i]; - Dictionary api; - api["name"] = m.identifier; - api["data_type"] = m.data_type.to_string(); - api["default_value"] = m.default_value; - api["setter"] = String(m.setter); - api["getter"] = String(m.getter); - api["export"] = m._export.type != Variant::NIL; - if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.line))) { - api["signature"] = symbol->detail; - api["description"] = symbol->documentation; - } - members.push_back(api); - } - class_api["members"] = members; - Array signals; - for (int i = 0; i < p_class->_signals.size(); ++i) { - const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i]; - Dictionary api; - api["name"] = signal.name; - Array args; - for (int j = 0; j < signal.arguments.size(); j++) { - args.append(signal.arguments[j]); - } - api["arguments"] = args; - if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(signal.line))) { - api["signature"] = symbol->detail; - api["description"] = symbol->documentation; + Array methods; + Array static_functions; + + for (int i = 0; i < p_class->members.size(); i++) { + const ClassNode::Member &m = p_class->members[i]; + switch (m.type) { + case ClassNode::Member::CLASS: + nested_classes.push_back(dump_class_api(m.m_class)); + break; + case ClassNode::Member::CONSTANT: { + Dictionary api; + api["name"] = m.constant->identifier->name; + api["value"] = m.constant->initializer->reduced_value; + api["data_type"] = m.constant->get_datatype().to_string(); + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.constant->start_line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + constants.push_back(api); + } break; + case ClassNode::Member::ENUM_VALUE: { + Dictionary api; + api["name"] = m.enum_value.identifier->name; + api["value"] = m.enum_value.value; + api["data_type"] = m.get_datatype().to_string(); + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.enum_value.line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + constants.push_back(api); + } break; + case ClassNode::Member::ENUM: { + Dictionary enum_dict; + for (int j = 0; j < m.m_enum->values.size(); i++) { + enum_dict[m.m_enum->values[i].identifier->name] = m.m_enum->values[i].value; + } + + Dictionary api; + api["name"] = m.m_enum->identifier->name; + api["value"] = enum_dict; + api["data_type"] = m.get_datatype().to_string(); + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.m_enum->start_line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + constants.push_back(api); + } break; + case ClassNode::Member::VARIABLE: { + Dictionary api; + api["name"] = m.variable->identifier->name; + api["data_type"] = m.variable->get_datatype().to_string(); + api["default_value"] = m.variable->initializer != nullptr ? m.variable->initializer->reduced_value : Variant(); + api["setter"] = m.variable->setter ? ("@" + String(m.variable->identifier->name) + "_setter") : (m.variable->setter_pointer != nullptr ? String(m.variable->setter_pointer->name) : String()); + api["getter"] = m.variable->getter ? ("@" + String(m.variable->identifier->name) + "_getter") : (m.variable->getter_pointer != nullptr ? String(m.variable->getter_pointer->name) : String()); + api["export"] = m.variable->exported; + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.variable->start_line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + members.push_back(api); + } break; + case ClassNode::Member::SIGNAL: { + Dictionary api; + api["name"] = m.signal->identifier->name; + Array pars; + for (int j = 0; j < m.signal->parameters.size(); j++) { + pars.append(String(m.signal->parameters[i]->identifier->name)); + } + api["arguments"] = pars; + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.signal->start_line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + signals.push_back(api); + } break; + case ClassNode::Member::FUNCTION: { + if (m.function->is_static) { + static_functions.append(dump_function_api(m.function)); + } else { + methods.append(dump_function_api(m.function)); + } + } break; + case ClassNode::Member::UNDEFINED: + break; // Unreachable. } - signals.push_back(api); } - class_api["signals"] = signals; - Array methods; - for (int i = 0; i < p_class->functions.size(); ++i) { - methods.append(dump_function_api(p_class->functions[i])); - } + class_api["sub_classes"] = nested_classes; + class_api["constants"] = constants; + class_api["members"] = members; + class_api["signals"] = signals; class_api["methods"] = methods; - - Array static_functions; - for (int i = 0; i < p_class->static_functions.size(); ++i) { - static_functions.append(dump_function_api(p_class->static_functions[i])); - } class_api["static_functions"] = static_functions; return class_api; } Dictionary ExtendGDScriptParser::generate_api() const { - Dictionary api; - const GDScriptParser::Node *head = get_parse_tree(); + const GDScriptParser::Node *head = get_tree(); if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) { api = dump_class_api(gdclass); } @@ -778,7 +800,11 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { path = p_path; lines = p_code.split("\n"); - Error err = GDScriptParser::parse(p_code, p_path.get_base_dir(), false, p_path, false, nullptr, false); + Error err = GDScriptParser::parse(p_code, p_path, false); + if (err == OK) { + GDScriptAnalyzer analyzer(this); + err = analyzer.analyze(); + } update_diagnostics(); update_symbols(); update_document_links(p_code); diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 43dfce994b..0c031d7883 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -50,7 +50,6 @@ typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers; class ExtendGDScriptParser : public GDScriptParser { - String path; Vector<String> lines; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 69662e96f7..2a67d2ff4f 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "gdscript_language_protocol.h" + #include "core/io/json.h" #include "core/os/copymem.h" #include "core/project_settings.h" @@ -47,10 +48,11 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { ERR_FAIL_COND_V_MSG(true, ERR_OUT_OF_MEMORY, "Response header too big"); } Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); - if (err != OK) + if (err != OK) { return FAILED; - else if (read != 1) // Busy, wait until next poll + } else if (read != 1) { // Busy, wait until next poll return ERR_BUSY; + } char *r = (char *)req_buf; int l = req_pos; @@ -75,10 +77,11 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { ERR_FAIL_COND_V_MSG(req_pos >= LSP_MAX_BUFFER_SIZE, ERR_OUT_OF_MEMORY, "Response content too big"); } Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); - if (err != OK) + if (err != OK) { return FAILED; - else if (read != 1) + } else if (read != 1) { return ERR_BUSY; + } req_pos++; } @@ -145,7 +148,6 @@ String GDScriptLanguageProtocol::process_message(const String &p_text) { } String GDScriptLanguageProtocol::format_output(const String &p_text) { - String header = "Content-Length: "; CharString charstr = p_text.utf8(); size_t len = charstr.length(); @@ -160,7 +162,7 @@ void GDScriptLanguageProtocol::_bind_methods() { ClassDB::bind_method(D_METHOD("initialized", "params"), &GDScriptLanguageProtocol::initialized); ClassDB::bind_method(D_METHOD("on_client_connected"), &GDScriptLanguageProtocol::on_client_connected); ClassDB::bind_method(D_METHOD("on_client_disconnected"), &GDScriptLanguageProtocol::on_client_disconnected); - ClassDB::bind_method(D_METHOD("notify_client", "p_method", "p_params"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("notify_client", "method", "params"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("is_smart_resolve_enabled"), &GDScriptLanguageProtocol::is_smart_resolve_enabled); ClassDB::bind_method(D_METHOD("get_text_document"), &GDScriptLanguageProtocol::get_text_document); ClassDB::bind_method(D_METHOD("get_workspace"), &GDScriptLanguageProtocol::get_workspace); @@ -168,7 +170,6 @@ void GDScriptLanguageProtocol::_bind_methods() { } Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { - lsp::InitializeResult ret; String root_uri = p_params["rootUri"]; @@ -183,13 +184,14 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { if (root_uri.length() && is_same_workspace) { workspace->root_uri = root_uri; } else { - workspace->root_uri = "file://" + workspace->root; Dictionary params; params["path"] = workspace->root; - Dictionary request = make_notification("gdscrip_client/changeWorkspace", params); + Dictionary request = make_notification("gdscript_client/changeWorkspace", params); + ERR_FAIL_COND_V_MSG(!clients.has(latest_client_id), ret.to_json(), + vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id)); Ref<LSPeer> peer = clients.get(latest_client_id); if (peer != nullptr) { String msg = JSON::print(request); @@ -208,12 +210,10 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { } void GDScriptLanguageProtocol::initialized(const Variant &p_params) { - lsp::GodotCapabilities capabilities; DocData *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; gdclass.class_doc = &(E->get()); @@ -271,8 +271,11 @@ void GDScriptLanguageProtocol::stop() { void GDScriptLanguageProtocol::notify_client(const String &p_method, const Variant &p_params, int p_client_id) { if (p_client_id == -1) { + ERR_FAIL_COND_MSG(latest_client_id == -1, + "GDScript LSP: Can't notify client as none was connected."); p_client_id = latest_client_id; } + ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); ERR_FAIL_COND(peer == nullptr); @@ -293,13 +296,10 @@ bool GDScriptLanguageProtocol::is_goto_native_symbols_enabled() const { GDScriptLanguageProtocol::GDScriptLanguageProtocol() { server.instance(); singleton = this; - _initialized = false; workspace.instance(); text_document.instance(); set_scope("textDocument", text_document.ptr()); set_scope("completionItem", text_document.ptr()); set_scope("workspace", workspace.ptr()); workspace->root = ProjectSettings::get_singleton()->get_resource_path(); - latest_client_id = 0; - next_client_id = 0; } diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index d929fd255d..cf5242e8c5 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -70,8 +70,8 @@ private: HashMap<int, Ref<LSPeer>> clients; Ref<TCP_Server> server; - int latest_client_id; - int next_client_id; + int latest_client_id = 0; + int next_client_id = 0; Ref<GDScriptTextDocument> text_document; Ref<GDScriptWorkspace> workspace; @@ -82,7 +82,7 @@ private: String process_message(const String &p_text); String format_output(const String &p_text); - bool _initialized; + bool _initialized = false; protected: static void _bind_methods(); diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index e1d86ecdd4..3387d262f8 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "gdscript_language_server.h" + #include "core/os/file_access.h" #include "core/os/os.h" #include "editor/editor_log.h" @@ -48,7 +49,6 @@ GDScriptLanguageServer::GDScriptLanguageServer() { } void GDScriptLanguageServer::_notification(int p_what) { - switch (p_what) { case NOTIFICATION_ENTER_TREE: start(); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index f065b33570..c6fe3169dc 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "gdscript_text_document.h" + #include "../gdscript.h" #include "core/os/os.h" #include "editor/editor_settings.h" @@ -85,19 +86,15 @@ void GDScriptTextDocument::notify_client_show_symbol(const lsp::DocumentSymbol * } void GDScriptTextDocument::initialize() { - if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - const HashMap<StringName, ClassMembers> &native_members = GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members; const StringName *class_ptr = native_members.next(nullptr); while (class_ptr) { - const ClassMembers &members = native_members.get(*class_ptr); const String *name = members.next(nullptr); while (name) { - const lsp::DocumentSymbol *symbol = members.get(*name); lsp::CompletionItem item = symbol->make_completion_item(); item.data = JOIN_SYMBOLS(String(*class_ptr), *name); @@ -112,7 +109,6 @@ void GDScriptTextDocument::initialize() { } Variant GDScriptTextDocument::nativeSymbol(const Dictionary &p_params) { - Variant ret; lsp::NativeSymbolInspectParams params; @@ -142,7 +138,6 @@ Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) { } Array GDScriptTextDocument::completion(const Dictionary &p_params) { - Array arr; lsp::CompletionParams params; @@ -153,12 +148,10 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { GDScriptLanguageProtocol::get_singleton()->get_workspace()->completion(params, &options); if (!options.empty()) { - int i = 0; arr.resize(options.size()); for (const List<ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) { - const ScriptCodeCompletionOption &option = E->get(); lsp::CompletionItem item; item.label = option.display; @@ -201,11 +194,9 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { i++; } } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - arr = native_member_completions.duplicate(); for (Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.front(); E; E = E->next()) { - ExtendGDScriptParser *script = E->get(); const Array &items = script->get_member_completions(); @@ -220,7 +211,6 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { } Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { - lsp::CompletionItem item; item.load(p_params); @@ -230,18 +220,15 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { const lsp::DocumentSymbol *symbol = nullptr; if (data.get_type() == Variant::DICTIONARY) { - params.load(p_params["data"]); symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params, item.label, item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function); } else if (data.get_type() == Variant::STRING) { - String query = data; Vector<String> param_symbols = query.split(SYMBOL_SEPERATOR, false); if (param_symbols.size() >= 2) { - String class_ = param_symbols[0]; StringName class_name = class_; String member_name = param_symbols[param_symbols.size() - 1]; @@ -313,13 +300,11 @@ Array GDScriptTextDocument::colorPresentation(const Dictionary &p_params) { } Variant GDScriptTextDocument::hover(const Dictionary &p_params) { - lsp::TextDocumentPositionParams params; params.load(p_params); const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params); if (symbol) { - lsp::Hover hover; hover.contents = symbol->render(); hover.range.start = params.position; @@ -327,7 +312,6 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) { return hover.to_json(); } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - Dictionary ret; Array contents; List<const lsp::DocumentSymbol *> list; diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 32fc8f36f0..776193e37c 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "gdscript_workspace.h" + #include "../gdscript.h" #include "../gdscript_parser.h" #include "core/project_settings.h" @@ -41,12 +42,12 @@ void GDScriptWorkspace::_bind_methods() { ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol); - ClassDB::bind_method(D_METHOD("parse_script", "p_path", "p_content"), &GDScriptWorkspace::parse_script); - ClassDB::bind_method(D_METHOD("parse_local_script", "p_path"), &GDScriptWorkspace::parse_local_script); - ClassDB::bind_method(D_METHOD("get_file_path", "p_uri"), &GDScriptWorkspace::get_file_path); - ClassDB::bind_method(D_METHOD("get_file_uri", "p_path"), &GDScriptWorkspace::get_file_uri); - ClassDB::bind_method(D_METHOD("publish_diagnostics", "p_path"), &GDScriptWorkspace::publish_diagnostics); - ClassDB::bind_method(D_METHOD("generate_script_api", "p_path"), &GDScriptWorkspace::generate_script_api); + ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script); + ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_local_script); + ClassDB::bind_method(D_METHOD("get_file_path", "uri"), &GDScriptWorkspace::get_file_path); + ClassDB::bind_method(D_METHOD("get_file_uri", "path"), &GDScriptWorkspace::get_file_uri); + ClassDB::bind_method(D_METHOD("publish_diagnostics", "path"), &GDScriptWorkspace::publish_diagnostics); + ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api); } void GDScriptWorkspace::remove_cache_parser(const String &p_path) { @@ -71,7 +72,6 @@ void GDScriptWorkspace::remove_cache_parser(const String &p_path) { } const lsp::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_class, const String &p_member) const { - StringName class_name = p_class; StringName empty; @@ -118,7 +118,7 @@ void GDScriptWorkspace::reload_all_workspace_scripts() { Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(path); String err_msg = "Failed parse script " + path; if (S) { - err_msg += "\n" + S->get()->get_error(); + err_msg += "\n" + S->get()->get_errors()[0].message; } ERR_CONTINUE_MSG(err != OK, err_msg); } @@ -185,11 +185,12 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) { } Error GDScriptWorkspace::initialize() { - if (initialized) return OK; + if (initialized) { + return OK; + } DocData *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; String class_name = E->key(); @@ -314,14 +315,12 @@ Error GDScriptWorkspace::initialize() { } Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_content) { - ExtendGDScriptParser *parser = memnew(ExtendGDScriptParser); Error err = parser->parse(p_content, p_path); Map<String, ExtendGDScriptParser *>::Element *last_parser = parse_results.find(p_path); Map<String, ExtendGDScriptParser *>::Element *last_script = scripts.find(p_path); if (err == OK) { - remove_cache_parser(p_path); parse_results[p_path] = parser; scripts[p_path] = parser; @@ -377,15 +376,15 @@ void GDScriptWorkspace::publish_diagnostics(const String &p_path) { } void GDScriptWorkspace::_get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners) { - if (!efsd) + if (!efsd) { return; + } for (int i = 0; i < efsd->get_subdir_count(); i++) { _get_owners(efsd->get_subdir(i), p_path, owners); } for (int i = 0; i < efsd->get_file_count(); i++) { - Vector<String> deps = efsd->get_file_deps(i); bool found = false; for (int j = 0; j < deps.size(); j++) { @@ -394,8 +393,9 @@ void GDScriptWorkspace::_get_owners(EditorFileSystemDirectory *efsd, String p_pa break; } } - if (!found) + if (!found) { continue; + } owners.push_back(efsd->get_file_path(i)); } @@ -421,7 +421,6 @@ Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) { } void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options) { - String path = get_file_path(p_params.textDocument.uri); String call_hint; bool forced = false; @@ -437,12 +436,10 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S } const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_requred) { - const lsp::DocumentSymbol *symbol = nullptr; String path = get_file_path(p_doc_pos.textDocument.uri); if (const ExtendGDScriptParser *parser = get_parse_result(path)) { - String symbol_identifier = p_symbol_name; Vector<String> identifier_parts = symbol_identifier.split("("); if (identifier_parts.size()) { @@ -457,19 +454,14 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } if (!symbol_identifier.empty()) { - if (ScriptServer::is_global_class(symbol_identifier)) { - String class_path = ScriptServer::get_global_class_path(symbol_identifier); symbol = get_script_symbol(class_path); } else { - ScriptLanguage::LookupResult ret; if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_requred), symbol_identifier, path, nullptr, ret)) { - if (ret.type == ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION) { - String target_script_path = path; if (!ret.script.is_null()) { target_script_path = ret.script->get_path(); @@ -480,7 +472,6 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } } else { - String member = ret.class_member; if (member.empty() && symbol_identifier != ret.class_name) { member = symbol_identifier; @@ -498,10 +489,8 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list) { - String path = get_file_path(p_doc_pos.textDocument.uri); if (const ExtendGDScriptParser *parser = get_parse_result(path)) { - String symbol_identifier; Vector2i offset; symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); @@ -525,7 +514,6 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP const HashMap<String, ClassMembers> &inner_classes = script->get_inner_classes(); const String *_class = inner_classes.next(nullptr); while (_class) { - const ClassMembers *inner_class = inner_classes.getptr(*_class); if (const lsp::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) { r_list.push_back(*symbol); @@ -538,7 +526,6 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP } const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params) { - if (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(p_params.native_class)) { const lsp::DocumentSymbol &symbol = E->get(); if (p_params.symbol_name.empty() || p_params.symbol_name == symbol.name) { @@ -574,12 +561,10 @@ Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) { Error GDScriptWorkspace::resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature) { if (const ExtendGDScriptParser *parser = get_parse_result(get_file_path(p_doc_pos.textDocument.uri))) { - lsp::TextDocumentPositionParams text_pos; text_pos.textDocument = p_doc_pos.textDocument; if (parser->get_left_function_call(p_doc_pos.position, text_pos.position, r_signature.activeParameter) == OK) { - List<const lsp::DocumentSymbol *> symbols; if (const lsp::DocumentSymbol *symbol = resolve_symbol(text_pos)) { @@ -591,7 +576,6 @@ Error GDScriptWorkspace::resolve_signature(const lsp::TextDocumentPositionParams for (List<const lsp::DocumentSymbol *>::Element *E = symbols.front(); E; E = E->next()) { const lsp::DocumentSymbol *symbol = E->get(); if (symbol->kind == lsp::SymbolKind::Method || symbol->kind == lsp::SymbolKind::Function) { - lsp::SignatureInformation signature_info; signature_info.label = symbol->detail; signature_info.documentation = symbol->render(); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 124fcbfed8..cf27a1578c 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -149,7 +149,6 @@ struct Location { * Represents a link between a source and a target location. */ struct LocationLink { - /** * Span of the origin of this link. * @@ -220,7 +219,6 @@ struct DocumentLinkParams { * text document or a web site. */ struct DocumentLink { - /** * The range this link applies to. */ @@ -282,7 +280,9 @@ struct Command { Dictionary dict; dict["title"] = title; dict["command"] = command; - if (arguments.size()) dict["arguments"] = arguments; + if (arguments.size()) { + dict["arguments"] = arguments; + } return dict; } }; @@ -486,7 +486,7 @@ struct TextDocumentSyncOptions { * If present save notifications are sent to the server. If omitted the notification should not be * sent. */ - SaveOptions save; + bool save = false; Dictionary to_json() { Dictionary dict; @@ -494,7 +494,7 @@ struct TextDocumentSyncOptions { dict["willSave"] = willSave; dict["openClose"] = openClose; dict["change"] = change; - dict["save"] = save.to_json(); + dict["save"] = save; return dict; } }; @@ -946,16 +946,24 @@ struct CompletionItem { dict["preselect"] = preselect; dict["sortText"] = sortText; dict["filterText"] = filterText; - if (commitCharacters.size()) dict["commitCharacters"] = commitCharacters; + if (commitCharacters.size()) { + dict["commitCharacters"] = commitCharacters; + } dict["command"] = command.to_json(); } return dict; } void load(const Dictionary &p_dict) { - if (p_dict.has("label")) label = p_dict["label"]; - if (p_dict.has("kind")) kind = p_dict["kind"]; - if (p_dict.has("detail")) detail = p_dict["detail"]; + if (p_dict.has("label")) { + label = p_dict["label"]; + } + if (p_dict.has("kind")) { + kind = p_dict["kind"]; + } + if (p_dict.has("detail")) { + detail = p_dict["detail"]; + } if (p_dict.has("documentation")) { Variant doc = p_dict["documentation"]; if (doc.get_type() == Variant::STRING) { @@ -965,12 +973,24 @@ struct CompletionItem { documentation.value = v["value"]; } } - if (p_dict.has("deprecated")) deprecated = p_dict["deprecated"]; - if (p_dict.has("preselect")) preselect = p_dict["preselect"]; - if (p_dict.has("sortText")) sortText = p_dict["sortText"]; - if (p_dict.has("filterText")) filterText = p_dict["filterText"]; - if (p_dict.has("insertText")) insertText = p_dict["insertText"]; - if (p_dict.has("data")) data = p_dict["data"]; + if (p_dict.has("deprecated")) { + deprecated = p_dict["deprecated"]; + } + if (p_dict.has("preselect")) { + preselect = p_dict["preselect"]; + } + if (p_dict.has("sortText")) { + sortText = p_dict["sortText"]; + } + if (p_dict.has("filterText")) { + filterText = p_dict["filterText"]; + } + if (p_dict.has("insertText")) { + insertText = p_dict["insertText"]; + } + if (p_dict.has("data")) { + data = p_dict["data"]; + } } }; @@ -1096,7 +1116,6 @@ struct DocumentedSymbolInformation : public SymbolInformation { * e.g. the range of an identifier. */ struct DocumentSymbol { - /** * The name of this symbol. Will be displayed in the user interface and therefore must not be * an empty string or a string only consisting of white spaces. @@ -1205,7 +1224,6 @@ struct DocumentSymbol { } _FORCE_INLINE_ CompletionItem make_completion_item(bool resolved = false) const { - lsp::CompletionItem item; item.label = name; @@ -1249,7 +1267,6 @@ struct DocumentSymbol { }; struct NativeSymbolInspectParams { - String native_class; String symbol_name; @@ -1281,7 +1298,6 @@ static const String Region = "region"; * Represents a folding range. */ struct FoldingRange { - /** * The zero-based line number from where the folded range starts. */ @@ -1364,7 +1380,6 @@ struct CompletionContext { }; struct CompletionParams : public TextDocumentPositionParams { - /** * The completion context. This is only available if the client specifies * to send this using `ClientCapabilities.textDocument.completion.contextSupport === true` @@ -1405,7 +1420,6 @@ struct Hover { * have a label and a doc-comment. */ struct ParameterInformation { - /** * The label of this parameter information. * @@ -1642,7 +1656,7 @@ struct ServerCapabilities { _FORCE_INLINE_ Dictionary to_json() { Dictionary dict; - dict["textDocumentSync"] = (int)textDocumentSync.change; + dict["textDocumentSync"] = textDocumentSync.to_json(); dict["completionProvider"] = completionProvider.to_json(); signatureHelpProvider.triggerCharacters.push_back(","); signatureHelpProvider.triggerCharacters.push_back("("); @@ -1684,7 +1698,6 @@ struct InitializeResult { }; struct GodotNativeClassInfo { - String name; const DocData::ClassDoc *class_doc = nullptr; const ClassDB::ClassInfo *class_info = nullptr; @@ -1699,7 +1712,6 @@ struct GodotNativeClassInfo { /** Features not included in the standard lsp specifications */ struct GodotCapabilities { - /** * Native class list */ @@ -1718,7 +1730,6 @@ struct GodotCapabilities { /** Format BBCode documentation from DocData to markdown */ static String marked_documentation(const String &p_bbcode) { - String markdown = p_bbcode.strip_edges(); Vector<String> lines = markdown.split("\n"); diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 62b9d94d6c..c554cbac05 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -35,31 +35,36 @@ #include "core/os/dir_access.h" #include "core/os/file_access.h" #include "gdscript.h" +#include "gdscript_analyzer.h" +#include "gdscript_cache.h" #include "gdscript_tokenizer.h" GDScriptLanguage *script_language_gd = nullptr; Ref<ResourceFormatLoaderGDScript> resource_loader_gd; Ref<ResourceFormatSaverGDScript> resource_saver_gd; +GDScriptCache *gdscript_cache = nullptr; #ifdef TOOLS_ENABLED #include "editor/editor_export.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/editor_translation_parser.h" #include "editor/gdscript_highlighter.h" +#include "editor/gdscript_translation_parser_plugin.h" #ifndef GDSCRIPT_NO_LSP #include "core/engine.h" #include "language_server/gdscript_language_server.h" #endif // !GDSCRIPT_NO_LSP -class EditorExportGDScript : public EditorExportPlugin { +Ref<GDScriptEditorTranslationParserPlugin> gdscript_translation_parser_plugin; +class EditorExportGDScript : public EditorExportPlugin { GDCLASS(EditorExportGDScript, EditorExportPlugin); public: - virtual void _export_file(const String &p_path, const String &p_type, const Set<String> &p_features) { - + virtual void _export_file(const String &p_path, const String &p_type, const Set<String> &p_features) override { int script_mode = EditorExportPreset::MODE_SCRIPT_COMPILED; String script_key; @@ -70,76 +75,26 @@ public: script_key = preset->get_script_encryption_key().to_lower(); } - if (!p_path.ends_with(".gd") || script_mode == EditorExportPreset::MODE_SCRIPT_TEXT) + if (!p_path.ends_with(".gd") || script_mode == EditorExportPreset::MODE_SCRIPT_TEXT) { return; - - Vector<uint8_t> file = FileAccess::get_file_as_array(p_path); - if (file.empty()) - return; - - String txt; - txt.parse_utf8((const char *)file.ptr(), file.size()); - file = GDScriptTokenizerBuffer::parse_code_string(txt); - - if (!file.empty()) { - - if (script_mode == EditorExportPreset::MODE_SCRIPT_ENCRYPTED) { - - String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("script.gde"); - FileAccess *fa = FileAccess::open(tmp_path, FileAccess::WRITE); - - Vector<uint8_t> key; - key.resize(32); - for (int i = 0; i < 32; i++) { - int v = 0; - if (i * 2 < script_key.length()) { - CharType ct = script_key[i * 2]; - if (ct >= '0' && ct <= '9') - ct = ct - '0'; - else if (ct >= 'a' && ct <= 'f') - ct = 10 + ct - 'a'; - v |= ct << 4; - } - - if (i * 2 + 1 < script_key.length()) { - CharType ct = script_key[i * 2 + 1]; - if (ct >= '0' && ct <= '9') - ct = ct - '0'; - else if (ct >= 'a' && ct <= 'f') - ct = 10 + ct - 'a'; - v |= ct; - } - key.write[i] = v; - } - FileAccessEncrypted *fae = memnew(FileAccessEncrypted); - Error err = fae->open_and_parse(fa, key, FileAccessEncrypted::MODE_WRITE_AES256); - - if (err == OK) { - fae->store_buffer(file.ptr(), file.size()); - } - - memdelete(fae); - - file = FileAccess::get_file_as_array(tmp_path); - add_file(p_path.get_basename() + ".gde", file, true); - - // Clean up temporary file. - DirAccess::remove_file_or_error(tmp_path); - - } else { - - add_file(p_path.get_basename() + ".gdc", file, true); - } } + + // TODO: Readd compiled/encrypted GDScript on export. + return; } }; static void _editor_init() { - Ref<EditorExportGDScript> gd_export; gd_export.instance(); EditorExport::get_singleton()->add_export_plugin(gd_export); +#ifdef TOOLS_ENABLED + Ref<GDScriptSyntaxHighlighter> gdscript_syntax_highlighter; + gdscript_syntax_highlighter.instance(); + ScriptEditor::get_singleton()->register_syntax_highlighter(gdscript_syntax_highlighter); +#endif + #ifndef GDSCRIPT_NO_LSP register_lsp_types(); GDScriptLanguageServer *lsp_plugin = memnew(GDScriptLanguageServer); @@ -151,7 +106,6 @@ static void _editor_init() { #endif // TOOLS_ENABLED void register_gdscript_types() { - ClassDB::register_class<GDScript>(); ClassDB::register_virtual_class<GDScriptFunctionState>(); @@ -164,22 +118,38 @@ void register_gdscript_types() { resource_saver_gd.instance(); ResourceSaver::add_resource_format_saver(resource_saver_gd); + gdscript_cache = memnew(GDScriptCache); + #ifdef TOOLS_ENABLED - ScriptEditor::register_create_syntax_highlighter_function(GDScriptSyntaxHighlighter::create); EditorNode::add_init_callback(_editor_init); + + gdscript_translation_parser_plugin.instance(); + EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD); #endif // TOOLS_ENABLED } void unregister_gdscript_types() { - ScriptServer::unregister_language(script_language_gd); - if (script_language_gd) + if (gdscript_cache) { + memdelete(gdscript_cache); + } + + if (script_language_gd) { memdelete(script_language_gd); + } ResourceLoader::remove_resource_format_loader(resource_loader_gd); resource_loader_gd.unref(); ResourceSaver::remove_resource_format_saver(resource_saver_gd); resource_saver_gd.unref(); + +#ifdef TOOLS_ENABLED + EditorTranslationParser::get_singleton()->remove_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD); + gdscript_translation_parser_plugin.unref(); +#endif // TOOLS_ENABLED + + GDScriptParser::cleanup(); + GDScriptAnalyzer::cleanup(); } |